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: