Files
Daniel Sutton 4c2c25511b test(webapp): split triggerTask engine test into per-concern files (#4167)
The engine `triggerTask` suite was a single 2447-line file with 23
`containerTest` cases, each spinning its own Postgres + Redis. vitest
shards by whole file, so all 23 container setups landed on one shard and
dominated its wall-clock. The recorded entry in `test-timings.json`
badly under-counts the real cost (it does not capture the
per-`containerTest` container startup that dominates on CI), so the
duration-sharding sequencer treated the file as light and stacked it,
producing one ~21 minute shard.

Splitting does not reduce the number of container setups; it lets those
23 cases distribute across shards instead of stacking on one. The webapp
unit-test stage is gated by its slowest shard, so this cuts the stage's
wall-clock roughly in half.

## CI timing (before vs after)

Real CI wall-clock of the `Unit Tests: Webapp` shards (`--shard=i/10`).
"Before" is sampled from recent runs on other branches (unsplit file,
from `main`); "after" is this PR.

| Shard | Before (s) | After (s) |
|------:|-----------:|----------:|
| 1  | 250 | 359 |
| 2  | 444 | 411 |
| 3  | 497 | 659 |
| 4  | **1257** | 284 |
| 5  | 545 | 641 |
| 6  | 284 | 644 |
| 7  | 244 | 214 |
| 8  | 340 | 445 |
| 9  | 188 | 395 |
| 10 | 234 | 567 |
| **Slowest shard (gates the stage)** | **~1247s (≈21m)** | **659s
(≈11m)** |
| Sum of all shards | 4283 | 4619 |

Before: shard 4 is the long pole at 1237s / 1247s / 1257s across three
sampled runs (the `triggerTask` file plus whatever else the packer put
with it). After: the six pieces spread across shards, the slowest drops
to 659s. The small rise in summed time is the extra per-file container
startup, paid in parallel across shards, so the gating number still
falls by about 10 minutes.

## Change

Split into six per-concern files that share a `triggerTaskTestHelpers`
module (the `vi.mock` calls stay per-file, since vitest hoists them):

- `triggerTask.test.ts` (3): trigger + concurrencyKey coercion
- `triggerTask.idempotency.test.ts` (4): idempotency + queue resolution
- `triggerTask.debounce.test.ts` (4): retries + debounce validation
- `triggerTask.mollifier.test.ts` (4): mollifier call-site behaviour
- `triggerTask.metadataCache.test.ts` (4): DefaultQueueManager task
metadata cache
- `triggerTask.residency.test.ts` (4): child run residency inheritance

All 23 cases are preserved. The file's `test-timings.json` entry is
split across the new files so bin-packing stays balanced.

While rewriting these files, cleanup was moved to `onTestFinished(() =>
engine.quit())` so an `engine`/`Redis` leaked on a failing assertion no
longer persists on the worker-scoped Redis and cascades into later cases
(`hookTimeout` raised to 60s so the after-cleanup gets the full budget).
Prisma lookups switched from `findUnique` to `findFirst` to match the
repo convention.

Verified: all six files run green locally (23/23), oxlint and oxfmt
clean.
2026-07-06 13:03:27 +01:00
..

Webapp tests

Three suites live in this directory.

Unit tests — *.test.ts

Run with pnpm test from apps/webapp. Default vitest pickup. No container setup. Run on every PR via unit-tests-webapp.yml.

Smoke e2e — *.e2e.test.ts

End-to-end auth baseline that proves the route auth plumbing is wired up. Each file spins up its own webapp + Postgres + Redis container in beforeAll (~30s startup). Vitest config: vitest.e2e.config.ts. Run on every PR via e2e-webapp.yml.

cd apps/webapp
pnpm exec vitest --config vitest.e2e.config.ts

Comprehensive auth e2e — *.e2e.full.test.ts

The full RBAC auth matrix — every route family with explicit pass/fail scenarios. See TRI-8731 for the parent ticket and TRI-8732 onwards for each family's coverage spec.

Architecture: one container reused across the whole suite via vitest.e2e.full.config.ts's globalSetup. Test files share the server through getTestServer() from helpers/sharedTestServer.ts. Each test seeds its own resources so order doesn't matter.

Layout:

File Top-level describe Family subtasks
auth-api.e2e.full.test.ts API TRI-8733 trigger, TRI-8734 run resource, TRI-8735 run mutations, TRI-8736 run lists, TRI-8737 batches, TRI-8738 prompts, TRI-8739 deployments + query, TRI-8740 waitpoints + input streams, TRI-8741 PAT
auth-dashboard.e2e.full.test.ts Dashboard TRI-8742 admin pages
auth-cross-cutting.e2e.full.test.ts Cross-cutting TRI-8743 deleted projects / revoked keys / expired JWTs / env mismatch / force-fallback toggle

Adding a new family: pick the relevant file, add a nested describe block. Inside, seed your own fixtures via the helpers and hit the shared server.

describe("Trigger task", () => {
  const server = getTestServer();

  it("missing Authorization → 401", async () => {
    const res = await server.webapp.fetch("/api/v1/tasks/x/trigger", { method: "POST", body: "{}" });
    expect(res.status).toBe(401);
  });
});

CI: e2e-webapp-auth-full.yml. Triggers on workflow_dispatch, nightly schedule, and PRs touching auth-relevant paths (route builders, rbac.server.ts, apiAuth.server.ts, apiroutes, the suite itself).

Run locally:

cd apps/webapp
pnpm exec vitest --config vitest.e2e.full.config.ts