fix(labd): wire v2.0 Phase 1 routes + smoke tests #15

Merged
michal merged 1 commits from fix/v2-wire-and-smoke-test into main 2026-05-05 21:18:44 +00:00
Owner

Summary

Completes the v2.0 Phase 1 PR test plan items 3-5 by:

  1. Discovering that the v2 routes/services were never wired into createApp (they were dead code)
  2. Wiring them in
  3. Adding 11 smoke tests that exercise the wiring end-to-end

What was wrong

The v2.0 Phase 1 commit (04faa07) added AuthService, RbacService, ResourceStore, AuditService, the bearer-auth middleware, and the v2-auth/environments/resources route files — but server.ts:createApp() never registered any of them. POST /api/auth/login, GET /api/resources, GET /api/events would all 404 on a running labd.

What this changes

server.ts — instantiates the four services at app creation, starts the AuditService timer, registers the v2 routes inside a Fastify scope with bearer-auth as preHandler. v1 routes are on the root scope so they're unaffected. Adds an onClose hook to flush pending audit events on shutdown.

audit.ts — exposes flushPending() so tests don't have to wait the 5s flush interval. One-line wrapper around the existing private flush().

v2-smoke.test.ts — 11 cases organized into three describe blocks:

  • Bootstrap flow (4): first login seeds admin + emits auth_bootstrap; second login uses normal flow; wrong password 401 + audit failure; missing credentials 400.
  • RBAC denial (5): no token 401; empty token 401; bad token 401; ADMIN bypasses; env-scoped binding denies non-matching env.
  • Audit correlation (2): bootstrap event queryable via /api/events?correlation=...; explicit parent/child chain preserved.

Tests use a mock PrismaClient (in-memory Maps) following the project's existing test pattern. No CockroachDB needed.

Test plan

  • vitest --run src/labd — 63 pass (52 prior + 11 new)
  • vitest --run (whole workspace) — 246/246
  • pnpm --filter {@lab/shared,@lab/core,@lab/labd} build — clean

Now ticks the previously-unchecked items from PR #14:

  • First bootstrap: POST /api/auth/login with no users seeds the admin
  • RBAC: env-scoped role binding actually denies
  • AuditService correlation-id chains visible in /api/events
## Summary Completes the v2.0 Phase 1 PR test plan items 3-5 by: 1. Discovering that the v2 routes/services were never wired into `createApp` (they were dead code) 2. Wiring them in 3. Adding 11 smoke tests that exercise the wiring end-to-end ### What was wrong The v2.0 Phase 1 commit (04faa07) added `AuthService`, `RbacService`, `ResourceStore`, `AuditService`, the `bearer-auth` middleware, and the `v2-auth`/`environments`/`resources` route files — but `server.ts:createApp()` never registered any of them. `POST /api/auth/login`, `GET /api/resources`, `GET /api/events` would all 404 on a running labd. ### What this changes **`server.ts`** — instantiates the four services at app creation, starts the `AuditService` timer, registers the v2 routes inside a Fastify scope with `bearer-auth` as `preHandler`. v1 routes are on the root scope so they're unaffected. Adds an `onClose` hook to flush pending audit events on shutdown. **`audit.ts`** — exposes `flushPending()` so tests don't have to wait the 5s flush interval. One-line wrapper around the existing private `flush()`. **`v2-smoke.test.ts`** — 11 cases organized into three describe blocks: - **Bootstrap flow** (4): first login seeds admin + emits `auth_bootstrap`; second login uses normal flow; wrong password 401 + audit failure; missing credentials 400. - **RBAC denial** (5): no token 401; empty token 401; bad token 401; ADMIN bypasses; env-scoped binding denies non-matching env. - **Audit correlation** (2): bootstrap event queryable via `/api/events?correlation=...`; explicit parent/child chain preserved. Tests use a mock PrismaClient (in-memory Maps) following the project's existing test pattern. No CockroachDB needed. ## Test plan - [x] `vitest --run src/labd` — 63 pass (52 prior + 11 new) - [x] `vitest --run` (whole workspace) — 246/246 - [x] `pnpm --filter {@lab/shared,@lab/core,@lab/labd} build` — clean Now ticks the previously-unchecked items from PR #14: - [x] First bootstrap: `POST /api/auth/login` with no users seeds the admin - [x] RBAC: env-scoped role binding actually denies - [x] AuditService correlation-id chains visible in `/api/events`
michal added 1 commit 2026-05-05 21:18:40 +00:00
fix(labd): wire v2.0 Phase 1 routes into createApp + smoke tests
Some checks failed
CI/CD / typecheck (pull_request) Failing after 11s
CI/CD / test (pull_request) Failing after 9s
CI/CD / lint (pull_request) Failing after 22s
CI/CD / build (pull_request) Has been skipped
CI/CD / publish-rpm (pull_request) Has been skipped
CI/CD / publish-deb (pull_request) Has been skipped
cdf3b5c045
The v2.0 Phase 1 commit (04faa07) added AuthService, RbacService,
ResourceStore, AuditService, the bearer auth middleware, and the
v2-auth/environments/resources route files, but createApp() never
registered any of them. They sat in the codebase as dead code: a
running labd would 404 on /api/auth/login, /api/resources, /api/events,
etc.

Wiring (server.ts)
- Instantiate AuthService, RbacService, ResourceStore, AuditService at
  app creation. Cast DbClient to PrismaClient (the runtime db is a real
  PrismaClient; DbClient is a structural shim).
- Start AuditService timer, register an onClose hook to stop it on
  shutdown so we never lose the last batch.
- Register v2 routes inside a Fastify scope with the bearer-auth
  middleware as preHandler. v1 routes (registered on the root scope)
  are unaffected so existing labd clients keep working.

AuditService (audit.ts)
- Expose flushPending() so tests can deterministically observe events
  without leaning on the 5-second flush interval. Implementation
  delegates to the existing private flush().

Smoke tests (v2-smoke.test.ts, 11 cases)
- Bootstrap: first POST /api/auth/login with empty users creates the
  admin (role=ADMIN, hashed password), returns a 64-hex token, marks
  isBootstrap=true, emits an auth_bootstrap audit event. Second login
  uses the normal flow. Wrong password returns 401 and audits failure.
  Missing credentials returns 400.
- RBAC: missing/empty/invalid bearer tokens return 401. ADMIN role
  bypasses RBAC. A non-admin with no role bindings gets 403 with
  "no matching role binding". A user with an env-A binding is denied
  for env-B resources.
- Audit: bootstrap event is queryable via /api/events?correlation=...
  Explicit parent/child chain (shared correlationId, parentEventId)
  is preserved across emits.

All 246 workspace tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
michal merged commit 7181a61cec into main 2026-05-05 21:18:44 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: michal/lab#15