From 0e2699c2300af79ecbeb23ac4567633775467161 Mon Sep 17 00:00:00 2001 From: CptSchnitz <12687466+CptSchnitz@users.noreply.github.com> Date: Sun, 23 Aug 2026 14:40:57 +0300 Subject: [PATCH] ci(jobnik-e2e): make the e2e suite a gate on unreleased code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the e2e composition's sibling-repo build with the pruned build: both the manager and migrator services now build docker/backend.Dockerfile from the repository root with APP_NAME=jobnik-manager, instead of a `./jobnik-manager` context that only ever existed for the cross-repository checkout. The migrator's `npx prisma ...` also picks up the `prisma@6` pin ticket 05 flagged — without it, a fresh image build resolves Prisma 7 and the migration fails outright, for the same reason the image build itself does. The suite is wired into the single entry point for real: turbo's existing `e2e` task already dispatched to a package script, but `vitest run` alone never brought the composition up. It now does, via a `globalSetup`/`teardown` pair mirroring the manager's own integration-test convention (tests/configurations/integration/ globalSetup.ts) — `docker compose up --build --wait` before the suite, `down` after it in CI only, so a local run leaves the stack up for faster iteration. `--build` is unconditional rather than skip-if- running, because the whole point of this ticket is that the suite tests the current checkout, never a stale container. `jobnik-e2e` gained a `type-check` script and a `jobnik-manager` workspace:* devDependency it doesn't import from — the former wires `tsc --noEmit` into the existing type-check task for free (all it needed was the script to exist), the latter gives turbo's package graph an edge from the suite to the service it tests, which is what `--affected` actually walks. Without it, a manager-only change with no SDK or spec involvement would never mark the suite affected. Verified by touching apps/jobnik-manager/src/index.ts and confirming `turbo run e2e --affected --dry=json` includes jobnik-e2e. A new `e2e` job in pull_request.yaml runs `turbo run e2e --affected`, following the existing matrix jobs' pattern of always running but doing nothing when unaffected — there is no scheduled trigger. Wiring in type-check surfaced the staleness ticket 02 called out: the workspace SDK's generated types no longer accept the status values pause.spec.ts and wait.spec.ts were setting. Checked both against the current OpenAPI contract (stageOperationStatus: PENDING only; taskOperationStatus: COMPLETED/FAILED only) and against the manager's actual transition logic, not just the type error: tasks are created PENDING by TaskManager.addTasks regardless of the stage's status, and a stage created via startAsWaiting is already WAITING, so both specs' redundant status-setting calls were dead weight even before they became type errors — removed rather than retargeted at a different value. The one remaining explicit PENDING call in pause.spec.ts (on a stage that's already PENDING at creation, being the job's first stage) stays: it's a no-op the manager already treats as idempotent, still valid against the contract, and not what tsc flagged. Verified end-to-end: `turbo run e2e` builds both images from this checkout, runs migrations through the pinned prisma@6, brings up the full composition, and all 24 tests across 9 files pass, including the two fixed specs. `turbo run type-check lint test` stays green repo-wide, confirming the manager's own integration tests — which resolve their compose file by the same upward search, from a sibling directory that still has none — are unaffected by anything here. Refs: .scratch/monorepo-migration/issues/06-e2e-suite-branch-gate.md Co-Authored-By: Claude Sonnet 5 --- .github/workflows/pull_request.yaml | 16 ++++++++++++++++ e2e/README.md | 16 +++++++++++++--- e2e/docker-compose.yaml | 14 +++++++++++--- e2e/infrastructure/globalSetup.ts | 17 +++++++++++++++++ e2e/package.json | 8 ++++++-- e2e/tests/pause.spec.ts | 8 +++----- e2e/tests/wait.spec.ts | 14 +++++--------- e2e/vitest.config.mts | 1 + pnpm-lock.yaml | 9 +++++++++ 9 files changed, 81 insertions(+), 22 deletions(-) create mode 100644 e2e/infrastructure/globalSetup.ts diff --git a/.github/workflows/pull_request.yaml b/.github/workflows/pull_request.yaml index 76ae548..f4f6179 100644 --- a/.github/workflows/pull_request.yaml +++ b/.github/workflows/pull_request.yaml @@ -59,6 +59,22 @@ jobs: with: working-directory: apps/jobnik-manager + e2e: + name: Run E2E Suite + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + filter: 'blob:none' + + - name: Init nodejs + uses: ./.github/actions/init-pnpm + + - name: Run e2e suite + run: pnpm exec turbo run e2e --affected + helm-lint: name: Run Helm lint Check runs-on: ubuntu-latest diff --git a/e2e/README.md b/e2e/README.md index 599dba4..173a8f5 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -37,10 +37,16 @@ npm ci npm test ``` -**For Docker setup**: Ensure the `jobnik-manager` repository is cloned alongside this repo, then run: +**For Docker setup**: the manager image is built from this monorepo's own checkout, so no other repository needs to be cloned. From the repository root: ```bash -docker compose up -d +pnpm --filter jobnik-e2e run e2e +``` + +or, to manage the services directly: + +```bash +docker compose -f e2e/docker-compose.yaml up -d --build ``` ## Usage @@ -64,7 +70,7 @@ npm test -- -t "retry" ## Docker Services -The test suite uses Docker Compose to orchestrate services. **Note**: Docker setup requires the `jobnik-manager` repository to be cloned in the same directory. +The test suite uses Docker Compose to orchestrate services. The `manager` and `migrator` services build the manager image from this repository's own checkout (`docker/backend.Dockerfile`), so no other repository needs to be cloned. **Services:** @@ -88,6 +94,10 @@ docker compose down -v **Alternative**: You can also run the jobnik-manager server locally instead of using Docker. See the [jobnik-manager repository](https://github.com/MapColonies/jobnik-manager) for local development setup instructions. +### Turbo wiring + +`package.json` lists `jobnik-manager` as a devDependency even though nothing here imports it. It's not dead weight: turbo derives which packages a change "affects" from the workspace dependency graph, and this suite's only real link to the manager is the Docker image built above, which turbo can't see. The unused dependency gives turbo that edge, so `turbo run e2e --affected` (and CI's e2e job) actually run when the manager changes. + ## Test Suites | Test File | Description | diff --git a/e2e/docker-compose.yaml b/e2e/docker-compose.yaml index 28e7d94..3b82fe7 100644 --- a/e2e/docker-compose.yaml +++ b/e2e/docker-compose.yaml @@ -2,8 +2,12 @@ version: '3.8' services: manager: + image: jobnik-manager:e2e build: - context: ./jobnik-manager + context: .. + dockerfile: docker/backend.Dockerfile + args: + APP_NAME: jobnik-manager ports: - '8080:8080' environment: @@ -28,15 +32,19 @@ services: migrator: restart: 'no' + image: jobnik-manager:e2e build: - context: ./jobnik-manager + context: .. + dockerfile: docker/backend.Dockerfile + args: + APP_NAME: jobnik-manager depends_on: db: condition: service_healthy environment: - DATABASE_URL=postgresql://postgres:postgres@db:5432/jobnik?schema=job_manager entrypoint: ['npx'] - command: ['prisma', 'migrate', 'deploy', '--schema', './db/prisma/schema.prisma'] + command: ['prisma@6', 'migrate', 'deploy', '--schema', './db/prisma/schema.prisma'] db: image: postgres:14 diff --git a/e2e/infrastructure/globalSetup.ts b/e2e/infrastructure/globalSetup.ts new file mode 100644 index 0000000..fc61607 --- /dev/null +++ b/e2e/infrastructure/globalSetup.ts @@ -0,0 +1,17 @@ +import path from 'node:path'; +import { upAll, downAll } from 'docker-compose'; +import isCI from 'is-ci'; + +const composeOptions = { cwd: path.join(__dirname, '..'), log: true }; + +export async function setup(): Promise { + // Always rebuild: the suite must run against an image built from the current checkout, + // never a stale one left over from a previous invocation. + await upAll({ ...composeOptions, commandOptions: ['--build', '--wait'] }); +} + +export async function teardown(): Promise { + if (isCI) { + await downAll({ ...composeOptions, commandOptions: ['-v'] }); + } +} diff --git a/e2e/package.json b/e2e/package.json index 32a4025..5efc25e 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -7,7 +7,8 @@ "scripts": { "e2e": "vitest run", "e2e:watch": "vitest watch", - "e2e:ui": "vitest --ui" + "e2e:ui": "vitest --ui", + "type-check": "tsc --noEmit" }, "repository": { "type": "git", @@ -32,6 +33,9 @@ }, "devDependencies": { "@faker-js/faker": "^10.0.0", - "@vitest/ui": "^3.2.4" + "@vitest/ui": "^3.2.4", + "docker-compose": "^1.3.0", + "is-ci": "^4.1.0", + "jobnik-manager": "workspace:*" } } diff --git a/e2e/tests/pause.spec.ts b/e2e/tests/pause.spec.ts index 721d61d..fc5f936 100644 --- a/e2e/tests/pause.spec.ts +++ b/e2e/tests/pause.spec.ts @@ -49,11 +49,9 @@ describe('pause test', () => { }); const taskSampleData = createTaskData(); - const task = await producer.createTasks(stage.id, stage.type, [taskSampleData]); - await api.PUT('/v1/tasks/{taskId}/status', { - body: { status: 'PENDING' }, - params: { path: { taskId: task[0]!.id } }, - }); + // Tasks are created PENDING already; the status endpoint only accepts COMPLETED/FAILED, so + // there is no user-facing way to set PENDING here (and none is needed). + await producer.createTasks(stage.id, stage.type, [taskSampleData]); //#endregion const dequeueResult = await consumer.dequeueTask(stage.type); diff --git a/e2e/tests/wait.spec.ts b/e2e/tests/wait.spec.ts index ea97807..b9a01f2 100644 --- a/e2e/tests/wait.spec.ts +++ b/e2e/tests/wait.spec.ts @@ -38,18 +38,14 @@ describe('wait test', () => { //#region create stage const stageSampleData = createStageData(); + // startAsWaiting creates the stage as WAITING directly; the status endpoint only accepts + // PENDING (WAITING is system-managed), so there is no user-facing way to set it here. const stage = await producer.createStage(job.id, stageSampleData, true); - await api.PUT('/v1/stages/{stageId}/status', { - body: { status: 'WAITING' }, - params: { path: { stageId: stage.id } }, - }); const taskSampleData = createTaskData(); - const task = await producer.createTasks(stage.id, stage.type, [taskSampleData]); - await api.PUT('/v1/tasks/{taskId}/status', { - body: { status: 'PENDING' }, - params: { path: { taskId: task[0]!.id } }, - }); + // Tasks are created PENDING already, even under a WAITING stage; the status endpoint only + // accepts COMPLETED/FAILED, so there is no user-facing way to set PENDING here. + await producer.createTasks(stage.id, stage.type, [taskSampleData]); //#endregion const dequeueResult = await consumer.dequeueTask(stage.type); diff --git a/e2e/vitest.config.mts b/e2e/vitest.config.mts index d29e725..9123336 100644 --- a/e2e/vitest.config.mts +++ b/e2e/vitest.config.mts @@ -10,6 +10,7 @@ export default defineConfig({ test: { include: ['tests/*.spec.ts'], environment: 'node', + globalSetup: ['./infrastructure/globalSetup.ts'], reporters, }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4595e73..70d27b8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -289,6 +289,15 @@ importers: '@vitest/ui': specifier: ^3.2.4 version: 3.2.7(vitest@3.2.7) + docker-compose: + specifier: ^1.3.0 + version: 1.4.2 + is-ci: + specifier: ^4.1.0 + version: 4.1.0 + jobnik-manager: + specifier: workspace:* + version: link:../apps/jobnik-manager packages/jobnik-openapi: devDependencies: