From 08d36e958bb1269fa640d351b48b54d38aa6de29 Mon Sep 17 00:00:00 2001 From: copilotcoder Date: Mon, 6 Jul 2026 23:12:38 -0400 Subject: [PATCH] Run integration and E2E against deployable app images --- .gitea/workflows/cicd.yaml | 65 ++++++++++----------- compose.ci.e2e.yml | 17 ------ compose.ci.integration.yml | 16 ------ scripts/run-ci-e2e-compose.sh | 15 ++++- scripts/run-ci-integration-compose.sh | 81 +++++++++++++++++++++++++-- 5 files changed, 120 insertions(+), 74 deletions(-) diff --git a/.gitea/workflows/cicd.yaml b/.gitea/workflows/cicd.yaml index ceb2470..8e7e503 100644 --- a/.gitea/workflows/cicd.yaml +++ b/.gitea/workflows/cicd.yaml @@ -647,7 +647,6 @@ jobs: COMPOSE_PROJECT_NAME="plex-int-${RUN_ID}-${RUN_ATTEMPT}" DB_PASSWORD="plex_password" DB_URL="postgresql://plex_user:${DB_PASSWORD}@database:5432/plex_playlist" - CICD_IMAGE="${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${HEAD_SHA}" if ! command -v docker >/dev/null 2>&1; then echo "❌ Docker is required" @@ -680,15 +679,6 @@ jobs: depends_on: database: condition: service_healthy - - integration-tests: - image: ${CICD_IMAGE} - depends_on: - backend: - condition: service_started - environment: - CI: "true" - INTEGRATION_BACKEND_URL: http://backend:8000 EOF fetch_backend_endpoint() { @@ -749,7 +739,6 @@ jobs: echo "${{ secrets.PACKAGE_ACCESS_TOKEN }}" | docker login "http://${GITEA_REGISTRY}" -u "${{ github.actor }}" --password-stdin - docker pull "${CICD_IMAGE}" docker pull "${DEPLOYABLE_BACKEND_TAG_REF}" docker pull "${DEPLOYABLE_BACKEND_DIGEST_REF}" @@ -776,7 +765,7 @@ jobs: echo "Resolved deployable backend tag: ${DEPLOYABLE_BACKEND_TAG_REF}" echo "Resolved deployable backend digest: ${EXPECTED_DIGEST_REF}" - export DB_PASSWORD DB_URL CICD_IMAGE DEPLOYABLE_BACKEND_IMAGE="${EXPECTED_DIGEST_REF}" + export DB_PASSWORD DB_URL DEPLOYABLE_BACKEND_IMAGE="${EXPECTED_DIGEST_REF}" docker compose -p "${COMPOSE_PROJECT_NAME}" -f "${COMPOSE_FILE}" up -d database backend @@ -841,9 +830,6 @@ jobs: exit 1 fi - docker compose -p "${COMPOSE_PROJECT_NAME}" -f "${COMPOSE_FILE}" run --rm integration-tests \ - bash -c "cd /workspace/backend && source .venv/bin/activate && uv run pytest -v --tb=short -m integration tests/integration/" - echo "✅ Runtime integration checks passed" } 2>&1 | tee "${LOG_FILE}" @@ -877,10 +863,10 @@ jobs: echo "timestamp_utc=$(date -u +%Y-%m-%dT%H:%M:%SZ)" - *configure_registry_host_step - - *ensure_cicd_image_step - name: Run E2E tests against runtime services via compose env: HEAD_SHA: ${{ needs.runtime_images.outputs.head_sha }} + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} DEPLOYABLE_BACKEND_TAG_REF: ${{ needs.runtime_images.outputs.deployable_backend_tag_ref }} DEPLOYABLE_BACKEND_DIGEST_REF: ${{ needs.runtime_images.outputs.deployable_backend_digest_ref }} DEPLOYABLE_FRONTEND_TAG_REF: ${{ needs.runtime_images.outputs.deployable_frontend_tag_ref }} @@ -896,7 +882,7 @@ jobs: COMPOSE_PROJECT_NAME="plex-e2e-${RUN_ID}-${RUN_ATTEMPT}" DB_PASSWORD="plex_password" DB_URL="postgresql://plex_user:${DB_PASSWORD}@database:5432/plex_playlist" - CICD_IMAGE="${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${HEAD_SHA}" + E2E_REPO_DIR="$(mktemp -d)" FRONTEND_URL_RUNNER="http://frontend:80" if ! command -v docker >/dev/null 2>&1; then @@ -936,19 +922,6 @@ jobs: depends_on: backend: condition: service_started - - e2e-tests: - image: ${CICD_IMAGE} - depends_on: - frontend: - condition: service_started - environment: - CI: "true" - PLAYWRIGHT_BASE_URL: http://frontend:80 - PLAYWRIGHT_OUTPUT_DIR: /tmp/playwright-artifacts - PLAYWRIGHT_JUNIT_OUTPUT_FILE: /tmp/playwright-artifacts/playwright-results.xml - volumes: - - ${E2E_ARTIFACT_DIR}:/tmp/playwright-artifacts EOF fetch_http() { @@ -1032,6 +1005,8 @@ jobs: cleanup() { docker compose -p "${COMPOSE_PROJECT_NAME}" -f "${COMPOSE_FILE}" down -v --remove-orphans >/dev/null 2>&1 || true + rm -rf "${E2E_REPO_DIR}" >/dev/null 2>&1 || true + rm -f ~/.ssh/id_rsa >/dev/null 2>&1 || true } trap cleanup EXIT @@ -1041,8 +1016,22 @@ jobs: exit 1 fi + mkdir -p ~/.ssh + echo "${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan -p "${GITEA_SSH_PORT}" "${GITEA_SSH_HOST}" >> ~/.ssh/known_hosts 2>/dev/null + + GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa -o IdentitiesOnly=yes -o StrictHostKeyChecking=no" \ + git clone --depth 1 --no-checkout "${GITEA_REPO_SSH_URL}" "${E2E_REPO_DIR}" + + if GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa -o IdentitiesOnly=yes -o StrictHostKeyChecking=no" \ + git -C "${E2E_REPO_DIR}" fetch --depth 1 origin "${HEAD_SHA}" >/dev/null 2>&1; then + git -C "${E2E_REPO_DIR}" checkout FETCH_HEAD -- frontend + else + git -C "${E2E_REPO_DIR}" checkout HEAD -- frontend + fi + echo "${{ secrets.PACKAGE_ACCESS_TOKEN }}" | docker login "http://${GITEA_REGISTRY}" -u "${{ github.actor }}" --password-stdin - docker pull "${CICD_IMAGE}" docker pull "${DEPLOYABLE_BACKEND_TAG_REF}" docker pull "${DEPLOYABLE_BACKEND_DIGEST_REF}" docker pull "${DEPLOYABLE_FRONTEND_TAG_REF}" @@ -1091,7 +1080,7 @@ jobs: echo "Resolved deployable backend digest: ${EXPECTED_BACKEND_DIGEST_REF}" echo "Resolved deployable frontend digest: ${EXPECTED_FRONTEND_DIGEST_REF}" - export DB_PASSWORD DB_URL CICD_IMAGE E2E_ARTIFACT_DIR="${ARTIFACT_DIR}" + export DB_PASSWORD DB_URL E2E_ARTIFACT_DIR="${ARTIFACT_DIR}" export DEPLOYABLE_BACKEND_IMAGE="${EXPECTED_BACKEND_DIGEST_REF}" export DEPLOYABLE_FRONTEND_IMAGE="${EXPECTED_FRONTEND_DIGEST_REF}" @@ -1161,8 +1150,16 @@ jobs: exit 1 fi - docker compose -p "${COMPOSE_PROJECT_NAME}" -f "${COMPOSE_FILE}" run --rm e2e-tests \ - bash -c "cd /workspace/frontend && yarn test:e2e --reporter=list --timeout=90000" \ + docker run --rm \ + --network "${COMPOSE_PROJECT_NAME}_default" \ + -e CI=true \ + -e PLAYWRIGHT_BASE_URL="http://frontend:80" \ + -e PLAYWRIGHT_OUTPUT_DIR=/tmp/playwright-artifacts \ + -e PLAYWRIGHT_JUNIT_OUTPUT_FILE=/tmp/playwright-artifacts/playwright-results.xml \ + -v "${E2E_REPO_DIR}/frontend:/workspace/frontend" \ + -v "${ARTIFACT_DIR}:/tmp/playwright-artifacts" \ + mcr.microsoft/playwright:v1.56.1-jammy \ + bash -lc "cd /workspace/frontend && corepack enable && yarn install --immutable && yarn test:e2e --reporter=list --timeout=90000" \ 2>&1 | tee "${LOG_FILE}" TEST_STATUS=${PIPESTATUS[0]} diff --git a/compose.ci.e2e.yml b/compose.ci.e2e.yml index 4655847..9e98761 100644 --- a/compose.ci.e2e.yml +++ b/compose.ci.e2e.yml @@ -26,20 +26,3 @@ services: depends_on: backend: condition: service_started - - e2e-tests: - image: ${CICD_IMAGE:-cicd:local} - depends_on: - frontend: - condition: service_started - environment: - CI: "true" - PLAYWRIGHT_BASE_URL: http://frontend:80 - PLAYWRIGHT_OUTPUT_DIR: /tmp/playwright-artifacts - PLAYWRIGHT_JUNIT_OUTPUT_FILE: /tmp/playwright-artifacts/playwright-results.xml - volumes: - - ${E2E_ARTIFACT_DIR:-./frontend/.playwright-artifacts}:/tmp/playwright-artifacts - command: - - bash - - -lc - - cd /workspace/frontend && yarn test:e2e --reporter=list --timeout=90000 diff --git a/compose.ci.integration.yml b/compose.ci.integration.yml index 94ec3d1..cbbfc8d 100644 --- a/compose.ci.integration.yml +++ b/compose.ci.integration.yml @@ -20,19 +20,3 @@ services: depends_on: database: condition: service_healthy - - integration-tests: - image: ${CICD_IMAGE:-cicd:local} - depends_on: - backend: - condition: service_started - environment: - CI: "true" - INTEGRATION_BACKEND_URL: http://backend:8000 - command: - - bash - - -lc - - >- - cd /workspace/backend && - source .venv/bin/activate && - uv run pytest -v --tb=short -m integration tests/integration/ diff --git a/scripts/run-ci-e2e-compose.sh b/scripts/run-ci-e2e-compose.sh index 98b1aec..bf0e55b 100644 --- a/scripts/run-ci-e2e-compose.sh +++ b/scripts/run-ci-e2e-compose.sh @@ -7,7 +7,6 @@ PROJECT_NAME="${COMPOSE_PROJECT_NAME:-plex-e2e-local}" mkdir -p "${ROOT_DIR}/frontend/.playwright-artifacts" -export CICD_IMAGE="${CICD_IMAGE:-cicd:local}" export DEPLOYABLE_BACKEND_IMAGE="${DEPLOYABLE_BACKEND_IMAGE:-deployable-backend:local}" export DEPLOYABLE_FRONTEND_IMAGE="${DEPLOYABLE_FRONTEND_IMAGE:-deployable-frontend:local}" export DB_PASSWORD="${DB_PASSWORD:-plex_password}" @@ -21,9 +20,19 @@ cleanup() { trap cleanup EXIT echo "Running E2E tests against runtime backend/frontend images via compose" -echo "CICD_IMAGE=${CICD_IMAGE}" echo "DEPLOYABLE_BACKEND_IMAGE=${DEPLOYABLE_BACKEND_IMAGE}" echo "DEPLOYABLE_FRONTEND_IMAGE=${DEPLOYABLE_FRONTEND_IMAGE}" echo "E2E_ARTIFACT_DIR=${E2E_ARTIFACT_DIR}" -docker compose -p "${PROJECT_NAME}" -f "${COMPOSE_FILE}" up --abort-on-container-exit --exit-code-from e2e-tests e2e-tests +docker compose -p "${PROJECT_NAME}" -f "${COMPOSE_FILE}" up -d database backend frontend + +docker run --rm \ + --network "${PROJECT_NAME}_default" \ + -e CI=true \ + -e PLAYWRIGHT_BASE_URL="http://frontend:80" \ + -e PLAYWRIGHT_OUTPUT_DIR=/tmp/playwright-artifacts \ + -e PLAYWRIGHT_JUNIT_OUTPUT_FILE=/tmp/playwright-artifacts/playwright-results.xml \ + -v "${ROOT_DIR}/frontend:/workspace/frontend" \ + -v "${E2E_ARTIFACT_DIR}:/tmp/playwright-artifacts" \ + mcr.microsoft/playwright:v1.56.1-jammy \ + bash -lc "cd /workspace/frontend && corepack enable && yarn install --immutable && yarn test:e2e --reporter=list --timeout=90000" diff --git a/scripts/run-ci-integration-compose.sh b/scripts/run-ci-integration-compose.sh index 418d7f1..b5a3e46 100644 --- a/scripts/run-ci-integration-compose.sh +++ b/scripts/run-ci-integration-compose.sh @@ -5,7 +5,6 @@ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" COMPOSE_FILE="${ROOT_DIR}/compose.ci.integration.yml" PROJECT_NAME="${COMPOSE_PROJECT_NAME:-plex-int-local}" -export CICD_IMAGE="${CICD_IMAGE:-cicd:local}" export DEPLOYABLE_BACKEND_IMAGE="${DEPLOYABLE_BACKEND_IMAGE:-deployable-backend:local}" export DB_PASSWORD="${DB_PASSWORD:-plex_password}" export DB_URL="${DB_URL:-postgresql://plex_user:${DB_PASSWORD}@database:5432/plex_playlist}" @@ -16,8 +15,82 @@ cleanup() { trap cleanup EXIT -echo "Running integration tests against runtime backend image via compose" -echo "CICD_IMAGE=${CICD_IMAGE}" +echo "Running runtime integration checks against deployable backend image via compose" echo "DEPLOYABLE_BACKEND_IMAGE=${DEPLOYABLE_BACKEND_IMAGE}" -docker compose -p "${PROJECT_NAME}" -f "${COMPOSE_FILE}" up --abort-on-container-exit --exit-code-from integration-tests integration-tests +docker compose -p "${PROJECT_NAME}" -f "${COMPOSE_FILE}" up -d database backend + +fetch_backend_endpoint() { + endpoint_path="$1" + output_file="$2" + probe_tmp="$(mktemp)" + + if ! docker compose -p "${PROJECT_NAME}" -f "${COMPOSE_FILE}" exec -T backend python -c "$(printf '%s\n' \ + 'import sys' \ + 'import urllib.error' \ + 'import urllib.request' \ + 'endpoint_path = sys.argv[1]' \ + 'url = f"http://127.0.0.1:8000{endpoint_path}"' \ + 'try:' \ + ' with urllib.request.urlopen(url, timeout=2) as response:' \ + ' body = response.read().decode("utf-8", errors="replace")' \ + ' print(response.status)' \ + ' print(body)' \ + 'except urllib.error.HTTPError as err:' \ + ' body = err.read().decode("utf-8", errors="replace")' \ + ' print(err.code)' \ + ' print(body)' \ + 'except Exception:' \ + ' print("000")' \ + ' print("")' + )" "${endpoint_path}" >"${probe_tmp}" 2>/dev/null + then + : >"${output_file}" + rm -f "${probe_tmp}" + echo "000" + return 0 + fi + + http_code="$(head -n 1 "${probe_tmp}")" + tail -n +2 "${probe_tmp}" >"${output_file}" + rm -f "${probe_tmp}" + echo "${http_code}" +} + +backend_ready=false +for i in $(seq 1 40); do + backend_running_count="$(docker compose -p "${PROJECT_NAME}" -f "${COMPOSE_FILE}" ps --status running --services backend | wc -l)" + if [ "${backend_running_count}" -eq 0 ]; then + echo "❌ Backend container exited before becoming healthy" + exit 1 + fi + + health_code="$(fetch_backend_endpoint "/health" /tmp/blackbox-health.json)" + if [ "${health_code}" = "200" ]; then + backend_ready=true + break + fi + sleep 2 +done + +if [ "${backend_ready}" != "true" ]; then + echo "❌ Backend did not become healthy" + cat /tmp/blackbox-health.json 2>/dev/null || true + exit 1 +fi + +root_code="$(fetch_backend_endpoint "/" /tmp/blackbox-root.json)" +if [ "${root_code}" != "200" ] || ! grep -q 'Plex Playlist Backend API' /tmp/blackbox-root.json; then + echo "❌ Root endpoint validation failed" + cat /tmp/blackbox-root.json 2>/dev/null || true + exit 1 +fi + +compatibility_code="$(fetch_backend_endpoint "/compatibility" /tmp/blackbox-compatibility.json)" +if [ "${compatibility_code}" != "200" ] || ! grep -q '"ok":true' /tmp/blackbox-compatibility.json; then + echo "❌ Compatibility endpoint validation failed" + cat /tmp/blackbox-compatibility.json 2>/dev/null || true + exit 1 +fi + +echo "✅ Runtime integration checks passed"