ci: standardize E2E naming across workflows and scripts

This commit is contained in:
neon
2026-04-02 05:25:00 -06:00
parent 00ecdc34cd
commit 6e999ccaa8
24 changed files with 344 additions and 194 deletions
+40
View File
@@ -0,0 +1,40 @@
name: E2E - App Smoke
run-name: ${{ format('E2E - App Smoke - {0}', github.ref_name) }}
on:
workflow_dispatch:
push:
paths:
- "server/src/**"
- "server/test/app-smoke.e2e-spec.ts"
- "server/src/db/schema/**"
- "packages/types/src/**"
- "scripts/test/**"
- "scripts/db/**"
- ".github/workflows/e2e-runner.yml"
pull_request:
paths:
- "server/src/**"
- "server/test/app-smoke.e2e-spec.ts"
- "server/src/db/schema/**"
- "packages/types/src/**"
- "scripts/test/**"
- "scripts/db/**"
- ".github/workflows/e2e-runner.yml"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
checks: write
jobs:
app-smoke:
uses: ./.github/workflows/e2e-runner.yml
with:
suite-id: app-smoke
check-name: E2E - App Smoke
artifact-name: e2e-app-smoke-report
job-timeout-minutes: 30
@@ -0,0 +1,48 @@
name: E2E - Auth Recovery OIDC Logout
run-name: ${{ format('E2E - Auth Recovery OIDC Logout - {0}', github.ref_name) }}
on:
workflow_dispatch:
push:
paths:
- "server/src/modules/auth/**"
- "server/src/modules/user/**"
- "server/src/modules/email/**"
- "server/src/modules/metadata/**"
- "server/src/db/schema/**"
- "server/test/auth-recovery-oidc-logout.e2e-spec.ts"
- "server/test/e2e/app-harness.ts"
- "packages/types/src/auth.ts"
- "scripts/test/**"
- "scripts/db/**"
- ".github/workflows/e2e-runner.yml"
pull_request:
paths:
- "server/src/modules/auth/**"
- "server/src/modules/user/**"
- "server/src/modules/email/**"
- "server/src/modules/metadata/**"
- "server/src/db/schema/**"
- "server/test/auth-recovery-oidc-logout.e2e-spec.ts"
- "server/test/e2e/app-harness.ts"
- "packages/types/src/auth.ts"
- "scripts/test/**"
- "scripts/db/**"
- ".github/workflows/e2e-runner.yml"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
checks: write
jobs:
auth-recovery-oidc-logout:
uses: ./.github/workflows/e2e-runner.yml
with:
suite-id: auth-recovery-oidc-logout
check-name: E2E - Auth Recovery OIDC Logout
artifact-name: e2e-auth-recovery-oidc-logout-report
job-timeout-minutes: 50
@@ -0,0 +1,54 @@
name: E2E - Auth Session Security
run-name: ${{ format('E2E - Auth Session Security - {0}', github.ref_name) }}
on:
workflow_dispatch:
push:
paths:
- "server/src/modules/auth/**"
- "server/src/modules/user/**"
- "server/src/modules/role/**"
- "server/src/common/guards/**"
- "server/src/common/decorators/**"
- "server/src/common/types/**"
- "server/src/db/schema/**"
- "server/test/auth-session-security.e2e-spec.ts"
- "server/test/e2e/app-harness.ts"
- "packages/types/src/auth.ts"
- "packages/types/src/permissions.ts"
- "scripts/test/**"
- "scripts/db/**"
- ".github/workflows/e2e-runner.yml"
pull_request:
paths:
- "server/src/modules/auth/**"
- "server/src/modules/user/**"
- "server/src/modules/role/**"
- "server/src/common/guards/**"
- "server/src/common/decorators/**"
- "server/src/common/types/**"
- "server/src/db/schema/**"
- "server/test/auth-session-security.e2e-spec.ts"
- "server/test/e2e/app-harness.ts"
- "packages/types/src/auth.ts"
- "packages/types/src/permissions.ts"
- "scripts/test/**"
- "scripts/db/**"
- ".github/workflows/e2e-runner.yml"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
checks: write
jobs:
auth-session-security:
uses: ./.github/workflows/e2e-runner.yml
with:
suite-id: auth-session-security
check-name: E2E - Auth Session Security
artifact-name: e2e-auth-session-security-report
job-timeout-minutes: 50
@@ -0,0 +1,42 @@
name: E2E - Authorization Matrix
run-name: ${{ format('E2E - Authorization Matrix - {0}', github.ref_name) }}
on:
workflow_dispatch:
push:
paths:
- "server/src/**"
- "server/src/db/schema/**"
- "server/test/authorization-matrix.e2e-spec.ts"
- "server/test/e2e/authorization-matrix/**"
- "packages/types/src/**"
- "scripts/test/**"
- "scripts/db/**"
- ".github/workflows/e2e-runner.yml"
pull_request:
paths:
- "server/src/**"
- "server/src/db/schema/**"
- "server/test/authorization-matrix.e2e-spec.ts"
- "server/test/e2e/authorization-matrix/**"
- "packages/types/src/**"
- "scripts/test/**"
- "scripts/db/**"
- ".github/workflows/e2e-runner.yml"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
checks: write
jobs:
authorization-matrix:
uses: ./.github/workflows/e2e-runner.yml
with:
suite-id: authorization-matrix
check-name: E2E - Authorization Matrix
artifact-name: e2e-authorization-matrix-report
job-timeout-minutes: 60
+5 -5
View File
@@ -1,5 +1,5 @@
name: Email Lifecycle E2E
run-name: ${{ format('Email Lifecycle E2E - {0}', github.ref_name) }}
name: E2E - Email Lifecycle
run-name: ${{ format('E2E - Email Lifecycle - {0}', github.ref_name) }}
on:
workflow_dispatch:
@@ -61,10 +61,10 @@ permissions:
checks: write
jobs:
email-lifecycle-e2e:
email-lifecycle:
uses: ./.github/workflows/e2e-runner.yml
with:
suite-id: email-lifecycle
check-name: Server Email Lifecycle E2E
artifact-name: email-lifecycle-e2e-report
check-name: E2E - Email Lifecycle
artifact-name: e2e-email-lifecycle-report
job-timeout-minutes: 45
+6 -6
View File
@@ -1,11 +1,11 @@
name: Metadata Write E2E
run-name: ${{ format('Metadata Write E2E - {0}', github.ref_name) }}
name: E2E - Metadata Write
run-name: ${{ format('E2E - Metadata Write - {0}', github.ref_name) }}
on:
workflow_dispatch:
inputs:
vitest-args:
description: "Optional vitest args (examples: --shard=1/2, --testNamePattern=\"concurrency hardening\")"
description: 'Optional vitest args (examples: --shard=1/2, --testNamePattern="concurrency hardening")'
required: false
default: ""
type: string
@@ -45,11 +45,11 @@ permissions:
checks: write
jobs:
metadata-write-e2e:
metadata-write:
uses: ./.github/workflows/e2e-runner.yml
with:
suite-id: metadata-write
check-name: Server Metadata Write E2E
artifact-name: metadata-write-e2e-report
check-name: E2E - Metadata Write
artifact-name: e2e-metadata-write-report
job-timeout-minutes: 60
vitest-args: ${{ github.event_name == 'workflow_dispatch' && inputs.vitest-args || '' }}
+5 -5
View File
@@ -1,5 +1,5 @@
name: OPDS Auth Catalog E2E
run-name: ${{ format('OPDS Auth Catalog E2E - {0}', github.ref_name) }}
name: E2E - OPDS Auth Catalog
run-name: ${{ format('E2E - OPDS Auth Catalog - {0}', github.ref_name) }}
on:
workflow_dispatch:
@@ -47,10 +47,10 @@ permissions:
checks: write
jobs:
opds-auth-catalog-e2e:
opds-auth-catalog:
uses: ./.github/workflows/e2e-runner.yml
with:
suite-id: opds-auth-catalog
check-name: Server OPDS Auth Catalog E2E
artifact-name: opds-auth-catalog-e2e-report
check-name: E2E - OPDS Auth Catalog
artifact-name: e2e-opds-auth-catalog-report
job-timeout-minutes: 45
@@ -1,5 +1,5 @@
name: Reader State Isolation E2E
run-name: ${{ format('Reader State Isolation E2E - {0}', github.ref_name) }}
name: E2E - Reader State Isolation
run-name: ${{ format('E2E - Reader State Isolation - {0}', github.ref_name) }}
on:
workflow_dispatch:
@@ -47,10 +47,10 @@ permissions:
checks: write
jobs:
reader-state-isolation-e2e:
reader-state-isolation:
uses: ./.github/workflows/e2e-runner.yml
with:
suite-id: reader-state-isolation
check-name: Server Reader State Isolation E2E
artifact-name: reader-state-isolation-e2e-report
check-name: E2E - Reader State Isolation
artifact-name: e2e-reader-state-isolation-report
job-timeout-minutes: 45
+3 -3
View File
@@ -4,7 +4,7 @@ on:
workflow_call:
inputs:
suite-id:
description: "E2E suite id from scripts/test/e2e-suites.mjs (e.g. scanner)"
description: "E2E suite id from scripts/test/e2e-suites.mjs (e.g. scanner-scenarios)"
required: true
type: string
check-name:
@@ -72,9 +72,9 @@ jobs:
shell: bash
run: |
if [ -n "${{ inputs.vitest-args }}" ]; then
pnpm run test:e2e -- ${{ inputs.suite-id }} ${{ inputs.vitest-args }}
pnpm run e2e:run -- ${{ inputs.suite-id }} ${{ inputs.vitest-args }}
else
pnpm run test:e2e -- ${{ inputs.suite-id }}
pnpm run e2e:run -- ${{ inputs.suite-id }}
fi
- name: Publish test report annotations
@@ -1,5 +1,5 @@
name: Scanner File Ops E2E
run-name: ${{ format('Scanner File Ops E2E - {0}', github.ref_name) }}
name: E2E - Scanner File Operations
run-name: ${{ format('E2E - Scanner File Operations - {0}', github.ref_name) }}
on:
workflow_dispatch:
@@ -8,19 +8,21 @@ on:
push:
paths:
- "server/src/modules/scanner/**"
- "server/test/scanner*"
- "server/test/e2e/**"
- "server/test/scanner-file-operations.e2e-spec.ts"
- "server/test/e2e/scanner/**"
- "server/src/db/schema/**"
- "scripts/test/**"
- "scripts/db/**"
- ".github/workflows/e2e-runner.yml"
pull_request:
paths:
- "server/src/modules/scanner/**"
- "server/test/scanner*"
- "server/test/e2e/**"
- "server/test/scanner-file-operations.e2e-spec.ts"
- "server/test/e2e/scanner/**"
- "server/src/db/schema/**"
- "scripts/test/**"
- "scripts/db/**"
- ".github/workflows/e2e-runner.yml"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -31,10 +33,10 @@ permissions:
checks: write
jobs:
scanner-file-ops-e2e:
scanner-file-operations:
uses: ./.github/workflows/e2e-runner.yml
with:
suite-id: scanner-file-ops
check-name: Server Scanner File Ops E2E
artifact-name: scanner-file-ops-e2e-report
suite-id: scanner-file-operations
check-name: E2E - Scanner File Operations
artifact-name: e2e-scanner-file-operations-report
job-timeout-minutes: 60
@@ -1,24 +1,26 @@
name: Scanner E2E
run-name: ${{ format('Scanner E2E - {0}', github.ref_name) }}
name: E2E - Scanner Scenarios
run-name: ${{ format('E2E - Scanner Scenarios - {0}', github.ref_name) }}
on:
workflow_dispatch:
push:
paths:
- "server/src/modules/scanner/**"
- "server/test/scanner*"
- "server/test/e2e/**"
- "server/test/scanner-scenarios.e2e-spec.ts"
- "server/test/e2e/scanner/**"
- "server/src/db/schema/**"
- "scripts/test/**"
- "scripts/db/**"
- ".github/workflows/e2e-runner.yml"
pull_request:
paths:
- "server/src/modules/scanner/**"
- "server/test/scanner*"
- "server/test/e2e/**"
- "server/test/scanner-scenarios.e2e-spec.ts"
- "server/test/e2e/scanner/**"
- "server/src/db/schema/**"
- "scripts/test/**"
- "scripts/db/**"
- ".github/workflows/e2e-runner.yml"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -29,10 +31,10 @@ permissions:
checks: write
jobs:
scanner-e2e:
scanner-scenarios:
uses: ./.github/workflows/e2e-runner.yml
with:
suite-id: scanner
check-name: Server Scanner E2E
artifact-name: scanner-e2e-report
suite-id: scanner-scenarios
check-name: E2E - Scanner Scenarios
artifact-name: e2e-scanner-scenarios-report
job-timeout-minutes: 45
@@ -1,5 +1,5 @@
name: Staging Ingest Finalize E2E
run-name: ${{ format('Staging Ingest Finalize E2E - {0}', github.ref_name) }}
name: E2E - Staging Ingest Finalize
run-name: ${{ format('E2E - Staging Ingest Finalize - {0}', github.ref_name) }}
on:
workflow_dispatch:
@@ -33,10 +33,10 @@ permissions:
checks: write
jobs:
staging-ingest-finalize-e2e:
staging-ingest-finalize:
uses: ./.github/workflows/e2e-runner.yml
with:
suite-id: staging-ingest-finalize
check-name: Server Staging Ingest Finalize E2E
artifact-name: staging-ingest-finalize-e2e-report
check-name: E2E - Staging Ingest Finalize
artifact-name: e2e-staging-ingest-finalize-report
job-timeout-minutes: 45
@@ -1,5 +1,5 @@
name: Users Admin Lifecycle E2E
run-name: ${{ format('Users Admin Lifecycle E2E - {0}', github.ref_name) }}
name: E2E - Users Admin Lifecycle
run-name: ${{ format('E2E - Users Admin Lifecycle - {0}', github.ref_name) }}
on:
workflow_dispatch:
@@ -43,10 +43,10 @@ permissions:
checks: write
jobs:
users-admin-lifecycle-e2e:
users-admin-lifecycle:
uses: ./.github/workflows/e2e-runner.yml
with:
suite-id: users-admin-lifecycle
check-name: Server Users Admin Lifecycle E2E
artifact-name: users-admin-lifecycle-e2e-report
check-name: E2E - Users Admin Lifecycle
artifact-name: e2e-users-admin-lifecycle-report
job-timeout-minutes: 50
+5 -5
View File
@@ -118,9 +118,9 @@ pnpm verify # run all checks before pushing
Other useful commands:
```bash
pnpm quick # faster checks while coding
pnpm verify:fast # lint + typecheck + tests (pre-push gate)
pnpm db:reset # wipe and reseed local database
pnpm doctor # environment health check
pnpm e2e:list # list available e2e suite ids
```
---
@@ -205,9 +205,9 @@ Use a **draft PR** early if you want feedback on direction before the work is do
The project has two automatic gates you should be aware of:
- **pre-commit:** runs `lint-staged` on your staged files. ESLint and Prettier are applied automatically. You do not need to format or lint manually before committing.
- **pre-push:** runs `pnpm quick` (lint + typecheck + tests). If this fails, the push is blocked. Fix the errors before pushing.
- **pre-push:** runs `pnpm verify:fast` (lint + typecheck + tests). If this fails, the push is blocked. Fix the errors before pushing.
`pnpm quick` is a fast check but does not include the e2e smoke suite. Before opening a PR, run the full gate:
`pnpm verify:fast` is a fast check but does not include the `app-smoke` e2e suite. Before opening a PR, run the full gate:
```bash
pnpm verify
@@ -216,7 +216,7 @@ pnpm verify
Before marking your PR ready for review:
- [ ] Linked to an approved issue
- [ ] `pnpm verify` passes locally (includes e2e smoke, stricter than pre-push)
- [ ] `pnpm verify` passes locally (includes `app-smoke`, stricter than pre-push)
- [ ] You ran the full stack and manually verified the change works
- [ ] UI changes include a screenshot or screen recording
- [ ] Tests are included per the expectations above
+52 -84
View File
@@ -114,21 +114,21 @@ Opens Drizzle Studio in your browser for browsing and editing tables directly.
pnpm run test # all tests (server + client)
pnpm run test:server # server unit tests only
pnpm run test:client # client unit tests only
pnpm run test:e2e -- all # run all e2e suites sequentially
pnpm run test:e2e -- smoke # e2e smoke suite (default DB)
pnpm run test:e2e -- scanner # scanner e2e suite (dedicated projectx_e2e DB)
pnpm run test:e2e -- scanner-file-ops # scanner file operation e2e suite (dedicated projectx_e2e DB)
pnpm run test:e2e -- staging-ingest-finalize # staging ingest/finalize e2e suite (dedicated projectx_e2e DB)
pnpm run test:e2e -- opds-auth-catalog # OPDS auth/catalog e2e suite (dedicated projectx_e2e DB)
pnpm run test:e2e -- reader-state-isolation # reader state isolation e2e suite (dedicated projectx_e2e DB)
pnpm run test:e2e -- email-lifecycle # email lifecycle e2e suite (dedicated projectx_e2e DB)
pnpm run test:e2e -- users-admin-lifecycle # users admin lifecycle e2e suite (dedicated projectx_e2e DB)
pnpm run test:e2e:all # alias for "pnpm run test:e2e -- all"
pnpm run test:e2e:opds-auth-catalog # alias for "pnpm run test:e2e -- opds-auth-catalog"
pnpm run test:e2e:reader-state-isolation # alias for "pnpm run test:e2e -- reader-state-isolation"
pnpm run test:e2e:email-lifecycle # alias for "pnpm run test:e2e -- email-lifecycle"
pnpm run test:e2e:users-admin-lifecycle # alias for "pnpm run test:e2e -- users-admin-lifecycle"
pnpm run test:e2e:list # list all supported e2e suite ids
pnpm run e2e:run -- all # run all e2e suites sequentially
pnpm run e2e:run -- app-smoke # app smoke suite (default DB)
pnpm run e2e:run -- scanner-scenarios # scanner scenario suite (dedicated projectx_e2e DB)
pnpm run e2e:run -- scanner-file-operations # scanner file operation suite (dedicated projectx_e2e DB)
pnpm run e2e:run -- auth-session-security # auth session security suite (dedicated projectx_e2e DB)
pnpm run e2e:run -- auth-recovery-oidc-logout # auth recovery and OIDC logout suite (dedicated projectx_e2e DB)
pnpm run e2e:run -- staging-ingest-finalize # staging ingest/finalize suite (dedicated projectx_e2e DB)
pnpm run e2e:run -- metadata-write # metadata write suite (dedicated projectx_e2e DB)
pnpm run e2e:run -- authorization-matrix # authorization matrix suite (dedicated projectx_e2e DB)
pnpm run e2e:run -- opds-auth-catalog # OPDS auth/catalog suite (dedicated projectx_e2e DB)
pnpm run e2e:run -- reader-state-isolation # reader state isolation suite (dedicated projectx_e2e DB)
pnpm run e2e:run -- email-lifecycle # email lifecycle suite (dedicated projectx_e2e DB)
pnpm run e2e:run -- users-admin-lifecycle # users admin lifecycle suite (dedicated projectx_e2e DB)
pnpm run e2e:all # shortcut for running all suites
pnpm run e2e:list # list supported suite ids
```
Watch mode while working on a specific area:
@@ -140,95 +140,63 @@ cd server && pnpm test:watch
Run a focused scanner scenario:
```bash
pnpm run test:e2e -- scanner --testNamePattern=book-per-folder-disc-folder-flattening
pnpm run e2e:run -- scanner-scenarios --testNamePattern=book-per-folder-disc-folder-flattening
```
### E2E runner architecture
- All suite metadata lives in `scripts/test/e2e-suites.mjs` (suite id, spec file, JUnit output, DB prep mode).
- Local and CI use the same command: `pnpm run test:e2e -- <suite-id>`.
- Use `all` as a composite suite id to run every configured suite in order.
- Dedicated-db suites auto-start local PostgreSQL when `CI != true`, then reset and migrate the e2e database through `pnpm run db:prepare:e2e`.
- Local and CI use the same command: `pnpm run e2e:run -- <suite-id>`.
- Use `all` as a composite suite id to run every configured suite in order, or use `pnpm run e2e:all`.
- Dedicated-db suites auto-start local PostgreSQL when `CI != true`, then reset and migrate the e2e database through `pnpm run e2e:db:prepare`.
- JUnit outputs are written to `test-results/server/`.
### Scanner e2e details
### E2E suite details
- `pnpm run test:e2e -- scanner` prepares and migrates a dedicated e2e database (`projectx_e2e`) on each run.
- Local runs auto-start PostgreSQL with `docker-compose.dev.yml` if needed.
- Results are written to `test-results/server/`:
- `scanner-e2e-junit.xml`
- `scanner-e2e-scenarios.json`
All suites write JUnit XML to `test-results/server/`. Dedicated-db suites also auto-start PostgreSQL locally (when needed) and use the `projectx_e2e` database.
### Scanner file operation e2e details
- `pnpm run test:e2e -- scanner-file-ops` prepares and migrates the same dedicated e2e database (`projectx_e2e`) before running.
- Local runs auto-start PostgreSQL with `docker-compose.dev.yml` if needed.
- Results are written to `test-results/server/`:
- `scanner-file-ops-e2e-junit.xml`
- `scanner-file-ops-e2e-scenarios.json`
### Staging ingest/finalize e2e details
- `pnpm run test:e2e -- staging-ingest-finalize` prepares and migrates the dedicated e2e database (`projectx_e2e`) before running.
- Local runs auto-start PostgreSQL with `docker-compose.dev.yml` if needed.
- Results are written to `test-results/server/`:
- `staging-ingest-finalize-e2e-junit.xml`
- `staging-ingest-finalize-e2e-scenarios.json`
### OPDS auth/catalog e2e details
- `pnpm run test:e2e -- opds-auth-catalog` prepares and migrates the dedicated e2e database (`projectx_e2e`) before running.
- Local runs auto-start PostgreSQL with `docker-compose.dev.yml` if needed.
- Results are written to `test-results/server/`:
- `opds-auth-catalog-e2e-junit.xml`
- `opds-auth-catalog-e2e-scenarios.json`
### Reader state isolation e2e details
- `pnpm run test:e2e -- reader-state-isolation` prepares and migrates the dedicated e2e database (`projectx_e2e`) before running.
- Local runs auto-start PostgreSQL with `docker-compose.dev.yml` if needed.
- Results are written to `test-results/server/`:
- `reader-state-isolation-e2e-junit.xml`
- `reader-state-isolation-e2e-scenarios.json`
### Email lifecycle e2e details
- `pnpm run test:e2e -- email-lifecycle` prepares and migrates the dedicated e2e database (`projectx_e2e`) before running.
- Local runs auto-start PostgreSQL with `docker-compose.dev.yml` if needed.
- Results are written to `test-results/server/`:
- `email-lifecycle-e2e-junit.xml`
- `email-lifecycle-e2e-scenarios.json`
### Users admin lifecycle e2e details
- `pnpm run test:e2e -- users-admin-lifecycle` prepares and migrates the dedicated e2e database (`projectx_e2e`) before running.
- Local runs auto-start PostgreSQL with `docker-compose.dev.yml` if needed.
- Results are written to `test-results/server/`:
- `users-admin-lifecycle-e2e-junit.xml`
- `users-admin-lifecycle-e2e-scenarios.json`
| Suite id | DB mode | JUnit output | Extra artifacts |
| --------------------------- | ------------ | ----------------------------------------- | -------------------------------------------- |
| `app-smoke` | default-db | `app-smoke-e2e-junit.xml` | none |
| `scanner-scenarios` | dedicated-db | `scanner-scenarios-e2e-junit.xml` | `scanner-scenarios-e2e-scenarios.json` |
| `scanner-file-operations` | dedicated-db | `scanner-file-operations-e2e-junit.xml` | `scanner-file-operations-e2e-scenarios.json` |
| `auth-session-security` | dedicated-db | `auth-session-security-e2e-junit.xml` | none |
| `auth-recovery-oidc-logout` | dedicated-db | `auth-recovery-oidc-logout-e2e-junit.xml` | none |
| `staging-ingest-finalize` | dedicated-db | `staging-ingest-finalize-e2e-junit.xml` | `staging-ingest-finalize-e2e-scenarios.json` |
| `metadata-write` | dedicated-db | `metadata-write-e2e-junit.xml` | `metadata-write-e2e-scenarios.json` |
| `authorization-matrix` | dedicated-db | `authorization-matrix-e2e-junit.xml` | none |
| `opds-auth-catalog` | dedicated-db | `opds-auth-catalog-e2e-junit.xml` | `opds-auth-catalog-e2e-scenarios.json` |
| `reader-state-isolation` | dedicated-db | `reader-state-isolation-e2e-junit.xml` | `reader-state-isolation-e2e-scenarios.json` |
| `email-lifecycle` | dedicated-db | `email-lifecycle-e2e-junit.xml` | `email-lifecycle-e2e-scenarios.json` |
| `users-admin-lifecycle` | dedicated-db | `users-admin-lifecycle-e2e-junit.xml` | `users-admin-lifecycle-e2e-scenarios.json` |
### E2E in CI (how to trigger)
All suite workflows call the reusable `E2E Runner (reusable)` workflow, which runs:
```bash
pnpm run test:e2e -- <suite-id>
pnpm run e2e:run -- <suite-id>
```
| Workflow | Suite id | Triggered by |
| --------------------------- | ------------------------- | ------------------------------------------------------------------------------------- |
| Scanner E2E | `scanner` | `workflow_dispatch`, `push` (scanner-related paths), `pull_request` (same paths) |
| Scanner File Ops E2E | `scanner-file-ops` | `workflow_dispatch`, nightly schedule (`0 4 * * *`), `push`, `pull_request` |
| Staging Ingest Finalize E2E | `staging-ingest-finalize` | `workflow_dispatch`, `push` (staging-related paths), `pull_request` (same paths) |
| OPDS Auth Catalog E2E | `opds-auth-catalog` | `workflow_dispatch`, `push` (opds-related paths), `pull_request` (same paths) |
| Reader State Isolation E2E | `reader-state-isolation` | `workflow_dispatch`, `push` (reader-state-related paths), `pull_request` (same paths) |
| Email Lifecycle E2E | `email-lifecycle` | `workflow_dispatch`, `push` (email-related paths), `pull_request` (same paths) |
| Users Admin Lifecycle E2E | `users-admin-lifecycle` | `workflow_dispatch`, `push` (users/auth-related paths), `pull_request` (same paths) |
| Workflow name | Suite id | Triggered by |
| --------------------------------- | --------------------------- | --------------------------------------------------------------------------- |
| `E2E - App Smoke` | `app-smoke` | `workflow_dispatch`, `push`, `pull_request` |
| `E2E - Scanner Scenarios` | `scanner-scenarios` | `workflow_dispatch`, `push`, `pull_request` |
| `E2E - Scanner File Operations` | `scanner-file-operations` | `workflow_dispatch`, nightly schedule (`0 4 * * *`), `push`, `pull_request` |
| `E2E - Auth Session Security` | `auth-session-security` | `workflow_dispatch`, `push`, `pull_request` |
| `E2E - Auth Recovery OIDC Logout` | `auth-recovery-oidc-logout` | `workflow_dispatch`, `push`, `pull_request` |
| `E2E - Staging Ingest Finalize` | `staging-ingest-finalize` | `workflow_dispatch`, `push`, `pull_request` |
| `E2E - Metadata Write` | `metadata-write` | `workflow_dispatch`, `push`, `pull_request` |
| `E2E - Authorization Matrix` | `authorization-matrix` | `workflow_dispatch`, `push`, `pull_request` |
| `E2E - OPDS Auth Catalog` | `opds-auth-catalog` | `workflow_dispatch`, `push`, `pull_request` |
| `E2E - Reader State Isolation` | `reader-state-isolation` | `workflow_dispatch`, `push`, `pull_request` |
| `E2E - Email Lifecycle` | `email-lifecycle` | `workflow_dispatch`, `push`, `pull_request` |
| `E2E - Users Admin Lifecycle` | `users-admin-lifecycle` | `workflow_dispatch`, `push`, `pull_request` |
Manual trigger steps:
1. Open GitHub Actions.
2. Select **Scanner E2E**, **Scanner File Ops E2E**, **Staging Ingest Finalize E2E**, **OPDS Auth Catalog E2E**, **Reader State Isolation E2E**, **Email Lifecycle E2E**, or **Users Admin Lifecycle E2E**.
2. Select any `E2E - ...` workflow for the suite you want to run.
3. Click **Run workflow**.
Each run uploads `test-results/server/` as an artifact and publishes JUnit annotations from `test-results/server/*-e2e-junit.xml`.
@@ -241,7 +209,7 @@ Run before pushing:
```bash
pnpm run verify:fast # lint + typecheck + tests (same as pre-push hook)
pnpm run verify # above + e2e smoke (run before opening a PR)
pnpm run verify # above + e2e app-smoke suite (run before opening a PR)
```
Individual checks:
+7 -13
View File
@@ -7,8 +7,8 @@
"dev:client": "cd client && npm run dev -- --host",
"dev:server": "cd server && npm run start:dev",
"verify:fast": "pnpm run lint:check && pnpm run typecheck && pnpm run test",
"verify": "pnpm run lint:check && pnpm run typecheck && pnpm run test && pnpm run test:e2e -- smoke",
"verify:strict": "pnpm run format:check && pnpm run lint:check && pnpm run typecheck:full && pnpm run test && pnpm run test:e2e -- smoke",
"verify": "pnpm run lint:check && pnpm run typecheck && pnpm run test && pnpm run e2e:run -- app-smoke",
"verify:strict": "pnpm run format:check && pnpm run lint:check && pnpm run typecheck:full && pnpm run test && pnpm run e2e:run -- app-smoke",
"build:client": "pnpm --filter client build",
"build:server": "pnpm --filter server build",
"typecheck": "pnpm run typecheck:server && pnpm run typecheck:client",
@@ -22,16 +22,9 @@
"test": "pnpm run test:server && pnpm run test:client",
"test:server": "pnpm --filter server test",
"test:client": "pnpm --filter client test:unit --run",
"test:e2e": "node ./scripts/test/run-e2e-suite.mjs",
"test:e2e:all": "node ./scripts/test/run-e2e-suite.mjs all",
"test:e2e:metadata-write": "node ./scripts/test/run-e2e-suite.mjs metadata-write",
"test:e2e:opds-auth-catalog": "node ./scripts/test/run-e2e-suite.mjs opds-auth-catalog",
"test:e2e:metadata-write:atomicity": "node ./scripts/test/run-e2e-suite.mjs metadata-write --testNamePattern=\"atomicity failpoints\"",
"test:e2e:metadata-write:concurrency": "node ./scripts/test/run-e2e-suite.mjs metadata-write --testNamePattern=\"concurrency hardening\"",
"test:e2e:reader-state-isolation": "node ./scripts/test/run-e2e-suite.mjs reader-state-isolation",
"test:e2e:email-lifecycle": "node ./scripts/test/run-e2e-suite.mjs email-lifecycle",
"test:e2e:users-admin-lifecycle": "node ./scripts/test/run-e2e-suite.mjs users-admin-lifecycle",
"test:e2e:list": "node ./scripts/test/run-e2e-suite.mjs --list",
"e2e:run": "node ./scripts/test/run-e2e-suite.mjs",
"e2e:all": "node ./scripts/test/run-e2e-suite.mjs all",
"e2e:list": "node ./scripts/test/run-e2e-suite.mjs --list",
"coverage": "pnpm run coverage:server && pnpm run coverage:client",
"coverage:server": "pnpm --filter server test:cov",
"coverage:client": "pnpm --filter client test:cov --run",
@@ -41,7 +34,8 @@
"db:migrate": "pnpm --filter server db:migrate",
"db:reset": "bash ./scripts/db/reset.sh",
"db:seed": "bash ./scripts/db/seed.sh",
"db:prepare:e2e": "bash ./scripts/db/prepare-e2e.sh",
"e2e:db:prepare": "bash ./scripts/db/prepare-e2e.sh",
"e2e:db:migrate": "pnpm --filter server e2e:db:migrate",
"prepare": "husky"
},
"engines": {
+2 -2
View File
@@ -7,7 +7,7 @@ cd "$ROOT_DIR"
E2E_DATABASE_URL="${E2E_DATABASE_URL:-postgres://projectx:projectx@localhost:5432/projectx_e2e}"
echo "Preparing dedicated e2e database..."
DATABASE_URL="$E2E_DATABASE_URL" pnpm --filter server db:prepare:e2e
DATABASE_URL="$E2E_DATABASE_URL" pnpm --filter server e2e:db:prepare
echo "Applying migrations to e2e database..."
DATABASE_URL="$E2E_DATABASE_URL" pnpm --filter server db:migrate
DATABASE_URL="$E2E_DATABASE_URL" pnpm --filter server e2e:db:migrate
+13 -13
View File
@@ -1,27 +1,27 @@
const TEST_RESULTS_DIR = "../test-results/server";
export const E2E_SUITES = Object.freeze({
smoke: {
id: "smoke",
description: "Server smoke suite",
vitestTarget: "test/app.e2e-spec.ts",
junitOutput: `${TEST_RESULTS_DIR}/smoke-e2e-junit.xml`,
"app-smoke": {
id: "app-smoke",
description: "App smoke suite",
vitestTarget: "test/app-smoke.e2e-spec.ts",
junitOutput: `${TEST_RESULTS_DIR}/app-smoke-e2e-junit.xml`,
prepareDedicatedDatabase: false,
useDedicatedDatabase: false,
},
scanner: {
id: "scanner",
"scanner-scenarios": {
id: "scanner-scenarios",
description: "Scanner scenario matrix",
vitestTarget: "test/scanner.e2e-spec.ts",
junitOutput: `${TEST_RESULTS_DIR}/scanner-e2e-junit.xml`,
vitestTarget: "test/scanner-scenarios.e2e-spec.ts",
junitOutput: `${TEST_RESULTS_DIR}/scanner-scenarios-e2e-junit.xml`,
prepareDedicatedDatabase: true,
useDedicatedDatabase: true,
},
"scanner-file-ops": {
id: "scanner-file-ops",
"scanner-file-operations": {
id: "scanner-file-operations",
description: "Scanner file operation matrix",
vitestTarget: "test/scanner-file-ops.e2e-spec.ts",
junitOutput: `${TEST_RESULTS_DIR}/scanner-file-ops-e2e-junit.xml`,
vitestTarget: "test/scanner-file-operations.e2e-spec.ts",
junitOutput: `${TEST_RESULTS_DIR}/scanner-file-operations-e2e-junit.xml`,
prepareDedicatedDatabase: true,
useDedicatedDatabase: true,
},
+10 -10
View File
@@ -28,17 +28,17 @@ function trimLeadingSeparator(args) {
function printUsage() {
const usage = [
"Usage:",
" pnpm run test:e2e -- <suite-id> [vitest args...]",
" pnpm run e2e:run -- <suite-id> [vitest args...]",
"",
"Examples:",
" pnpm run test:e2e -- smoke",
" pnpm run test:e2e -- all",
" pnpm run test:e2e -- metadata-write",
" pnpm run test:e2e -- metadata-write --testNamePattern=\"concurrency hardening\"",
" pnpm run test:e2e -- metadata-write --shard=1/2",
" pnpm run test:e2e -- scanner --testNamePattern=book-per-folder-disc-folder-flattening",
" pnpm run test:e2e -- --list",
" pnpm run test:e2e:list",
" pnpm run e2e:run -- app-smoke",
" pnpm run e2e:run -- all",
" pnpm run e2e:run -- metadata-write",
' pnpm run e2e:run -- metadata-write --testNamePattern="concurrency hardening"',
" pnpm run e2e:run -- metadata-write --shard=1/2",
" pnpm run e2e:run -- scanner-scenarios --testNamePattern=book-per-folder-disc-folder-flattening",
" pnpm run e2e:run -- --list",
" pnpm run e2e:list",
];
console.log(usage.join("\n"));
}
@@ -122,7 +122,7 @@ async function main() {
if (suite.prepareDedicatedDatabase) {
console.log(`Resetting and migrating dedicated e2e database for suite ${suite.id}...`);
await runCommand("pnpm", ["run", "db:prepare:e2e"], { E2E_DATABASE_URL: e2eDatabaseUrl });
await runCommand("pnpm", ["run", "e2e:db:prepare"], { E2E_DATABASE_URL: e2eDatabaseUrl });
}
const junitOutput = process.env.JUNIT_OUTPUT ?? suite.junitOutput;
+5 -5
View File
@@ -50,16 +50,16 @@ pnpm db:studio # open Drizzle Studio (DB browser)
```bash
pnpm test # unit tests
pnpm test:watch # unit tests in watch mode
pnpm test:e2e # smoke end-to-end test from server package
pnpm e2e:run # app-smoke end-to-end test from server package
pnpm test:cov # unit tests with coverage
```
For suite-based e2e runs (scanner, scanner-file-ops, auth suites), run from the repository root:
For suite-based e2e runs, run from the repository root:
```bash
pnpm run test:e2e:list
pnpm run test:e2e -- all
pnpm run test:e2e -- scanner
pnpm run e2e:list
pnpm run e2e:all
pnpm run e2e:run -- scanner-scenarios
```
## Quality
+8 -8
View File
@@ -16,9 +16,9 @@
"start:prod": "node dist/main",
"db:generate": "drizzle-kit generate --name",
"db:migrate": "drizzle-kit migrate",
"db:migrate:e2e": "DATABASE_URL=postgres://projectx:projectx@localhost:5432/projectx_e2e pnpm db:migrate",
"e2e:db:migrate": "DATABASE_URL=postgres://projectx:projectx@localhost:5432/projectx_e2e pnpm db:migrate",
"db:migrate:runtime": "node dist/scripts/migrate.js",
"db:prepare:e2e": "tsx src/scripts/db-prepare-e2e.ts",
"e2e:db:prepare": "tsx src/scripts/db-prepare-e2e.ts",
"db:studio": "drizzle-kit studio",
"db:seed": "tsx src/scripts/db-seed.ts",
"pretype-check": "pnpm --filter @projectx/types build",
@@ -26,10 +26,10 @@
"lint": "pnpm run lint:check",
"lint:check": "eslint \"{src,apps,libs,test}/**/*.ts\"",
"lint:fix": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "vitest run --exclude test/scanner.e2e-spec.ts --exclude test/scanner-file-ops.e2e-spec.ts",
"test": "vitest run --exclude test/scanner-scenarios.e2e-spec.ts --exclude test/scanner-file-operations.e2e-spec.ts",
"test:watch": "vitest",
"test:cov": "vitest run --coverage",
"test:e2e": "vitest run test/app.e2e-spec.ts"
"e2e:run": "vitest run test/app-smoke.e2e-spec.ts"
},
"dependencies": {
"7z-wasm": "^1.2.0",
@@ -84,10 +84,10 @@
"devDependencies": {
"@eslint/eslintrc": "^3.3.5",
"@eslint/js": "^10.0.1",
"@nestjs/cli": "^11.0.16",
"@nestjs/schematics": "^11.0.9",
"@nestjs/cli": "^11.0.17",
"@nestjs/schematics": "^11.0.10",
"@nestjs/testing": "^11.1.17",
"@swc/cli": "^0.8.0",
"@swc/cli": "^0.8.1",
"@swc/core": "^1.15.21",
"@types/bcrypt": "^6.0.0",
"@types/express": "^5.0.6",
@@ -112,7 +112,7 @@
"tsconfig-paths": "^4.2.0",
"tsx": "^4.21.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.57.2",
"typescript-eslint": "^8.58.0",
"vitest": "^4.1.2"
}
}