diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 0000000..83e0d7c --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,2 @@ +#!/usr/bin/env sh +# Global hooks run via ~/.config/husky/init.sh diff --git a/apps/api/src/account/account.module.ts b/apps/api/src/account/account.module.ts index 5d283d0..a03d551 100644 --- a/apps/api/src/account/account.module.ts +++ b/apps/api/src/account/account.module.ts @@ -8,7 +8,6 @@ * guard configuration is required. * * @layer account - * @see docs/DEVELOPMENT_PLAN.md §Phase 14 P14-2 */ import { Module } from '@nestjs/common'; diff --git a/apps/api/src/app.module.ts b/apps/api/src/app.module.ts index af759b6..b5bfdf2 100644 --- a/apps/api/src/app.module.ts +++ b/apps/api/src/app.module.ts @@ -2,30 +2,16 @@ * @file app.module.ts * @description Root NestJS module for `@nest-auth-example/api`. * - * Phase 7 adds: - * - `AuthModule` — wires `BymaxAuthModule.registerAsync` with all four - * implementation bindings and mounts `/api/auth/*` controllers. - * - `ThrottlerModule.forRoot(AUTH_THROTTLE_CONFIGS)` — rate-limiting applied - * globally (auth routes use the library's throttle configs). - * - `TenantsModule` and `ProjectsModule` — example domain modules that - * demonstrate RBAC, multi-tenant scoping, and library decorators. - * - `UsersModule` — exposes `PATCH /api/users/:id/status` for the admin - * suspension demo (FCM row #23). - * - `PlatformModule` — exposes `/api/platform/*` endpoints protected by - * `JwtPlatformGuard` + `PlatformRolesGuard` (FCM row #22). - * - `DebugModule` (non-production only) — dev helper for brute-force lockout - * demo (FCM row #16). - * - `NotificationsModule` — WebSocket gateway at `/ws/notifications` protected by - * `WsJwtGuard`; includes the dev-only `POST /api/debug/notify/:userId` trigger - * (FCM row #24). - * - Five global `APP_GUARD` providers registered in the exact order mandated by - * `docs/guidelines/nest-auth-guidelines.md`: JwtAuthGuard → UserStatusGuard → - * MfaRequiredGuard → TenantMfaPolicyGuard → RolesGuard. The new - * `TenantMfaPolicyGuard` is app-owned (see `auth/tenant-mfa-policy.guard.ts`) - * and forces every user in the tenants listed in `MFA_REQUIRED_TENANT_SLUGS` - * to enrol in MFA before they can touch protected endpoints; it composes - * with the lib's `MfaRequiredGuard` rather than replacing it. Order must - * not be changed without an ADR. + * Wires infrastructure, auth, and all feature modules. Registers five global + * `APP_GUARD` providers in the exact order mandated by + * `docs/guidelines/nest-auth-guidelines.md`: JwtAuthGuard → UserStatusGuard → + * MfaRequiredGuard → TenantMfaPolicyGuard → RolesGuard. Order must not be + * changed without an ADR. + * + * `TenantMfaPolicyGuard` is app-owned and forces every user in the tenants + * listed in `MFA_REQUIRED_TENANT_SLUGS` to enrol in MFA before reaching + * protected endpoints; it composes with the library's `MfaRequiredGuard` + * rather than replacing it. * * Import order is intentional: * 1. `AppConfigModule` must be first — registers `ConfigService` globally. @@ -37,7 +23,6 @@ * * @layer root * @see docs/guidelines/nest-auth-guidelines.md §Decorators & guards - * @see docs/DEVELOPMENT_PLAN.md §Phase 7 P7-2 */ import { Module } from '@nestjs/common'; @@ -101,13 +86,12 @@ import { DebugModule } from './debug/debug.module.js'; TenantsModule, ProjectsModule, UsersModule, - // Phase 9 — Platform admin context (FCM #22). Mounts /api/platform/* routes - // that are protected by JwtPlatformGuard + PlatformRolesGuard. + // Platform admin endpoints under /api/platform/*, protected by + // JwtPlatformGuard + PlatformRolesGuard. PlatformModule, - // Phase 10 — WebSocket notifications gateway (FCM #24). Mounts the - // /ws/notifications WebSocket endpoint guarded by WsJwtGuard. The dev-only - // POST /api/debug/notify/:userId controller is included by NotificationsModule - // itself when NODE_ENV !== 'production'. + // WebSocket notifications gateway at /ws/notifications, guarded by WsJwtGuard. + // The dev-only POST /api/debug/notify/:userId trigger is included by + // NotificationsModule itself when NODE_ENV !== 'production'. NotificationsModule, // DebugModule is conditionally included only outside of production to // keep brute-force demo helpers out of production deployments. diff --git a/apps/api/src/auth/app-auth.hooks.ts b/apps/api/src/auth/app-auth.hooks.ts index 6b256df..e04868a 100644 --- a/apps/api/src/auth/app-auth.hooks.ts +++ b/apps/api/src/auth/app-auth.hooks.ts @@ -24,8 +24,6 @@ * oauth.login | * invitation.accepted * - * Covers FCM row #30 (audit / lifecycle hooks). - * * @layer auth * @see docs/guidelines/observability-guidelines.md * @see docs/guidelines/nest-auth-guidelines.md @@ -51,7 +49,7 @@ import { isBlockedStatus } from './auth.constants.js'; /** * Auth lifecycle hooks that write immutable `AuditLog` rows for every event. * - * Injected via `BYMAX_AUTH_HOOKS` token in Phase 7's `AuthModule`. The hooks + * Injected via `BYMAX_AUTH_HOOKS` token in `AuthModule`. The hooks * class also dispatches the new-session security email — the library does not * call `IEmailProvider.sendNewSessionAlert` automatically; consumers wire it * inside their own `onNewSession` hook. @@ -232,7 +230,7 @@ export class AppAuthHooks implements IAuthHooks { device: sessionInfo.device, }); - // Dispatch the new-session security email (FCM #15). The library never + // Dispatch the new-session security email. The library never // calls `sendNewSessionAlert` itself — consumers are responsible for the // dispatch, typically from this hook. Wrap in try/catch so an email // failure never blocks the login response. @@ -283,7 +281,7 @@ export class AppAuthHooks implements IAuthHooks { * to `PrismaUserRepository.createWithOAuth`, which performs an upsert on * `(tenantId, email)`. If a user registered via email/password with the same * address, their OAuth fields are updated in-place rather than creating a - * duplicate row — implementing the account-linking guarantee (FCM #12). + * duplicate row — implementing the account-linking guarantee. * * @param profile - Normalised OAuth profile from the provider. * @param existingUser - Existing user found by OAuth provider ID, or null. diff --git a/apps/api/src/auth/auth-exception.filter.ts b/apps/api/src/auth/auth-exception.filter.ts index 4551b55..eb00a38 100644 --- a/apps/api/src/auth/auth-exception.filter.ts +++ b/apps/api/src/auth/auth-exception.filter.ts @@ -13,11 +13,8 @@ * NestJS's built-in exception handler. Stack traces and internal Prisma/Redis * diagnostics are never included in the response body. * - * Covers FCM row #29 (shared error codes, anti-enumeration). - * * @layer auth * @see docs/guidelines/security-privacy-guidelines.md - * @see docs/DEVELOPMENT_PLAN.md §Phase 7 P7-7 */ import type { ArgumentsHost, ExceptionFilter } from '@nestjs/common'; diff --git a/apps/api/src/auth/auth.config.ts b/apps/api/src/auth/auth.config.ts index 2e58138..6b1c48e 100644 --- a/apps/api/src/auth/auth.config.ts +++ b/apps/api/src/auth/auth.config.ts @@ -1,7 +1,7 @@ /** * @file auth.config.ts * @description Factory that builds `BymaxAuthModuleOptions` from the Zod-validated - * environment, consumed by `BymaxAuthModule.registerAsync` in Phase 7. + * environment, consumed by `BymaxAuthModule.registerAsync`. * * Keeps configuration concerns separate from module wiring: this file answers * "what are the options?" while `auth.module.ts` answers "how is the module wired?". @@ -14,7 +14,6 @@ * * @layer auth * @see docs/guidelines/nest-auth-guidelines.md - * @see docs/DEVELOPMENT_PLAN.md §Phase 6.1 */ import type { Request } from 'express'; @@ -27,12 +26,7 @@ import { BLOCKED_USER_STATUSES } from './auth.constants.js'; /** * Builds the `BymaxAuthModuleOptions` object for `BymaxAuthModule.registerAsync`. * - * Every option group directly maps to the development plan §6.1 spec. The function - * is pure — no side effects, no logging, no secret values emitted anywhere. - * - * FCM rows covered: #3 (refresh grace), #5 (email verification), #13 (sessions), - * #14 (FIFO eviction), #16 (brute-force), #18/#19 (RBAC), #20 (tenant resolver), - * #23 (blocked statuses). + * The function is pure — no side effects, no logging, no secret values emitted anywhere. * * @param config - Zod-validated `ConfigService`. Every required variable * is guaranteed present because the app refuses to start on an invalid config. diff --git a/apps/api/src/auth/auth.module.ts b/apps/api/src/auth/auth.module.ts index 6012175..076596a 100644 --- a/apps/api/src/auth/auth.module.ts +++ b/apps/api/src/auth/auth.module.ts @@ -1,6 +1,6 @@ /** * @file auth.module.ts - * @description Phase 7 module that wires `BymaxAuthModule.registerAsync` with all + * @description NestJS module that wires `BymaxAuthModule.registerAsync` with all * five required implementation bindings: user repository, platform user repository, * Redis client, email provider, and auth hooks. * @@ -15,11 +15,8 @@ * validates this eagerly and does not check `imports`. The global `RedisModule` provides * a separate client instance for other feature modules (e.g. NotificationsModule). * - * Covers FCM rows #1–#5, #13–#20, #23, #29–#32 (module-level wiring layer). - * * @layer auth * @see docs/guidelines/nest-auth-guidelines.md - * @see docs/DEVELOPMENT_PLAN.md §Phase 7 P7-1 */ import { Module } from '@nestjs/common'; diff --git a/apps/api/src/auth/mailpit-email.provider.ts b/apps/api/src/auth/mailpit-email.provider.ts index db68e3d..206e35d 100644 --- a/apps/api/src/auth/mailpit-email.provider.ts +++ b/apps/api/src/auth/mailpit-email.provider.ts @@ -13,9 +13,6 @@ * - Template rendering uses simple `{{var}}` string replacement; no eval or * dynamic code execution. * - * Covers FCM rows #5 (email verification), #6/#7 (password reset), #15 (new-session - * alert), #21 (invitations), #31 (custom email provider). - * * @layer auth * @see docs/guidelines/email-guidelines.md * @see docs/guidelines/logging-guidelines.md diff --git a/apps/api/src/auth/noop-fallbacks.spec.ts b/apps/api/src/auth/noop-fallbacks.spec.ts index 61c7220..0f2a409 100644 --- a/apps/api/src/auth/noop-fallbacks.spec.ts +++ b/apps/api/src/auth/noop-fallbacks.spec.ts @@ -11,7 +11,6 @@ * linking to an existing user (if found) or creating a new account. * `NoOpEmailProvider` — resolves all delivery methods without doing anything. * - * @see docs/DEVELOPMENT_PLAN.md §Appendix B — Library Export → Example File Map * @layer test */ diff --git a/apps/api/src/auth/prisma-platform-user.repository.spec.ts b/apps/api/src/auth/prisma-platform-user.repository.spec.ts index 2845c2c..75efa22 100644 --- a/apps/api/src/auth/prisma-platform-user.repository.spec.ts +++ b/apps/api/src/auth/prisma-platform-user.repository.spec.ts @@ -7,7 +7,7 @@ * - `findByEmail`: found/not-found, lower-case normalisation. * - `updateLastLogin`, `updateMfa`, `updatePassword`, `updateStatus`. * - * Security-critical invariants validated here (FCM row #22): + * Security-critical invariants validated here: * - `mfaSecret` is absent (not undefined) when null in the DB. * - `mfaRecoveryCodes` is absent when `mfaEnabled=false`. * - `platformId` is absent when null in the DB. @@ -77,7 +77,7 @@ describe('PrismaPlatformUserRepository.findById', () => { it('returns a mapped AuthPlatformUser when the row exists', async () => { // Happy path — the row must be mapped to AuthPlatformUser with all required - // fields populated correctly (FCM #22). + // fields populated correctly. platformUserFindUnique.mockResolvedValue(makePlatformUserRow()); const result = await repo.findById('platform-user-1'); diff --git a/apps/api/src/auth/prisma-platform-user.repository.ts b/apps/api/src/auth/prisma-platform-user.repository.ts index 13bbd0b..f4c5c63 100644 --- a/apps/api/src/auth/prisma-platform-user.repository.ts +++ b/apps/api/src/auth/prisma-platform-user.repository.ts @@ -11,8 +11,6 @@ * - Platform users are never mixed with tenant users — different Prisma models, * different JWT payloads, different guards. * - * Covers FCM row #22 (platform admin backing repository). - * * @layer auth * @see docs/guidelines/prisma-guidelines.md * @see docs/guidelines/nest-auth-guidelines.md @@ -31,7 +29,7 @@ import { PrismaService } from '../prisma/prisma.service.js'; /** * Prisma-backed repository for the platform admin auth context. * - * Injected via `BYMAX_AUTH_PLATFORM_USER_REPOSITORY` token in Phase 7's `AuthModule`. + * Injected via `BYMAX_AUTH_PLATFORM_USER_REPOSITORY` token in `AuthModule`. * No `tenantId` filtering — platform users are not tenant-scoped. * * @public diff --git a/apps/api/src/auth/prisma-user.repository.spec.ts b/apps/api/src/auth/prisma-user.repository.spec.ts index 4cea78f..e000d26 100644 --- a/apps/api/src/auth/prisma-user.repository.spec.ts +++ b/apps/api/src/auth/prisma-user.repository.spec.ts @@ -12,7 +12,7 @@ * - `findByOAuthId` — provider + providerId scoped to tenant. * - `linkOAuth` — OAuth fields update call. * - * These paths are security-critical (FCM #12, #23, #32): regressions here would + * These paths are security-critical: regressions here would * break tenant isolation, allow blocked accounts to receive tokens, or corrupt * user credentials. * @@ -350,7 +350,7 @@ describe('PrismaUserRepository.findById', () => { it('returns AuthUser scoped to tenantId when tenantId is provided', async () => { // findById must pass both `id` and `tenantId` in the WHERE clause when - // tenantId is supplied — this is the tenant-isolation requirement (FCM #32). + // tenantId is supplied — this is the tenant-isolation requirement. userFindFirst.mockResolvedValue(makeUserRow()); const result = await repo.findById('user-1', 'acme'); diff --git a/apps/api/src/auth/prisma-user.repository.ts b/apps/api/src/auth/prisma-user.repository.ts index 20ed25f..9dfe3c1 100644 --- a/apps/api/src/auth/prisma-user.repository.ts +++ b/apps/api/src/auth/prisma-user.repository.ts @@ -12,8 +12,6 @@ * - Every query that returns a user is scoped by `tenantId` to prevent cross-tenant leaks. * - Email is stored lower-case on write and returned as-is from the DB. * - * Covers FCM row #32 (custom user repository). - * * @layer auth * @see docs/guidelines/prisma-guidelines.md * @see docs/guidelines/nest-auth-guidelines.md @@ -36,7 +34,7 @@ import { BLOCKED_USER_STATUSES } from './auth.constants.js'; /** * Prisma-backed user repository for the tenant (dashboard) auth context. * - * Injected via `BYMAX_AUTH_USER_REPOSITORY` token in Phase 7's `AuthModule`. + * Injected via `BYMAX_AUTH_USER_REPOSITORY` token in `AuthModule`. * Repositories are the only layer that imports `PrismaService` directly. * * @public diff --git a/apps/api/src/auth/resend-email.provider.ts b/apps/api/src/auth/resend-email.provider.ts index 6654f71..4bb9e71 100644 --- a/apps/api/src/auth/resend-email.provider.ts +++ b/apps/api/src/auth/resend-email.provider.ts @@ -14,8 +14,6 @@ * - Template rendering uses simple `{{var}}` string replacement; no eval or * dynamic code execution. * - * Covers FCM row #31 (custom email provider — production variant). - * * @layer auth * @see docs/guidelines/email-guidelines.md * @see docs/guidelines/logging-guidelines.md @@ -44,7 +42,7 @@ const TEMPLATE_DIR = resolve(dirname(fileURLToPath(import.meta.url)), 'email-tem * Resend SDK-backed email provider for production deployments. * * Injected in place of `MailpitEmailProvider` when `EMAIL_PROVIDER=resend`. - * Enabled and registered by the `AuthModule` in Phase 7. + * Enabled and registered by the `AuthModule`. * * @public */ diff --git a/apps/api/src/config/env.schema.ts b/apps/api/src/config/env.schema.ts index b8261db..fa3a846 100644 --- a/apps/api/src/config/env.schema.ts +++ b/apps/api/src/config/env.schema.ts @@ -11,7 +11,6 @@ * * @layer config * @see docs/guidelines/environment-guidelines.md - * @see docs/DEVELOPMENT_PLAN.md Appendix A */ import { z } from 'zod'; @@ -179,7 +178,7 @@ const base = z.object({ * Password reset delivery method. * * `token` (default) sends a signed link; `otp` sends a short numeric code. - * Both modes are available in this example to cover FCM rows #6 and #7. + * Both modes are available in this example to demonstrate token-link and OTP flows. */ PASSWORD_RESET_METHOD: z .enum(['token', 'otp']) diff --git a/apps/api/src/debug/debug.controller.ts b/apps/api/src/debug/debug.controller.ts index fb665b7..c7c3d14 100644 --- a/apps/api/src/debug/debug.controller.ts +++ b/apps/api/src/debug/debug.controller.ts @@ -10,13 +10,12 @@ * Endpoints: * - `POST /api/debug/lockout` — forces brute-force lockout for a given * `(tenantId, email)` pair so QA can demo the lockout flow without - * manually exhausting failed attempts. Covers FCM row #16. + * manually exhausting failed attempts. * * The lockout key format mirrors the library's internal Redis key: * `:lf:` * * @layer debug - * @see docs/DEVELOPMENT_PLAN.md §Phase 7 P7-5 */ import { diff --git a/apps/api/src/debug/debug.module.ts b/apps/api/src/debug/debug.module.ts index 5ee5189..088165a 100644 --- a/apps/api/src/debug/debug.module.ts +++ b/apps/api/src/debug/debug.module.ts @@ -11,7 +11,6 @@ * * @layer debug * @see debug.controller.ts - * @see docs/DEVELOPMENT_PLAN.md §Phase 7 P7-5 */ import { Module } from '@nestjs/common'; diff --git a/apps/api/src/health/health.controller.ts b/apps/api/src/health/health.controller.ts index 9e0c7d8..5b2f015 100644 --- a/apps/api/src/health/health.controller.ts +++ b/apps/api/src/health/health.controller.ts @@ -2,19 +2,18 @@ * @file health.controller.ts * @description Aggregate readiness probe for `apps/api`. * - * Phase 5 upgrade: checks Postgres (`SELECT 1`), Redis (`PING`), and reads the + * Checks Postgres (`SELECT 1`), Redis (`PING`), and reads the * installed `@bymax-one/nest-auth` version via a walk-up package.json strategy * that is robust against library restructuring. Individual dependency failures * downgrade `status` to `'degraded'` but still return HTTP 200 so orchestrators * can distinguish a degraded-but-alive process from a crash. * * Also exposes `GET /api/health/throttle-demo` decorated with - * `@Throttle(AUTH_THROTTLE_CONFIGS.login)` to demonstrate FCM row #17 (IP-based - * rate limiting) without touching any auth state. + * `@Throttle(AUTH_THROTTLE_CONFIGS.login)` to demonstrate IP-based rate limiting + * without touching any auth state. * * @layer infrastructure * @see health.types.ts - * @see docs/DEVELOPMENT_PLAN.md §Phase 5 P5-4 */ import { createRequire } from 'node:module'; @@ -89,7 +88,7 @@ const LIB_VERSION: string = resolveLibraryVersion(); * * Mounted under the global `/api` prefix: * - `GET /api/health` — aggregate readiness probe. - * - `GET /api/health/throttle-demo` — throttled endpoint for FCM #17 demo. + * - `GET /api/health/throttle-demo` — throttled endpoint for rate-limiting demo. * * @public */ @@ -110,8 +109,8 @@ export class HealthController { * status without throwing, so HTTP 200 is always returned with a body * that orchestrators can inspect. * - * Marked `@Public()` so that once the global `JwtAuthGuard` is registered - * in Phase 7 this route remains accessible to liveness probes without a token. + * Marked `@Public()` so the route remains accessible to liveness probes without + * a JWT (the global `JwtAuthGuard` would otherwise reject it). * * @returns Aggregate health status with per-dependency details. */ @@ -140,15 +139,16 @@ export class HealthController { } /** - * Throttled demo endpoint for FCM row #17. + * Throttled demo endpoint for IP-based rate limiting. * * Applies the `login` throttle tier (5 requests per 60 s per IP) so the * frontend can demonstrate the HTTP 429 response without touching any auth * state. `@UseGuards(ThrottlerGuard)` enables throttling for this route; * `@Throttle` overrides the module-level default with the login tier. * - * Marked `@Public()` so it remains reachable after Phase 7 registers the - * global `JwtAuthGuard`. The ThrottlerGuard provides IP-level rate limiting. + * Marked `@Public()` so it remains reachable without a JWT + * (the global `JwtAuthGuard` would otherwise reject it). The ThrottlerGuard + * provides IP-level rate limiting. * * @returns Timestamp object confirming the request was served. */ diff --git a/apps/api/src/health/health.module.ts b/apps/api/src/health/health.module.ts index cd79794..da03045 100644 --- a/apps/api/src/health/health.module.ts +++ b/apps/api/src/health/health.module.ts @@ -2,13 +2,11 @@ * @file health.module.ts * @description NestJS module that registers the upgraded health-check controller. * - * Phase 5 additions: - * - Imports `PrismaModule` to give `HealthController` access to `PrismaService`. - * - The Redis client is injected via the global `RedisModule` registered in `AppModule`; - * no explicit import is required here. - * - Registers `ThrottlerModule.forRoot` locally so `GET /api/health/throttle-demo` - * can exercise `@Throttle(AUTH_THROTTLE_CONFIGS.login)` in isolation. - * Phase 7 will migrate throttle registration to `AppModule` as a global guard. + * Imports `PrismaModule` to give `HealthController` access to `PrismaService`. + * The Redis client is injected via the global `RedisModule` registered in `AppModule`; + * no explicit import is required here. + * Registers `ThrottlerModule.forRoot` locally so `GET /api/health/throttle-demo` + * can exercise `@Throttle(AUTH_THROTTLE_CONFIGS.login)` in isolation. * * @layer infrastructure */ @@ -24,7 +22,7 @@ import { HealthController } from './health.controller.js'; * * Exposes: * - `GET /api/health` — aggregate Postgres + Redis readiness probe. - * - `GET /api/health/throttle-demo` — throttle demonstration (FCM row #17). + * - `GET /api/health/throttle-demo` — IP-based rate-limiting demonstration. * * The Redis client (`BYMAX_AUTH_REDIS_CLIENT`) is resolved via the global * `RedisModule`; `PrismaService` is resolved via the explicit `PrismaModule` import. diff --git a/apps/api/src/health/health.types.ts b/apps/api/src/health/health.types.ts index 8ea6656..d7dab18 100644 --- a/apps/api/src/health/health.types.ts +++ b/apps/api/src/health/health.types.ts @@ -2,8 +2,6 @@ * @file health.types.ts * @description Shared types for the health-check module. * - * Upgraded in Phase 5 to include Postgres, Redis, and library-version status. - * * @layer infrastructure */ diff --git a/apps/api/src/invitations/invitations.module.ts b/apps/api/src/invitations/invitations.module.ts index bdd0666..c17697c 100644 --- a/apps/api/src/invitations/invitations.module.ts +++ b/apps/api/src/invitations/invitations.module.ts @@ -7,7 +7,6 @@ * the `@Global() RedisModule` and does not need a separate import. * * @layer invitations - * @see docs/DEVELOPMENT_PLAN.md §Phase 14 P14-6 */ import { Module } from '@nestjs/common'; diff --git a/apps/api/src/invitations/invitations.service.spec.ts b/apps/api/src/invitations/invitations.service.spec.ts index 10c811d..4d2ce2b 100644 --- a/apps/api/src/invitations/invitations.service.spec.ts +++ b/apps/api/src/invitations/invitations.service.spec.ts @@ -13,7 +13,6 @@ * `@bymax-one/nest-auth` helper functions are mocked via `jest.unstable_mockModule` * so tests run deterministically without real crypto or Redis connections. * - * FCM rows covered: #20 (multi-tenant isolation), #4 (invitation flow). * * @layer test * @see apps/api/src/invitations/invitations.service.ts diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index 7efbb0e..80bd840 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -7,10 +7,6 @@ * cookie delivery by @bymax-one/nest-auth), the `/api` global prefix, a global * ValidationPipe, and graceful shutdown hooks. * - * Phase 5: Migrates `API_PORT` and `WEB_ORIGIN` from `process.env.*` reads to - * `ConfigService`, eliminating the risk of silent `undefined` values - * that bypassed Zod validation. - * * @layer bootstrap */ diff --git a/apps/api/src/notifications/dto/notify.dto.ts b/apps/api/src/notifications/dto/notify.dto.ts index 42476b7..9461fb0 100644 --- a/apps/api/src/notifications/dto/notify.dto.ts +++ b/apps/api/src/notifications/dto/notify.dto.ts @@ -7,8 +7,6 @@ * both fields default server-side when omitted. * * @layer notifications - * @see docs/DEVELOPMENT_PLAN.md §Phase 10 P10-2 - * @see docs/DEVELOPMENT_PLAN.md §Phase 16 P16-3 */ import { IsNotEmpty, IsOptional, IsString, MaxLength } from 'class-validator'; diff --git a/apps/api/src/notifications/notifications.controller.ts b/apps/api/src/notifications/notifications.controller.ts index 92aba53..722b3bd 100644 --- a/apps/api/src/notifications/notifications.controller.ts +++ b/apps/api/src/notifications/notifications.controller.ts @@ -11,13 +11,10 @@ * - `POST /api/debug/notify/:userId` — admin-only; pushes to any user in the * same tenant. Covered by the class-level `@Roles('ADMIN')`. * - * Covers FCM row #24 (WebSocket auth + `WsJwtGuard`) — this controller is the - * trigger side of the demo loop: a call here → gateway emits to user's sockets → - * client receives `notification:new` → `sonner` toast appears. + * This controller is the trigger side of the demo loop: a call here → gateway + * emits to user's sockets → client receives `notification:new` → `sonner` toast appears. * * @layer notifications - * @see docs/DEVELOPMENT_PLAN.md §Phase 10 P10-2 - * @see docs/DEVELOPMENT_PLAN.md §Phase 16 P16-3 */ import { diff --git a/apps/api/src/notifications/notifications.gateway.ts b/apps/api/src/notifications/notifications.gateway.ts index 15c9c68..389db81 100644 --- a/apps/api/src/notifications/notifications.gateway.ts +++ b/apps/api/src/notifications/notifications.gateway.ts @@ -22,10 +22,7 @@ * is called (via `UsersService` and `PlatformService`). All sockets belonging to * that user are forcibly closed. * - * Covers FCM row #24 (WebSocket auth + `WsJwtGuard`). - * * @layer notifications - * @see docs/DEVELOPMENT_PLAN.md §Phase 10 P10-1 * @see docs/guidelines/nest-auth-guidelines.md §Decorators & guards */ diff --git a/apps/api/src/notifications/notifications.module.ts b/apps/api/src/notifications/notifications.module.ts index 1df909e..263d43d 100644 --- a/apps/api/src/notifications/notifications.module.ts +++ b/apps/api/src/notifications/notifications.module.ts @@ -18,7 +18,6 @@ * inject it for status-change disconnect propagation. * * @layer notifications - * @see docs/DEVELOPMENT_PLAN.md §Phase 10 P10-1, P10-2 */ import { Module } from '@nestjs/common'; diff --git a/apps/api/src/platform/dto/list-users.dto.ts b/apps/api/src/platform/dto/list-users.dto.ts index 032d456..12cdaf7 100644 --- a/apps/api/src/platform/dto/list-users.dto.ts +++ b/apps/api/src/platform/dto/list-users.dto.ts @@ -8,7 +8,6 @@ * * @layer platform * @see docs/guidelines/validation-guidelines.md - * @see docs/DEVELOPMENT_PLAN.md §Phase 9 P9-2 */ import { IsNotEmpty, IsString, MaxLength } from 'class-validator'; diff --git a/apps/api/src/platform/dto/update-user-status.dto.ts b/apps/api/src/platform/dto/update-user-status.dto.ts index 837b386..6333462 100644 --- a/apps/api/src/platform/dto/update-user-status.dto.ts +++ b/apps/api/src/platform/dto/update-user-status.dto.ts @@ -9,7 +9,6 @@ * * @layer platform * @see docs/guidelines/validation-guidelines.md - * @see docs/DEVELOPMENT_PLAN.md §Phase 9 P9-2 */ import { IsIn, IsNotEmpty, IsString } from 'class-validator'; diff --git a/apps/api/src/platform/platform.controller.spec.ts b/apps/api/src/platform/platform.controller.spec.ts index 9c697fe..86e86df 100644 --- a/apps/api/src/platform/platform.controller.spec.ts +++ b/apps/api/src/platform/platform.controller.spec.ts @@ -13,7 +13,6 @@ * Pipes (`ParseUUIDPipe`) are bypassed by calling the handler directly with a pre- * validated UUID string — this is correct controller-layer testing practice. * - * FCM row covered: #22 (Platform admin context). * * @layer test * @see apps/api/src/platform/platform.controller.ts diff --git a/apps/api/src/platform/platform.controller.ts b/apps/api/src/platform/platform.controller.ts index caf3ff6..0acf88b 100644 --- a/apps/api/src/platform/platform.controller.ts +++ b/apps/api/src/platform/platform.controller.ts @@ -2,10 +2,10 @@ * @file platform.controller.ts * @description HTTP controller for platform admin endpoints. * - * Demonstrates FCM row #22 (Platform admin context): a platform super-admin - * can list all tenants, list users in any tenant, and mutate a user's status - * across tenant boundaries — capabilities that the tenant-scoped `UsersController` - * intentionally does not allow. + * Demonstrates platform admin capabilities: a platform super-admin can list all + * tenants, list users in any tenant, and mutate a user's status across tenant + * boundaries — capabilities that the tenant-scoped `UsersController` intentionally + * does not allow. * * Guard pipeline (applied at class level): * 1. `JwtPlatformGuard` — verifies the platform-specific JWT cookie. Rejects @@ -20,7 +20,6 @@ * @layer platform * @see docs/guidelines/nestjs-guidelines.md * @see docs/guidelines/nest-auth-guidelines.md §Decorators & guards - * @see docs/DEVELOPMENT_PLAN.md §Phase 9 P9-2 */ import { Body, Controller, Get, Headers, Ip, Param, Patch, Query, UseGuards } from '@nestjs/common'; diff --git a/apps/api/src/platform/platform.module.ts b/apps/api/src/platform/platform.module.ts index e087c45..a9bf4be 100644 --- a/apps/api/src/platform/platform.module.ts +++ b/apps/api/src/platform/platform.module.ts @@ -9,11 +9,9 @@ * `JwtPlatformGuard` and `PlatformRolesGuard` singleton instances created inside * `BymaxAuthModule` are available for `@UseGuards()` in `PlatformController`. * - * Covers FCM row #22 (Platform admin context). * * @layer platform * @see docs/guidelines/nestjs-guidelines.md - * @see docs/DEVELOPMENT_PLAN.md §Phase 9 P9-2 */ import { Module } from '@nestjs/common'; diff --git a/apps/api/src/platform/platform.service.spec.ts b/apps/api/src/platform/platform.service.spec.ts index 0e9a949..f680d3b 100644 --- a/apps/api/src/platform/platform.service.spec.ts +++ b/apps/api/src/platform/platform.service.spec.ts @@ -12,7 +12,6 @@ * - Creates an `AuditLog` row after the transaction commits, with correct fields. * - Swallows `AuditLog` write failures and logs an error instead of propagating. * - * FCM row covered: #22 (Platform admin context). * * @layer test * @see apps/api/src/platform/platform.service.ts diff --git a/apps/api/src/platform/platform.service.ts b/apps/api/src/platform/platform.service.ts index c401284..2238e25 100644 --- a/apps/api/src/platform/platform.service.ts +++ b/apps/api/src/platform/platform.service.ts @@ -16,12 +16,9 @@ * - `passwordHash`, `mfaSecret`, and `mfaRecoveryCodes` are NEVER included in any * field returned by this service. All user reads use an explicit `select` block. * - * Covers FCM row #22 (Platform admin context). - * * @layer platform * @see docs/guidelines/nestjs-guidelines.md * @see docs/guidelines/observability-guidelines.md - * @see docs/DEVELOPMENT_PLAN.md §Phase 9 P9-2 */ import { Injectable, Logger, NotFoundException } from '@nestjs/common'; diff --git a/apps/api/src/projects/projects.controller.spec.ts b/apps/api/src/projects/projects.controller.spec.ts index c4e55f6..c0838bc 100644 --- a/apps/api/src/projects/projects.controller.spec.ts +++ b/apps/api/src/projects/projects.controller.spec.ts @@ -11,9 +11,8 @@ * `RolesGuard` enforces the role gate without the controller hand-rolling RBAC. * * The RolesGuard itself is NOT tested here — it is a library concern. We only - * verify the metadata (FCM #18) so a regression that removes `@Roles` fails here. + * verify the metadata so a regression that removes `@Roles` fails here. * - * FCM rows covered: #18 (RBAC decorator), #19 (CurrentUser), #20 (tenant scoping). * * @layer test * @see apps/api/src/projects/projects.controller.ts @@ -97,7 +96,7 @@ describe('ProjectsController', () => { describe('list', () => { it('returns the array from listByTenant called with user.tenantId', async () => { // The controller must pass user.tenantId — not a query param — to ensure - // the list is always scoped to the authenticated user's tenant (FCM #20). + // the list is always scoped to the authenticated user's tenant. const projects = [makeProject()]; listByTenant.mockResolvedValue(projects); const user = makeUser(); @@ -114,7 +113,7 @@ describe('ProjectsController', () => { describe('create', () => { it('calls service.create with dto, user.id, and user.tenantId', async () => { // The controller must forward all three arguments so the service stores the - // project with the correct owner and tenant (FCM #20). + // project with the correct owner and tenant. const dto: CreateProjectDto = { name: 'My Project' }; const project = makeProject({ name: 'My Project' }); create.mockResolvedValue(project); @@ -128,7 +127,7 @@ describe('ProjectsController', () => { it('has @Roles("ADMIN") metadata so RolesGuard enforces the role gate', () => { // The @Roles decorator must be present on the create handler — removing it - // would silently open project creation to all roles (FCM #18 regression). + // would silently open project creation to all roles. const reflector = new Reflector(); // Access method metadata through the prototype — handler-level metadata // is stored on the prototype property. @@ -148,7 +147,7 @@ describe('ProjectsController', () => { describe('delete', () => { it('calls service.delete with the URL param id and user.tenantId', async () => { // The controller forwards the route param and tenantId so the service can - // do the atomic deleteMany({ id, tenantId }) isolation check (FCM #20). + // do the atomic deleteMany({ id, tenantId }) isolation check. del.mockResolvedValue(undefined); const user = makeUser({ tenantId: 'acme' }); @@ -169,7 +168,7 @@ describe('ProjectsController', () => { it('propagates NotFoundException from service.delete so NestJS maps it to HTTP 404', async () => { // The controller must not swallow service errors — NestJS exception filters // translate NotFoundException to 404. Swallowing it would silently return 204 - // for a project that was not found, misleading the client (FCM #20). + // for a project that was not found, misleading the client. del.mockRejectedValueOnce(new NotFoundException('Project not found')); const user = makeUser({ tenantId: 'acme' }); diff --git a/apps/api/src/projects/projects.controller.ts b/apps/api/src/projects/projects.controller.ts index 36b7a68..8fb2620 100644 --- a/apps/api/src/projects/projects.controller.ts +++ b/apps/api/src/projects/projects.controller.ts @@ -2,10 +2,10 @@ * @file projects.controller.ts * @description HTTP controller for tenant-scoped project endpoints. * - * Demonstrates FCM rows: - * - #18 (RBAC): `@Roles('ADMIN')` gates project creation. - * - #19 (decorators): `@CurrentUser()` injects the authenticated user. - * - #20 (multi-tenant): every service call passes `user.tenantId` so rows are + * Demonstrates library patterns: + * - RBAC: `@Roles('ADMIN')` gates project creation. + * - Decorators: `@CurrentUser()` injects the authenticated user. + * - Multi-tenant isolation: every service call passes `user.tenantId` so rows are * always scoped to the current tenant — never a bare `findMany`. * * `DELETE /:id` is open to any authenticated tenant member — tenant isolation is diff --git a/apps/api/src/projects/projects.module.ts b/apps/api/src/projects/projects.module.ts index 774c0ad..eb79039 100644 --- a/apps/api/src/projects/projects.module.ts +++ b/apps/api/src/projects/projects.module.ts @@ -7,7 +7,6 @@ * providers in `AppModule` — no need to import `AuthModule` here. * * @layer projects - * @see docs/DEVELOPMENT_PLAN.md §Phase 7 P7-4 */ import { Module } from '@nestjs/common'; diff --git a/apps/api/src/projects/projects.service.spec.ts b/apps/api/src/projects/projects.service.spec.ts index 9d6db6a..65582be 100644 --- a/apps/api/src/projects/projects.service.spec.ts +++ b/apps/api/src/projects/projects.service.spec.ts @@ -8,7 +8,6 @@ * - `delete` uses an atomic `deleteMany({ id, tenantId })` and throws * `NotFoundException` when no row is affected — preventing cross-tenant leaks. * - * FCM rows covered: #18 (RBAC), #20 (multi-tenant isolation). * * @layer test * @see apps/api/src/projects/projects.service.ts diff --git a/apps/api/src/projects/projects.service.ts b/apps/api/src/projects/projects.service.ts index 5369b80..732ce31 100644 --- a/apps/api/src/projects/projects.service.ts +++ b/apps/api/src/projects/projects.service.ts @@ -4,7 +4,7 @@ * * Every query is scoped to a `tenantId` parameter — no operation touches * rows belonging to another tenant. This file is a reference implementation - * of tenant-safe data access for FCM row #20 (multi-tenant isolation). + * of tenant-safe data access (multi-tenant isolation). * * Repositories are the only layer that calls `PrismaService` directly. * Projects has no separate repository class because the queries are trivial and diff --git a/apps/api/src/tenants/tenants.controller.spec.ts b/apps/api/src/tenants/tenants.controller.spec.ts index 6733c11..66f91cf 100644 --- a/apps/api/src/tenants/tenants.controller.spec.ts +++ b/apps/api/src/tenants/tenants.controller.spec.ts @@ -9,7 +9,6 @@ * - `@Roles('OWNER')` metadata is present on the `create` handler so that a * regression removing the decorator causes this test to fail. * - * FCM rows covered: #18 (RBAC decorator), #19 (CurrentUser), #20 (tenant scoping). * * @layer test * @see apps/api/src/tenants/tenants.controller.ts diff --git a/apps/api/src/tenants/tenants.controller.ts b/apps/api/src/tenants/tenants.controller.ts index e54ca2a..c7bec52 100644 --- a/apps/api/src/tenants/tenants.controller.ts +++ b/apps/api/src/tenants/tenants.controller.ts @@ -2,7 +2,7 @@ * @file tenants.controller.ts * @description HTTP controller for tenant-management endpoints. * - * Demonstrates FCM rows #19 (library decorators) and #20 (multi-tenant isolation): + * Demonstrates library patterns (decorators and multi-tenant isolation): * - `@CurrentUser()` extracts the authenticated user without touching `req.user` directly. * - `@Roles('OWNER')` uses the library's `RolesGuard` and role hierarchy from auth.config.ts. * diff --git a/apps/api/src/tenants/tenants.module.ts b/apps/api/src/tenants/tenants.module.ts index 18ce507..231ba09 100644 --- a/apps/api/src/tenants/tenants.module.ts +++ b/apps/api/src/tenants/tenants.module.ts @@ -7,7 +7,6 @@ * available globally via the `APP_GUARD` providers registered in `AppModule`. * * @layer tenants - * @see docs/DEVELOPMENT_PLAN.md §Phase 7 P7-3 */ import { Module } from '@nestjs/common'; diff --git a/apps/api/src/tenants/tenants.service.spec.ts b/apps/api/src/tenants/tenants.service.spec.ts index 7f7d676..69c5fc3 100644 --- a/apps/api/src/tenants/tenants.service.spec.ts +++ b/apps/api/src/tenants/tenants.service.spec.ts @@ -7,7 +7,6 @@ * - `create` writes name and slug, propagates unknown DB errors, and converts * Prisma `P2002` (unique violation) to `ConflictException` — race-safe slug check. * - * FCM rows covered: #18 (RBAC), #20 (multi-tenant isolation). * * @layer test * @see apps/api/src/tenants/tenants.service.ts diff --git a/apps/api/src/tenants/tenants.service.ts b/apps/api/src/tenants/tenants.service.ts index fce9d66..0794626 100644 --- a/apps/api/src/tenants/tenants.service.ts +++ b/apps/api/src/tenants/tenants.service.ts @@ -20,7 +20,7 @@ import type { CreateTenantDto } from './dto/create-tenant.dto.js'; * Service that manages tenant CRUD operations. * * Only exposes operations that are safe for authenticated tenant users — - * platform-level tenant management (Phase 9) lives in `platform/`. + * platform-level tenant management lives in `platform/`. * * @public */ diff --git a/apps/api/src/users/users.controller.spec.ts b/apps/api/src/users/users.controller.spec.ts index 6f972e2..2c34b2c 100644 --- a/apps/api/src/users/users.controller.spec.ts +++ b/apps/api/src/users/users.controller.spec.ts @@ -4,10 +4,10 @@ * * Verifies that: * - `GET /users` calls `UsersService.listByTenant` with the authenticated user's - * `tenantId` (cross-tenant access prevention, FCM #20). + * `tenantId` (cross-tenant access prevention. * - `PATCH /users/:id/status` calls `UsersService.updateStatus` with the correct * argument order: targetId, dto, admin.tenantId, admin.id, ip, userAgent. - * - `@Roles('ADMIN')` metadata is present on `updateStatus` (FCM #18). + * - `@Roles('ADMIN')` metadata is present on `updateStatus`. * - `ip` and `userAgent` default to empty strings when the header is absent. * * @layer test @@ -106,7 +106,7 @@ describe('UsersController', () => { describe('listByTenant', () => { it('calls service.listByTenant with user.tenantId and returns the result', async () => { // Scoping by JWT tenantId prevents cross-tenant data leakage regardless - // of what the X-Tenant-Id header contains (FCM #20). + // of what the X-Tenant-Id header contains. const users = [makeUserRecord()]; listByTenant.mockResolvedValue(users); const admin = makeAdmin({ tenantId: 'tenant-99' }); @@ -168,7 +168,7 @@ describe('UsersController', () => { it('has @Roles("ADMIN") metadata so RolesGuard enforces the role gate', () => { // Removing @Roles would allow MEMBER/VIEWER users to ban or suspend - // other members, violating RBAC (FCM #18). + // other members, violating RBAC. const roles = Reflect.getMetadata( 'roles', UsersController.prototype.updateStatus as object, diff --git a/apps/api/src/users/users.controller.ts b/apps/api/src/users/users.controller.ts index f13bd6c..d8f5fbd 100644 --- a/apps/api/src/users/users.controller.ts +++ b/apps/api/src/users/users.controller.ts @@ -2,7 +2,7 @@ * @file users.controller.ts * @description HTTP controller for user-management endpoints. * - * Demonstrates FCM row #23 (account status enforcement): admins can update + * Demonstrates account status enforcement: admins can update * a tenant member's status so that `UserStatusGuard` blocks them on their * next authenticated request. * diff --git a/apps/api/src/users/users.module.ts b/apps/api/src/users/users.module.ts index d86b6b6..6bfe373 100644 --- a/apps/api/src/users/users.module.ts +++ b/apps/api/src/users/users.module.ts @@ -8,7 +8,6 @@ * need user reads/writes without duplicating Prisma logic. * * @layer users - * @see docs/DEVELOPMENT_PLAN.md §Phase 7 P7-6 */ import { Module } from '@nestjs/common'; @@ -25,7 +24,7 @@ import { UsersService } from './users.service.js'; * * Imports `NotificationsModule` so that `UsersService` can call * `NotificationsGateway.maybeDisconnectBlockedUser` when a user's status is - * changed to a blocked value (FCM row #24 — suspend disconnects the WS session). + * changed to a blocked value. * * @public */ diff --git a/apps/api/src/users/users.service.spec.ts b/apps/api/src/users/users.service.spec.ts index 4b6ef61..d86a0ff 100644 --- a/apps/api/src/users/users.service.spec.ts +++ b/apps/api/src/users/users.service.spec.ts @@ -8,9 +8,6 @@ * maybeDisconnectBlockedUser called; AuditLog write failure swallowed. * - `listByTenant`: delegates to prisma.user.findMany scoped by tenantId. * - * FCM rows covered: #20 (multi-tenant isolation), #21 (audit logging), - * #24 (blocked-user disconnect). - * * @layer test * @see apps/api/src/users/users.service.ts */ diff --git a/apps/api/src/users/users.service.ts b/apps/api/src/users/users.service.ts index 32719a1..28410f4 100644 --- a/apps/api/src/users/users.service.ts +++ b/apps/api/src/users/users.service.ts @@ -5,13 +5,11 @@ * * The audit log entry is written directly via `PrismaService` in this service * rather than through `AppAuthHooks`, which is an acceptable "equivalent direct - * write" explicitly permitted by the development plan §P7-6. - * `AppAuthHooks` owns lifecycle hooks fired by the auth library; this service - * owns admin-initiated mutations that are outside the library's responsibility. + * write". `AppAuthHooks` owns lifecycle hooks fired by the auth library; this + * service owns admin-initiated mutations that are outside the library's responsibility. * * @layer users * @see docs/guidelines/nestjs-guidelines.md - * @see docs/DEVELOPMENT_PLAN.md §Phase 7 P7-6 */ import { ForbiddenException, Inject, Injectable, Logger, NotFoundException } from '@nestjs/common'; @@ -46,7 +44,7 @@ export interface TenantUserRecord { * Service that handles user-admin operations. * * Only exposes operations that are safe for tenant admins to perform. - * Platform-level user management lives in `platform/` (Phase 9). + * Platform-level user management lives in `platform/`. * * @public */ diff --git a/apps/api/test/audit-hooks.e2e-spec.ts b/apps/api/test/audit-hooks.e2e-spec.ts index 31c0135..1a11b6e 100644 --- a/apps/api/test/audit-hooks.e2e-spec.ts +++ b/apps/api/test/audit-hooks.e2e-spec.ts @@ -1,7 +1,7 @@ import { WsAdapter } from '@nestjs/platform-ws'; /** * @file audit-hooks.e2e-spec.ts - * @description e2e spec for FCM #30 — comprehensive `IAuthHooks` coverage via + * @description End-to-end spec for comprehensive `IAuthHooks` coverage via * the `AuditLog` table. * * Other specs already cover `session.new`, `session.evicted`, `oauth.login`, @@ -222,7 +222,7 @@ describe('AppAuthHooks audit trail — FCM #30 (every lifecycle slug is recorded /* * Enrolling MFA fires `afterMfaEnabled`. This is the audit trail any * compliance audit will require to prove who turned on / off MFA on which - * account. Drives FCM #8. + * account. */ const email = uniqueEmail('audit-mfa-on'); const password = 'P@ssw0rd12345'; diff --git a/apps/api/test/auth-smoke.e2e-spec.ts b/apps/api/test/auth-smoke.e2e-spec.ts index a1e3756..da1246a 100644 --- a/apps/api/test/auth-smoke.e2e-spec.ts +++ b/apps/api/test/auth-smoke.e2e-spec.ts @@ -1,18 +1,13 @@ import { WsAdapter } from '@nestjs/platform-ws'; /** * @file auth-smoke.e2e-spec.ts - * @description Phase 7 smoke e2e test that exercises the core auth flow end-to-end: + * @description End-to-end smoke spec exercising the core auth flow end-to-end: * register → verify email → login → /me → /projects → logout → refresh. * * Requires `docker-compose.test.yml` services to be running (Postgres at 55432, * Redis at 56379, Mailpit SMTP at 51025, Mailpit UI at 58025). * - * FCM rows covered: #1 (register), #2 (login), #3 (JWT rotation), #4 (revocation - * via logout), #5 (email verification), #13 (session implied), #20 (tenant-scoped - * project listing), #29 (error envelope path). - * * @layer test - * @see docs/DEVELOPMENT_PLAN.md §Phase 7 P7-8 * @see test/helpers/mailpit.ts */ diff --git a/apps/api/test/brute-force-lockout.e2e-spec.ts b/apps/api/test/brute-force-lockout.e2e-spec.ts index 9b650f0..710b794 100644 --- a/apps/api/test/brute-force-lockout.e2e-spec.ts +++ b/apps/api/test/brute-force-lockout.e2e-spec.ts @@ -1,18 +1,16 @@ import { WsAdapter } from '@nestjs/platform-ws'; /** * @file brute-force-lockout.e2e-spec.ts - * @description Phase 17 e2e spec for brute-force account lockout protection. + * @description End-to-end spec for brute-force account lockout protection. * * Verifies that 5 consecutive wrong-password attempts lock the account, and * that repeated failures on an unknown email do not expose enumeration data. * - * Covers FCM row #16 (brute-force / rate limiting per user). * * Requires `docker-compose.test.yml` services to be running (Postgres at 55432, * Redis at 56379, Mailpit SMTP at 51025, Mailpit UI at 58025). * * @layer test - * @see docs/DEVELOPMENT_PLAN.md §Phase 17 P17-6 * @see test/helpers/mailpit.ts */ diff --git a/apps/api/test/crypto-roundtrip.spec.ts b/apps/api/test/crypto-roundtrip.spec.ts index 00c8ea8..4f7741b 100644 --- a/apps/api/test/crypto-roundtrip.spec.ts +++ b/apps/api/test/crypto-roundtrip.spec.ts @@ -5,12 +5,11 @@ * * These tests demonstrate and verify the library's `encrypt`/`decrypt` * (AES-256-GCM), `hmacSha256`, `timingSafeCompare`, and `sleep` utilities. - * The functions are consumed here via their public export so the Phase 20 audit - * confirms every symbol is reachable from application code. + * The functions are consumed here via their public export to confirm every + * symbol is reachable from application code. * * All keys and test inputs are synthetic — never use these values in production. * - * @see docs/DEVELOPMENT_PLAN.md §Appendix B — Library Export → Example File Map * @layer test */ diff --git a/apps/api/test/debug-timing.e2e-spec.ts b/apps/api/test/debug-timing.e2e-spec.ts index c0f9ad3..453c723 100644 --- a/apps/api/test/debug-timing.e2e-spec.ts +++ b/apps/api/test/debug-timing.e2e-spec.ts @@ -1,3 +1,9 @@ +/** + * @file debug-timing.e2e-spec.ts + * @description End-to-end spec verifying that the debug endpoint is unreachable + * in production and returns results within the expected latency window in development. + */ + process.env['NODE_ENV'] = 'test'; process.env['DATABASE_URL'] = 'postgresql://postgres:postgres@localhost:55432/example_app_test'; process.env['REDIS_URL'] = 'redis://127.0.0.1:56379'; @@ -13,11 +19,9 @@ process.env['JWT_SECRET'] = process.env['MFA_ENCRYPTION_KEY'] = 'dGVzdC1lbmNyeXB0aW9uLWtleS0zMmJ5dGVzLW9rPT0='; // Verify process.env was set BEFORE imports are processed -console.log('[PRE-IMPORT] REDIS_URL =', process.env['REDIS_URL']); import type { INestApplication } from '@nestjs/common'; import { ValidationPipe } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; import { Test } from '@nestjs/testing'; import { WsAdapter } from '@nestjs/platform-ws'; import cookieParser from 'cookie-parser'; @@ -32,15 +36,8 @@ describe('Debug env vars', () => { let agent: Agent; beforeAll(async () => { - console.log('[BEFORE-ALL] process.env.REDIS_URL =', process.env['REDIS_URL']); - const moduleRef = await Test.createTestingModule({ imports: [AppModule] }).compile(); - const config = moduleRef.get(ConfigService); - console.log('[BEFORE-ALL] ConfigService REDIS_URL =', config.get('REDIS_URL')); - console.log('[BEFORE-ALL] ConfigService DATABASE_URL =', config.get('DATABASE_URL')); - console.log('[BEFORE-ALL] ConfigService LOG_LEVEL =', config.get('LOG_LEVEL')); - const prisma = moduleRef.get(PrismaService); await prisma.$executeRaw` INSERT INTO "Tenant" (id, name, slug, "createdAt", "updatedAt") @@ -65,19 +62,19 @@ describe('Debug env vars', () => { await app.close(); }, 60000); + // Protects: environment variables are correctly injected before any module is imported. it('env vars are correct', () => { expect(process.env['REDIS_URL']).toBe('redis://127.0.0.1:56379'); }); + // Protects: the registration endpoint is reachable and does not return a 5xx error. it('POST /register responds', async () => { const email = `debug-${Date.now()}@example.test`; - const t = Date.now(); const res = await agent .post('/api/auth/register') .set('Content-Type', 'application/json') .set('X-Tenant-Id', 'acme') .send({ email, password: 'P@ssw0rd12345', name: 'Debug User', tenantId: 'acme' }); - console.log(`[DEBUG] /register: ${Date.now() - t}ms, status=${res.status}`); expect(res.status).toBeLessThan(500); }, 15000); }); diff --git a/apps/api/test/dto-schema.spec.ts b/apps/api/test/dto-schema.spec.ts index dc2f03b..d05be2d 100644 --- a/apps/api/test/dto-schema.spec.ts +++ b/apps/api/test/dto-schema.spec.ts @@ -12,7 +12,6 @@ * 3. Confirming that service tokens are importable for DI composition * (`SessionService`, `OtpService`, `PasswordResetService`). * - * @see docs/DEVELOPMENT_PLAN.md §Appendix B — Library Export → Example File Map * @layer test */ diff --git a/apps/api/test/helpers/db.ts b/apps/api/test/helpers/db.ts index 9dc534c..c1a5ec9 100644 --- a/apps/api/test/helpers/db.ts +++ b/apps/api/test/helpers/db.ts @@ -10,7 +10,6 @@ * the FK constraints intact — see the `keepRows` note below. * * @layer test - * @see docs/DEVELOPMENT_PLAN.md §Phase 17 P17-8 */ import type { PrismaClient } from '@prisma/client'; diff --git a/apps/api/test/helpers/fake-google.ts b/apps/api/test/helpers/fake-google.ts index 1755d49..3358924 100644 --- a/apps/api/test/helpers/fake-google.ts +++ b/apps/api/test/helpers/fake-google.ts @@ -11,7 +11,6 @@ * uninstallFakeGoogle(); * * @layer test - * @see docs/DEVELOPMENT_PLAN.md §Phase 8 P8-3 */ /** Google token endpoint intercepted by this stub. */ diff --git a/apps/api/test/helpers/mailpit.ts b/apps/api/test/helpers/mailpit.ts index 45f9cbd..bef462e 100644 --- a/apps/api/test/helpers/mailpit.ts +++ b/apps/api/test/helpers/mailpit.ts @@ -8,7 +8,6 @@ * Mailpit API base: `http://localhost:58025` (test-stack port from docker-compose.test.yml). * * @layer test - * @see docs/DEVELOPMENT_PLAN.md §Phase 7 P7-8 */ /** Base URL for the Mailpit REST API running on the test stack. */ diff --git a/apps/api/test/helpers/redis.ts b/apps/api/test/helpers/redis.ts index aaed0b1..be209b3 100644 --- a/apps/api/test/helpers/redis.ts +++ b/apps/api/test/helpers/redis.ts @@ -9,7 +9,6 @@ * keys intact when multiple test suites run concurrently. * * @layer test - * @see docs/DEVELOPMENT_PLAN.md §Phase 17 P17-8 * @see docs/guidelines/redis-guidelines.md */ diff --git a/apps/api/test/helpers/ws.ts b/apps/api/test/helpers/ws.ts index 1f7f3ef..636b9da 100644 --- a/apps/api/test/helpers/ws.ts +++ b/apps/api/test/helpers/ws.ts @@ -14,7 +14,6 @@ * obtained by POSTing to `POST /api/auth/login`. * * @layer test - * @see docs/DEVELOPMENT_PLAN.md §Phase 10 P10-3 * @see apps/api/test/websocket-auth.e2e-spec.ts */ diff --git a/apps/api/test/invitations.e2e-spec.ts b/apps/api/test/invitations.e2e-spec.ts index de35ce1..fd71dc6 100644 --- a/apps/api/test/invitations.e2e-spec.ts +++ b/apps/api/test/invitations.e2e-spec.ts @@ -1,18 +1,16 @@ import { WsAdapter } from '@nestjs/platform-ws'; /** * @file invitations.e2e-spec.ts - * @description Phase 8 e2e spec for the full user-invitation flow: + * @description End-to-end spec for the user-invitation flow: * an ADMIN creates an invitation → Mailpit captures the email → the invitee * extracts the accept token → calls the accept endpoint → a new `users` row * is created with the correct `tenantId`, `role`, and `emailVerified = true`. * - * Covers FCM row #21 (User invitations). * * Requires `docker-compose.test.yml` services to be running (Postgres at 55432, * Redis at 56379, Mailpit SMTP at 51025, Mailpit UI at 58025). * * @layer test - * @see docs/DEVELOPMENT_PLAN.md §Phase 8 P8-5 * @see test/helpers/mailpit.ts */ diff --git a/apps/api/test/jwt-revocation.e2e-spec.ts b/apps/api/test/jwt-revocation.e2e-spec.ts index 69fab88..9b06d12 100644 --- a/apps/api/test/jwt-revocation.e2e-spec.ts +++ b/apps/api/test/jwt-revocation.e2e-spec.ts @@ -1,7 +1,7 @@ import { WsAdapter } from '@nestjs/platform-ws'; /** * @file jwt-revocation.e2e-spec.ts - * @description Phase 17 e2e spec for JWT revocation and session invalidation. + * @description End-to-end spec for JWT revocation and session invalidation. * * Covers: * 1. DELETE /api/auth/sessions/all revokes all active sessions; GET /me returns 401. @@ -11,7 +11,6 @@ import { WsAdapter } from '@nestjs/platform-ws'; * Redis at 56379, Mailpit SMTP at 51025, Mailpit UI at 58025). * * @layer test - * @see docs/DEVELOPMENT_PLAN.md §Phase 17 P17-4 * @see test/helpers/mailpit.ts */ diff --git a/apps/api/test/login-and-logout.e2e-spec.ts b/apps/api/test/login-and-logout.e2e-spec.ts index e790015..e6ed100 100644 --- a/apps/api/test/login-and-logout.e2e-spec.ts +++ b/apps/api/test/login-and-logout.e2e-spec.ts @@ -1,7 +1,7 @@ import { WsAdapter } from '@nestjs/platform-ws'; /** * @file login-and-logout.e2e-spec.ts - * @description Phase 17 e2e spec for login, /me, and logout flows. + * @description End-to-end spec for the login, /me, and logout flows. * * Covers: * 1. Login with valid credentials sets HttpOnly auth cookies. @@ -14,7 +14,6 @@ import { WsAdapter } from '@nestjs/platform-ws'; * Redis at 56379, Mailpit SMTP at 51025, Mailpit UI at 58025). * * @layer test - * @see docs/DEVELOPMENT_PLAN.md §Phase 17 P17-4 * @see test/helpers/mailpit.ts */ diff --git a/apps/api/test/mfa-setup-challenge-disable.e2e-spec.ts b/apps/api/test/mfa-setup-challenge-disable.e2e-spec.ts index 7b040c8..3d7f5d8 100644 --- a/apps/api/test/mfa-setup-challenge-disable.e2e-spec.ts +++ b/apps/api/test/mfa-setup-challenge-disable.e2e-spec.ts @@ -16,7 +16,6 @@ import { WsAdapter } from '@nestjs/platform-ws'; * Mailpit SMTP at 51025, Mailpit UI at 58025). * * @layer test - * @see docs/DEVELOPMENT_PLAN.md §Phase 17 P17-5 * @see test/helpers/mailpit.ts */ diff --git a/apps/api/test/new-session-alert.e2e-spec.ts b/apps/api/test/new-session-alert.e2e-spec.ts index e62ee4c..de33789 100644 --- a/apps/api/test/new-session-alert.e2e-spec.ts +++ b/apps/api/test/new-session-alert.e2e-spec.ts @@ -1,7 +1,7 @@ import { WsAdapter } from '@nestjs/platform-ws'; /** * @file new-session-alert.e2e-spec.ts - * @description e2e spec for FCM #15 — new-session security email alert. + * @description End-to-end spec for the new-session security email alert. * * The library never invokes `IEmailProvider.sendNewSessionAlert` itself — * consumers are responsible for dispatching the email from the `onNewSession` diff --git a/apps/api/test/oauth-link.e2e-spec.ts b/apps/api/test/oauth-link.e2e-spec.ts index 923254e..ef5d924 100644 --- a/apps/api/test/oauth-link.e2e-spec.ts +++ b/apps/api/test/oauth-link.e2e-spec.ts @@ -1,13 +1,12 @@ import { WsAdapter } from '@nestjs/platform-ws'; /** * @file oauth-link.e2e-spec.ts - * @description Phase 8 e2e spec that verifies the OAuth account-linking guarantee: + * @description End-to-end spec verifying the OAuth account-linking guarantee: * a user who first registers with email+password, then signs in via Google OAuth * with the same email address, ends up on the **same** `users` row — with * `oauthProvider = 'google'` and `oauthProviderId` populated — rather than a * duplicate row being created. * - * Covers FCM row #12 (OAuth Google sign-in & link — the linking half). * * Requires `docker-compose.test.yml` services to be running (Postgres at 55432, * Redis at 56379, Mailpit SMTP at 51025, Mailpit UI at 58025). @@ -16,7 +15,6 @@ import { WsAdapter } from '@nestjs/platform-ws'; * `accounts.google.com` or `googleapis.com` requests are made. * * @layer test - * @see docs/DEVELOPMENT_PLAN.md §Phase 8 P8-3 * @see test/helpers/fake-google.ts * @see test/helpers/mailpit.ts */ diff --git a/apps/api/test/password-reset-otp.e2e-spec.ts b/apps/api/test/password-reset-otp.e2e-spec.ts index 8c73f63..de5d819 100644 --- a/apps/api/test/password-reset-otp.e2e-spec.ts +++ b/apps/api/test/password-reset-otp.e2e-spec.ts @@ -13,7 +13,6 @@ import { WsAdapter } from '@nestjs/platform-ws'; * Mailpit SMTP at 51025, Mailpit UI at 58025). * * @layer test - * @see docs/DEVELOPMENT_PLAN.md §Phase 17 P17-5 * @see test/helpers/mailpit.ts */ diff --git a/apps/api/test/password-reset-token.e2e-spec.ts b/apps/api/test/password-reset-token.e2e-spec.ts index d46af25..b2f2848 100644 --- a/apps/api/test/password-reset-token.e2e-spec.ts +++ b/apps/api/test/password-reset-token.e2e-spec.ts @@ -15,7 +15,6 @@ import { WsAdapter } from '@nestjs/platform-ws'; * Mailpit SMTP at 51025, Mailpit UI at 58025). * * @layer test - * @see docs/DEVELOPMENT_PLAN.md §Phase 17 P17-5 * @see test/helpers/mailpit.ts */ diff --git a/apps/api/test/platform-endpoints.e2e-spec.ts b/apps/api/test/platform-endpoints.e2e-spec.ts index 21ac568..09eff11 100644 --- a/apps/api/test/platform-endpoints.e2e-spec.ts +++ b/apps/api/test/platform-endpoints.e2e-spec.ts @@ -1,8 +1,7 @@ import { WsAdapter } from '@nestjs/platform-ws'; /** * @file platform-endpoints.e2e-spec.ts - * @description Consolidated e2e spec for FCM #22 platform endpoints that were - * previously uncovered by the suite: + * @description Consolidated e2e spec for the platform admin endpoints: * * - POST /api/auth/platform/refresh (rotates refresh, returns admin) * - POST /api/auth/platform/logout (revokes the session's tokens) diff --git a/apps/api/test/platform-isolation.e2e-spec.ts b/apps/api/test/platform-isolation.e2e-spec.ts index f5a4c4a..a4c1f9e 100644 --- a/apps/api/test/platform-isolation.e2e-spec.ts +++ b/apps/api/test/platform-isolation.e2e-spec.ts @@ -1,10 +1,10 @@ import { WsAdapter } from '@nestjs/platform-ws'; /** * @file platform-isolation.e2e-spec.ts - * @description Phase 9 e2e spec that proves platform-context tokens cannot access + * @description End-to-end spec proving platform-context tokens cannot access * dashboard routes, and dashboard tokens cannot access platform routes. * - * This is the guard for FCM row #22: "platform admin context". If a platform JWT + * This spec guards the platform admin context isolation: if a platform JWT * ever passes `JwtAuthGuard` on a tenant route, or a tenant JWT ever passes * `JwtPlatformGuard` on a platform route, this spec fails — which is the point. * @@ -17,10 +17,8 @@ import { WsAdapter } from '@nestjs/platform-ws'; * Requires `docker-compose.test.yml` services to be running (Postgres at 55432, * Redis at 56379, Mailpit SMTP at 51025, Mailpit UI at 58025). * - * Covers FCM row #22 (Platform admin context — isolation guarantee). * * @layer test - * @see docs/DEVELOPMENT_PLAN.md §Phase 9 P9-3 * @see test/helpers/mailpit.ts */ @@ -256,7 +254,7 @@ describe('Platform isolation — platform token cannot access dashboard routes a // Scenario: JwtAuthGuard is wired as a global guard for all routes EXCEPT // platform ones. A platform JWT (issued by /api/auth/platform/login) must be // rejected at the JwtAuthGuard boundary so that /api/projects returns 401 or 403. - // This guards against cross-context privilege escalation (FCM #22). + // This guards against cross-context privilege escalation. const res = await supertest .agent(app.getHttpServer()) .get('/api/projects') @@ -271,7 +269,7 @@ describe('Platform isolation — platform token cannot access dashboard routes a // Scenario: JwtPlatformGuard is applied at the PlatformController class level. // A tenant JWT (issued by /api/auth/login) must be rejected when used against // /api/platform/tenants — the guard verifies the JWT payload shape is a platform - // payload, not a tenant payload. Covers FCM #22 isolation guarantee. + // payload, not a tenant payload. const res = await dashboardAgent.get('/api/platform/tenants'); // 401 or 403 — both are valid rejections of a non-platform token. @@ -284,7 +282,7 @@ describe('Platform isolation — platform token cannot access dashboard routes a // Scenario: positive sanity check — the platform admin's bearer token must // successfully reach GET /api/platform/tenants and receive a 200 with an array. // Platform auth is bearer-only; the token is sent in the Authorization header. - // Confirms the guard pipeline works in the happy path (FCM #22). + // Confirms the guard pipeline works in the happy path. const res = await supertest .agent(app.getHttpServer()) .get('/api/platform/tenants') @@ -300,7 +298,7 @@ describe('Platform isolation — platform token cannot access dashboard routes a it('allows the dashboard token to access tenant-scoped project listing', async () => { // Scenario: positive sanity check — the dashboard agent logged in above must // successfully reach GET /api/projects (with X-Tenant-Id) and receive 200. - // Confirms that the dashboard guard pipeline still works correctly (FCM #20). + // Confirms that the dashboard guard pipeline still works correctly. const res = await dashboardAgent.get('/api/projects').set('X-Tenant-Id', TENANT_ID); expect(res.status).toBe(200); @@ -313,7 +311,7 @@ describe('Platform isolation — platform token cannot access dashboard routes a // Scenario: the @PlatformRoles('SUPER_ADMIN') override on the PATCH endpoint // means SUPPORT users (who can read tenants and users) must be blocked from // status mutations. This guards against SUPPORT role privilege escalation - // to write operations (FCM #22 — authorization boundary). + // to write operations — authorization boundary. const res = await supertest .agent(app.getHttpServer()) .patch(`/api/platform/users/00000000-0000-4000-8000-000000000001/status`) @@ -330,7 +328,7 @@ describe('Platform isolation — platform token cannot access dashboard routes a // Scenario: platform auth is bearer-only (tokens in response body, no cookies) // while dashboard auth uses HttpOnly cookies (no tokens in body). This separation // prevents a browser from accidentally sending a platform token on a dashboard route - // or vice versa (FCM #22 — isolation guarantee). + // or vice versa — context isolation guarantee. const freshPlatformAgent = supertest.agent(app.getHttpServer()); const platformLogin = await freshPlatformAgent .post('/api/auth/platform/login') diff --git a/apps/api/test/rbac.e2e-spec.ts b/apps/api/test/rbac.e2e-spec.ts index 7c21705..cf03bee 100644 --- a/apps/api/test/rbac.e2e-spec.ts +++ b/apps/api/test/rbac.e2e-spec.ts @@ -1,19 +1,17 @@ import { WsAdapter } from '@nestjs/platform-ws'; /** * @file rbac.e2e-spec.ts - * @description Phase 17 e2e spec for Role-Based Access Control (RBAC). + * @description End-to-end spec for Role-Based Access Control (RBAC). * * Verifies that the `@Roles` decorator and `RolesGuard` wiring enforce the * role hierarchy (OWNER > ADMIN > MEMBER > VIEWER) for the project creation * endpoint (`POST /api/projects`, gated at `ADMIN`). * - * Covers FCM row #18 (RBAC — role hierarchy enforcement). * * Requires `docker-compose.test.yml` services to be running (Postgres at 55432, * Redis at 56379, Mailpit SMTP at 51025, Mailpit UI at 58025). * * @layer test - * @see docs/DEVELOPMENT_PLAN.md §Phase 17 P17-7 * @see test/helpers/mailpit.ts */ diff --git a/apps/api/test/recovery-codes.e2e-spec.ts b/apps/api/test/recovery-codes.e2e-spec.ts index 6ffa116..18a3946 100644 --- a/apps/api/test/recovery-codes.e2e-spec.ts +++ b/apps/api/test/recovery-codes.e2e-spec.ts @@ -14,7 +14,6 @@ import { WsAdapter } from '@nestjs/platform-ws'; * Mailpit SMTP at 51025, Mailpit UI at 58025). * * @layer test - * @see docs/DEVELOPMENT_PLAN.md §Phase 17 P17-5 * @see test/helpers/mailpit.ts */ diff --git a/apps/api/test/refresh-rotation.e2e-spec.ts b/apps/api/test/refresh-rotation.e2e-spec.ts index dad27b8..43b7a50 100644 --- a/apps/api/test/refresh-rotation.e2e-spec.ts +++ b/apps/api/test/refresh-rotation.e2e-spec.ts @@ -1,7 +1,7 @@ import { WsAdapter } from '@nestjs/platform-ws'; /** * @file refresh-rotation.e2e-spec.ts - * @description Phase 17 e2e spec for JWT refresh-token rotation. + * @description End-to-end spec for JWT refresh-token rotation. * * Covers: * 1. POST /api/auth/refresh rotates the access_token cookie (200 + new Set-Cookie). @@ -16,7 +16,6 @@ import { WsAdapter } from '@nestjs/platform-ws'; * (jwt.refreshGraceWindowSeconds: 30). * * @layer test - * @see docs/DEVELOPMENT_PLAN.md §Phase 17 P17-4 * @see test/helpers/mailpit.ts */ diff --git a/apps/api/test/register-and-verify.e2e-spec.ts b/apps/api/test/register-and-verify.e2e-spec.ts index 95f4f64..034b93b 100644 --- a/apps/api/test/register-and-verify.e2e-spec.ts +++ b/apps/api/test/register-and-verify.e2e-spec.ts @@ -1,7 +1,7 @@ import { WsAdapter } from '@nestjs/platform-ws'; /** * @file register-and-verify.e2e-spec.ts - * @description Phase 17 e2e spec for user registration and email-verification flow. + * @description End-to-end spec for user registration and email-verification flow. * * Covers: * 1. POST /api/auth/register returns 201 with PENDING status; Mailpit receives @@ -14,7 +14,6 @@ import { WsAdapter } from '@nestjs/platform-ws'; * Redis at 56379, Mailpit SMTP at 51025, Mailpit UI at 58025). * * @layer test - * @see docs/DEVELOPMENT_PLAN.md §Phase 17 P17-4 * @see test/helpers/mailpit.ts */ diff --git a/apps/api/test/security-headers.e2e-spec.ts b/apps/api/test/security-headers.e2e-spec.ts index ef618db..76d7b19 100644 --- a/apps/api/test/security-headers.e2e-spec.ts +++ b/apps/api/test/security-headers.e2e-spec.ts @@ -18,7 +18,6 @@ import { WsAdapter } from '@nestjs/platform-ws'; * the same error code as a known-email / wrong-password login, so the * response does not leak account existence. * - * FCM rows covered: security hardening (Phase 20 P20-3). * * @layer test * @see apps/api/src/main.ts (helmet registration) diff --git a/apps/api/test/session-fifo-eviction.e2e-spec.ts b/apps/api/test/session-fifo-eviction.e2e-spec.ts index 6dfb7ab..c319564 100644 --- a/apps/api/test/session-fifo-eviction.e2e-spec.ts +++ b/apps/api/test/session-fifo-eviction.e2e-spec.ts @@ -1,17 +1,15 @@ import { WsAdapter } from '@nestjs/platform-ws'; /** * @file session-fifo-eviction.e2e-spec.ts - * @description Phase 17 e2e spec for FIFO session eviction when a user exceeds + * @description End-to-end spec for FIFO session eviction when a user exceeds * `defaultMaxSessions` (5). Creating a 6th session must evict the oldest (first) * session and record a `session.evicted` AuditLog entry. * - * Covers FCM row #13 (session management — FIFO eviction policy). * * Requires `docker-compose.test.yml` services to be running (Postgres at 55432, * Redis at 56379, Mailpit SMTP at 51025, Mailpit UI at 58025). * * @layer test - * @see docs/DEVELOPMENT_PLAN.md §Phase 17 P17-6 * @see test/helpers/mailpit.ts */ diff --git a/apps/api/test/sessions-list-revoke.e2e-spec.ts b/apps/api/test/sessions-list-revoke.e2e-spec.ts index 25ca6e8..264db5d 100644 --- a/apps/api/test/sessions-list-revoke.e2e-spec.ts +++ b/apps/api/test/sessions-list-revoke.e2e-spec.ts @@ -1,17 +1,15 @@ import { WsAdapter } from '@nestjs/platform-ws'; /** * @file sessions-list-revoke.e2e-spec.ts - * @description Phase 17 e2e spec for session listing and revocation: + * @description End-to-end spec for session listing and revocation: * listing active sessions, revoking a single session by sessionHash, and * revoking all sessions at once via DELETE /api/auth/sessions/all. * - * Covers FCM rows: #13 (session management), #4 (token revocation). * * Requires `docker-compose.test.yml` services to be running (Postgres at 55432, * Redis at 56379, Mailpit SMTP at 51025, Mailpit UI at 58025). * * @layer test - * @see docs/DEVELOPMENT_PLAN.md §Phase 17 P17-6 * @see test/helpers/mailpit.ts */ diff --git a/apps/api/test/setup.ts b/apps/api/test/setup.ts index d6af0cd..fd35f98 100644 --- a/apps/api/test/setup.ts +++ b/apps/api/test/setup.ts @@ -15,7 +15,6 @@ * ``` * * @layer test - * @see docs/DEVELOPMENT_PLAN.md §Phase 17 P17-8 * @see test/helpers/db.ts * @see test/helpers/redis.ts */ diff --git a/apps/api/test/status-enforcement.e2e-spec.ts b/apps/api/test/status-enforcement.e2e-spec.ts index 0a10f8e..1f05e9f 100644 --- a/apps/api/test/status-enforcement.e2e-spec.ts +++ b/apps/api/test/status-enforcement.e2e-spec.ts @@ -1,19 +1,17 @@ import { WsAdapter } from '@nestjs/platform-ws'; /** * @file status-enforcement.e2e-spec.ts - * @description Phase 17 e2e spec for account status enforcement. + * @description End-to-end spec for account status enforcement. * * Verifies that the `UserStatusGuard` blocks suspended users on every * authenticated request, and that re-activating a suspended user restores * their ability to log in. * - * Covers FCM row #23 (account status enforcement — UserStatusGuard wiring). * * Requires `docker-compose.test.yml` services to be running (Postgres at 55432, * Redis at 56379, Mailpit SMTP at 51025, Mailpit UI at 58025). * * @layer test - * @see docs/DEVELOPMENT_PLAN.md §Phase 17 P17-7 * @see test/helpers/mailpit.ts */ diff --git a/apps/api/test/tenant-isolation.e2e-spec.ts b/apps/api/test/tenant-isolation.e2e-spec.ts index d83e035..4e2baa6 100644 --- a/apps/api/test/tenant-isolation.e2e-spec.ts +++ b/apps/api/test/tenant-isolation.e2e-spec.ts @@ -1,20 +1,18 @@ import { WsAdapter } from '@nestjs/platform-ws'; /** * @file tenant-isolation.e2e-spec.ts - * @description Phase 17 e2e spec for multi-tenant data isolation. + * @description End-to-end spec for multi-tenant data isolation. * * Verifies that users in different tenants cannot see each other's projects or * user lists. Every query in the application is scoped by `tenantId` — this * spec confirms that wiring at the HTTP layer correctly prevents cross-tenant * data leakage. * - * Covers FCM row #20 (multi-tenant data isolation). * * Requires `docker-compose.test.yml` services to be running (Postgres at 55432, * Redis at 56379, Mailpit SMTP at 51025, Mailpit UI at 58025). * * @layer test - * @see docs/DEVELOPMENT_PLAN.md §Phase 17 P17-7 * @see test/helpers/mailpit.ts */ diff --git a/apps/api/test/throttle-demo.e2e-spec.ts b/apps/api/test/throttle-demo.e2e-spec.ts index 20c0c91..ab712d7 100644 --- a/apps/api/test/throttle-demo.e2e-spec.ts +++ b/apps/api/test/throttle-demo.e2e-spec.ts @@ -1,20 +1,18 @@ import { WsAdapter } from '@nestjs/platform-ws'; /** * @file throttle-demo.e2e-spec.ts - * @description Phase 17 e2e spec for IP-based rate limiting on the throttle-demo + * @description End-to-end spec for IP-based rate limiting on the throttle-demo * endpoint (`GET /api/health/throttle-demo`). * * The endpoint is `@Public()` (no auth required) and applies the `login` * throttle tier: 5 requests per 60 seconds per IP. The 6th request from the * same IP must receive HTTP 429 Too Many Requests. * - * Covers FCM row #17 (IP-based rate limiting / throttle-demo). * * Requires `docker-compose.test.yml` services to be running (Postgres at 55432, * Redis at 56379, Mailpit SMTP at 51025, Mailpit UI at 58025). * * @layer test - * @see docs/DEVELOPMENT_PLAN.md §Phase 17 P17-6 */ // Set test env vars BEFORE importing AppModule so ConfigService sees them. diff --git a/apps/api/test/websocket-auth.e2e-spec.ts b/apps/api/test/websocket-auth.e2e-spec.ts index eb8354e..eb60db2 100644 --- a/apps/api/test/websocket-auth.e2e-spec.ts +++ b/apps/api/test/websocket-auth.e2e-spec.ts @@ -1,6 +1,6 @@ /** * @file websocket-auth.e2e-spec.ts - * @description End-to-end spec for WebSocket authentication (FCM row #24). + * @description End-to-end spec for WebSocket authentication and notifications. * * Proves: * 1. Happy path — a valid dashboard JWT in `Authorization: Bearer` allows @@ -28,7 +28,6 @@ * `WsJwtGuard` from `@bymax-one/nest-auth` is the library-side guard used here. * * @layer test - * @see docs/DEVELOPMENT_PLAN.md §Phase 10 P10-3 * @see apps/api/src/notifications/notifications.gateway.ts */ diff --git a/apps/web/app/api/auth/client-refresh/route.ts b/apps/web/app/api/auth/client-refresh/route.ts index 3824065..cceeba4 100644 --- a/apps/web/app/api/auth/client-refresh/route.ts +++ b/apps/web/app/api/auth/client-refresh/route.ts @@ -5,7 +5,6 @@ * 401 from the API. The handler forwards the refresh cookie to NestJS and sets fresh * cookies on the response so the next request carries valid credentials. * - * @see FCM rows #27 (client-refresh). * @layer api/auth */ diff --git a/apps/web/app/api/auth/logout/route.ts b/apps/web/app/api/auth/logout/route.ts index c4f9d67..5055098 100644 --- a/apps/web/app/api/auth/logout/route.ts +++ b/apps/web/app/api/auth/logout/route.ts @@ -5,7 +5,6 @@ * auth cookies and redirects the browser to the login page. The revocation call to * NestJS blacklists the refresh token in Redis so it cannot be reused. * - * @see FCM rows #28 (logout). * @layer api/auth */ diff --git a/apps/web/app/api/auth/silent-refresh/route.ts b/apps/web/app/api/auth/silent-refresh/route.ts index 4a77c59..a8b02fa 100644 --- a/apps/web/app/api/auth/silent-refresh/route.ts +++ b/apps/web/app/api/auth/silent-refresh/route.ts @@ -9,7 +9,6 @@ * On failure (refresh token absent or revoked), the handler clears auth cookies and * redirects to the login page per the library's documented behaviour. * - * @see FCM rows #27 (silent-refresh). * @layer api/auth */ diff --git a/apps/web/app/auth/register/page.tsx b/apps/web/app/auth/register/page.tsx index 3201f23..f5c5326 100644 --- a/apps/web/app/auth/register/page.tsx +++ b/apps/web/app/auth/register/page.tsx @@ -7,9 +7,8 @@ * - After successful registration: "Check your email" confirmation screen with * a Resend button (60-second client-side cooldown, persisted in sessionStorage) * - * The tenant dropdown is populated with a static list because a public tenants API - * is not yet available. - * TODO(P14): replace the static list with a fetch from `/api/tenants/public`. + * The tenant dropdown uses a static list intentionally — see `lib/tenants.ts` + * for the design rationale and the note on production tenant-discovery patterns. * * @layer pages/auth */ diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index a00f5e5..afe632b 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -9,7 +9,7 @@ * - Monospace typography for headings * * This is a pure server component — no client-side JS required for the - * landing surface. Auth logic starts at Phase 12. + * landing surface. */ import Link from 'next/link'; diff --git a/apps/web/app/platform/(protected)/layout.tsx b/apps/web/app/platform/(protected)/layout.tsx index 5d65156..5386581 100644 --- a/apps/web/app/platform/(protected)/layout.tsx +++ b/apps/web/app/platform/(protected)/layout.tsx @@ -7,8 +7,6 @@ * * Route: `/platform/*` (excluding `/platform/login` which has its own standalone layout). * - * FCM row #22 — Platform admin context. - * * @layer layouts */ diff --git a/apps/web/app/platform/(protected)/tenants/page.tsx b/apps/web/app/platform/(protected)/tenants/page.tsx index c90d1ba..9cb66bc 100644 --- a/apps/web/app/platform/(protected)/tenants/page.tsx +++ b/apps/web/app/platform/(protected)/tenants/page.tsx @@ -4,8 +4,6 @@ * Lists every tenant in the system via `GET /api/platform/tenants`. * Accessible to both `SUPER_ADMIN` and `SUPPORT` roles. * - * FCM row #22 — Platform admin context. - * * @layer pages/platform */ diff --git a/apps/web/app/platform/(protected)/users/page.tsx b/apps/web/app/platform/(protected)/users/page.tsx index 981d1e9..729f2c0 100644 --- a/apps/web/app/platform/(protected)/users/page.tsx +++ b/apps/web/app/platform/(protected)/users/page.tsx @@ -6,8 +6,6 @@ * Selecting a tenant updates the URL via `router.replace` which triggers a re-render * with the new `tenantId`. * - * FCM row #22 — Platform admin context. - * * @layer pages/platform */ diff --git a/apps/web/app/platform/login/page.tsx b/apps/web/app/platform/login/page.tsx index 9c43471..2b18be0 100644 --- a/apps/web/app/platform/login/page.tsx +++ b/apps/web/app/platform/login/page.tsx @@ -9,8 +9,6 @@ * Platform auth and tenant auth are completely separate contexts: a tenant-authenticated * user who navigates to this URL will see the form, not an automatic redirect. * - * FCM row #22 — Platform admin context (`controllers.platform: true`). - * * @layer pages/platform */ diff --git a/apps/web/components/auth/sign-out-button.tsx b/apps/web/components/auth/sign-out-button.tsx index 1dd7acd..3e46100 100644 --- a/apps/web/components/auth/sign-out-button.tsx +++ b/apps/web/components/auth/sign-out-button.tsx @@ -1,12 +1,12 @@ /** - * @fileoverview Sign-out button — posts to the logout route handler and refreshes. + * @file sign-out-button.tsx + * @fileoverview Sign-out button — posts to the logout route handler and redirects. * * Client component that posts to `POST /api/auth/logout` (the `createLogoutHandler` - * endpoint from Phase 12). The handler owns cookie clearing and the redirect to - * `/auth/login`; this component only triggers the request and reflects loading state. + * endpoint). The handler owns cookie clearing and the redirect to `/auth/login`; + * this component only triggers the request and reflects loading state. * - * Used in the dashboard header dropdown (Phase 14). Shipping it here makes Phase 12's - * auth wiring end-to-end testable without the full dashboard being built. + * Can be composed in any layout that needs a sign-out affordance. * * @layer components/auth */ diff --git a/apps/web/components/auth/tenant-switcher.tsx b/apps/web/components/auth/tenant-switcher.tsx index f7bbaab..a9dbf0b 100644 --- a/apps/web/components/auth/tenant-switcher.tsx +++ b/apps/web/components/auth/tenant-switcher.tsx @@ -24,7 +24,6 @@ * so the user completes the destination tenant's MFA challenge through * the canonical password-login flow. * - * Covers FCM row #20 (multi-tenant workspace switching). * * @layer components/auth */ diff --git a/apps/web/components/dashboard/send-test-notification-button.tsx b/apps/web/components/dashboard/send-test-notification-button.tsx index 57c4722..f0931ef 100644 --- a/apps/web/components/dashboard/send-test-notification-button.tsx +++ b/apps/web/components/dashboard/send-test-notification-button.tsx @@ -9,7 +9,6 @@ * into production builds. The underlying endpoint also enforces this server-side. * * @layer components/dashboard - * @see docs/DEVELOPMENT_PLAN.md §Phase 16 P16-3 */ 'use client'; diff --git a/apps/web/components/notifications/notification-listener.tsx b/apps/web/components/notifications/notification-listener.tsx index 53113c9..e66115f 100644 --- a/apps/web/components/notifications/notification-listener.tsx +++ b/apps/web/components/notifications/notification-listener.tsx @@ -15,7 +15,6 @@ * Renders nothing — this component is purely a side-effect host. * * @layer components/notifications - * @see docs/DEVELOPMENT_PLAN.md §Phase 16 P16-2 */ 'use client'; diff --git a/apps/web/components/platform/platform-users-table.tsx b/apps/web/components/platform/platform-users-table.tsx index 4984df3..367aa3a 100644 --- a/apps/web/components/platform/platform-users-table.tsx +++ b/apps/web/components/platform/platform-users-table.tsx @@ -10,8 +10,6 @@ * * Columns: Name · Email · Role · Status · Actions. * - * FCM row #22 — Platform admin context. - * * @layer components/platform */ @@ -57,17 +55,17 @@ interface PlatformUsersTableProps { * * @param tenantId - Tenant to fetch users for (from URL search param). */ -/** - * Static dependency arrays for `useCallback` / `useEffect`. Extracted so - * Stryker disable directives can land on a single-AST-node line — when the - * deps array sits on the closing `}, [...]);` of a hook call, Stryker - * attributes the ArrayDeclaration mutant to the parent hook's start line and - * a `next-line` directive there cannot reach it. - */ -// Hooks reading these arrays still re-fire when their `useCallback`-wrapped -// closure changes, so the underlying dependency contract is unchanged. - export function PlatformUsersTable({ tenantId }: PlatformUsersTableProps) { + /* + * Static dependency arrays for `useCallback` / `useEffect`. Extracted so + * Stryker disable directives can land on a single-AST-node line — when the + * deps array sits on the closing `}, [...]);` of a hook call, Stryker + * attributes the ArrayDeclaration mutant to the parent hook's start line and + * a `next-line` directive there cannot reach it. + * + * Hooks reading these arrays still re-fire when their `useCallback`-wrapped + * closure changes, so the underlying dependency contract is unchanged. + */ const [users, setUsers] = useState([]); // Stryker disable next-line BooleanLiteral: initial `true` is a belt-and-suspenders flag — even if mutated to `false`, the `users.length === 0` empty-state guard later still renders the loading-equivalent empty paragraph until the fetch settles. The two guards cover overlapping failure modes (network-pending vs empty-response). const [isLoading, setIsLoading] = useState(true); diff --git a/apps/web/components/platform/tenants-table.tsx b/apps/web/components/platform/tenants-table.tsx index 2537b0c..38eedeb 100644 --- a/apps/web/components/platform/tenants-table.tsx +++ b/apps/web/components/platform/tenants-table.tsx @@ -8,8 +8,6 @@ * Columns: Name · Slug · Created · Actions. * Empty state, loading skeleton, and error toast are all handled. * - * FCM row #22 — Platform admin context. - * * @layer components/platform */ diff --git a/apps/web/e2e/brute-force-account-locked.spec.ts b/apps/web/e2e/brute-force-account-locked.spec.ts index dfa76fc..5be6125 100644 --- a/apps/web/e2e/brute-force-account-locked.spec.ts +++ b/apps/web/e2e/brute-force-account-locked.spec.ts @@ -1,5 +1,5 @@ /** - * @fileoverview E2E (FCM #38): brute-force protection surfaces `ACCOUNT_LOCKED` + * @fileoverview Brute-force protection end-to-end spec — verifies that `ACCOUNT_LOCKED` * on the login form after the configured attempt threshold. * * The example API is configured with `bruteForce: { maxAttempts: 5, diff --git a/apps/web/e2e/edge-proxy-gating.spec.ts b/apps/web/e2e/edge-proxy-gating.spec.ts index bb32c07..774247a 100644 --- a/apps/web/e2e/edge-proxy-gating.spec.ts +++ b/apps/web/e2e/edge-proxy-gating.spec.ts @@ -1,5 +1,5 @@ /** - * @fileoverview E2E (FCM #40): Next.js edge proxy gating — public routes + * @fileoverview Next.js edge proxy gating end-to-end spec — public routes * vs protected routes vs role-gated routes. * * Verifies the three classes of routes declared in `apps/web/proxy.ts`: diff --git a/apps/web/e2e/fixtures/auth.ts b/apps/web/e2e/fixtures/auth.ts index d6ea2a1..7e757fd 100644 --- a/apps/web/e2e/fixtures/auth.ts +++ b/apps/web/e2e/fixtures/auth.ts @@ -15,7 +15,6 @@ * ``` * * @layer test/e2e/fixtures - * @see docs/DEVELOPMENT_PLAN.md §Phase 17 P17-10 */ import path from 'node:path'; diff --git a/apps/web/e2e/fixtures/mailpit.ts b/apps/web/e2e/fixtures/mailpit.ts index 1f6be44..a1cd996 100644 --- a/apps/web/e2e/fixtures/mailpit.ts +++ b/apps/web/e2e/fixtures/mailpit.ts @@ -5,7 +5,6 @@ * until a matching email arrives (or the timeout elapses). * * @layer test/e2e/fixtures - * @see docs/DEVELOPMENT_PLAN.md §Phase 17 P17-10 */ /** Base URL for the Mailpit API (matches docker-compose.yml dev stack port). */ diff --git a/apps/web/e2e/forgot-password.spec.ts b/apps/web/e2e/forgot-password.spec.ts index ce5ac49..871ccca 100644 --- a/apps/web/e2e/forgot-password.spec.ts +++ b/apps/web/e2e/forgot-password.spec.ts @@ -5,7 +5,6 @@ * Prerequisites: full stack + Mailpit running at http://localhost:58025. * * @layer test/e2e - * @see docs/DEVELOPMENT_PLAN.md §Phase 17 P17-10 */ import { test, expect } from '@playwright/test'; @@ -26,7 +25,7 @@ test.describe('Forgot password flow', () => { page, }) => { /** - * Complete token-based password-reset flow (FCM #6). + * Complete token-based password-reset flow. * Depends on Mailpit capturing the reset email so the token can be extracted. */ // 1. Navigate to forgot-password page. diff --git a/apps/web/e2e/invitations.spec.ts b/apps/web/e2e/invitations.spec.ts index a84ad2c..ecc167b 100644 --- a/apps/web/e2e/invitations.spec.ts +++ b/apps/web/e2e/invitations.spec.ts @@ -6,7 +6,6 @@ * link and completes registration. * * @layer test/e2e - * @see docs/DEVELOPMENT_PLAN.md §Phase 17 P17-10 */ import { test, expect } from '@playwright/test'; @@ -26,7 +25,7 @@ test.describe('Invitation flow', () => { browser, }) => { /** - * Full invitation flow (FCM #21). + * Full invitation flow. * Admin context sends the invite; a new browser context simulates the * invitee opening the invite link from their email client. */ diff --git a/apps/web/e2e/login-happy.spec.ts b/apps/web/e2e/login-happy.spec.ts index 1a8ecfb..74535fb 100644 --- a/apps/web/e2e/login-happy.spec.ts +++ b/apps/web/e2e/login-happy.spec.ts @@ -7,7 +7,6 @@ * Prerequisites: full stack running (`pnpm infra:up` + API + web dev server). * * @layer test/e2e - * @see docs/DEVELOPMENT_PLAN.md §Phase 17 P17-10 */ import { test, expect } from '@playwright/test'; @@ -29,7 +28,7 @@ test.describe('Login — happy path', () => { test('logs in with valid credentials and redirects to /dashboard', async ({ page }) => { /** * Submit canonical seeded member credentials and expect a redirect to /dashboard. - * Protects the register → verify → login → dashboard flow (FCM #2). + * Protects the register → verify → login → dashboard flow. */ await page.getByLabel(/email/i).fill(MEMBER_EMAIL); await page.getByLabel(/password/i).fill(MEMBER_PASSWORD); @@ -41,7 +40,7 @@ test.describe('Login — happy path', () => { test('dashboard renders the authenticated user email or name after login', async ({ page }) => { /** * After a successful login the dashboard must display the user's identity - * (name or email), proving the session is actually populated (FCM #29). + * (name or email), proving the session is actually populated. */ await page.getByLabel(/email/i).fill(MEMBER_EMAIL); await page.getByLabel(/password/i).fill(MEMBER_PASSWORD); diff --git a/apps/web/e2e/login-wrong-password.spec.ts b/apps/web/e2e/login-wrong-password.spec.ts index a536a1c..7383a94 100644 --- a/apps/web/e2e/login-wrong-password.spec.ts +++ b/apps/web/e2e/login-wrong-password.spec.ts @@ -6,7 +6,6 @@ * The page must NOT redirect to `/dashboard` on failure. * * @layer test/e2e - * @see docs/DEVELOPMENT_PLAN.md §Phase 17 P17-10 */ import { test, expect } from '@playwright/test'; @@ -22,7 +21,7 @@ test.describe('Login — wrong password', () => { test('shows an error message and stays on the login page', async ({ page }) => { /** * Wrong password must surface the INVALID_CREDENTIALS message from auth-errors.ts - * and keep the user on the login page (FCM #29, anti-enumeration). + * and keep the user on the login page. */ await page.getByLabel(/email/i).fill(MEMBER_EMAIL); await page.getByLabel(/password/i).fill('definitly-wrong-password-999'); @@ -42,7 +41,7 @@ test.describe('Login — wrong password', () => { }) => { /** * Anti-enumeration: an attacker must not be able to determine whether an - * email exists by observing different error messages (FCM #29). + * email exists by observing different error messages. */ await page.getByLabel(/email/i).fill('nobody@nonexistent.example.test'); await page.getByLabel(/password/i).fill('SomePassword123!'); diff --git a/apps/web/e2e/logout-rbac.spec.ts b/apps/web/e2e/logout-rbac.spec.ts index 72b0169..1e37f66 100644 --- a/apps/web/e2e/logout-rbac.spec.ts +++ b/apps/web/e2e/logout-rbac.spec.ts @@ -1,5 +1,5 @@ /** - * @fileoverview E2E (FCM #39): logout button + RBAC nav visibility. + * @fileoverview Logout button and RBAC navigation visibility end-to-end spec. * * Exercises two concerns that share the same UI surface: * diff --git a/apps/web/e2e/mfa-disable.spec.ts b/apps/web/e2e/mfa-disable.spec.ts index 2046b3a..41ed7ce 100644 --- a/apps/web/e2e/mfa-disable.spec.ts +++ b/apps/web/e2e/mfa-disable.spec.ts @@ -1,5 +1,5 @@ /** - * @fileoverview E2E (FCM #34): MFA disable via the dashboard security UI. + * @fileoverview E2E: MFA disable via the dashboard security UI. * * Mirrors `mfa-enroll-and-login.spec.ts` for the inverse transition — enrolls * MFA inline (so the test is self-contained against a clean seeded user), @@ -67,7 +67,7 @@ test.describe('MFA disable via UI', () => { page, }) => { /** - * Full enroll → disable lifecycle on the dashboard UI (FCM #34, complement + * Full enroll → disable lifecycle on the dashboard UI * to the mfa-disable lib e2e spec). The test enrolls first so it does not * depend on any other spec leaving state behind — Playwright runs files * serially with `workers: 1` but the test is self-contained either way. diff --git a/apps/web/e2e/mfa-enroll-and-login.spec.ts b/apps/web/e2e/mfa-enroll-and-login.spec.ts index e5d581c..882cc31 100644 --- a/apps/web/e2e/mfa-enroll-and-login.spec.ts +++ b/apps/web/e2e/mfa-enroll-and-login.spec.ts @@ -8,7 +8,6 @@ * Uses `otplib` to generate a valid TOTP code from the displayed secret. * * @layer test/e2e - * @see docs/DEVELOPMENT_PLAN.md §Phase 17 P17-10 */ import { test, expect } from '@playwright/test'; @@ -47,7 +46,7 @@ test.describe('MFA enroll and login', () => { page, }) => { /** - * Full MFA enrollment + login challenge flow (FCM #8, #9). + * Full MFA enrollment + login challenge flow. * The TOTP secret is extracted from the QR page so otplib can generate a * valid code without hardcoding any secret. */ diff --git a/apps/web/e2e/mfa-recovery-code-challenge.spec.ts b/apps/web/e2e/mfa-recovery-code-challenge.spec.ts index 0d60141..72d41e5 100644 --- a/apps/web/e2e/mfa-recovery-code-challenge.spec.ts +++ b/apps/web/e2e/mfa-recovery-code-challenge.spec.ts @@ -1,5 +1,5 @@ /** - * @fileoverview E2E (FCM #33): MFA recovery-code path on the challenge UI. + * @fileoverview E2E: MFA recovery-code path on the challenge UI. * * Exercises the failure-mode branch of the MFA flow — when a user has lost * their authenticator app and must fall back to one of the eight recovery @@ -52,7 +52,7 @@ test.describe('MFA recovery code via challenge UI', () => { test('signs in with a recovery code on the MFA challenge page', async ({ page }) => { /** - * Full recovery-code flow (FCM #33, browser layer). Mirrors the lib's + * Full recovery-code flow. Mirrors the lib's * recovery-code service test but at the UI surface — proves the * challenge page exposes the recovery-code branch and that the response * is processed by the same `mfaChallenge` client helper. diff --git a/apps/web/e2e/notifications-isolation.spec.ts b/apps/web/e2e/notifications-isolation.spec.ts index 0e7f5d1..f687b9e 100644 --- a/apps/web/e2e/notifications-isolation.spec.ts +++ b/apps/web/e2e/notifications-isolation.spec.ts @@ -13,7 +13,6 @@ * The tenant_id cookie is set automatically by the TenantSwitcher on dashboard load. * * @layer test/e2e/notifications - * @see docs/DEVELOPMENT_PLAN.md §Phase 16 P16-3 */ import { test, expect, type Browser } from '@playwright/test'; @@ -71,15 +70,16 @@ test.describe('Notifications — per-user isolation', () => { * Scenario: clicking "Send test notification" in Context A (member) fires a * WS notification only to that user's own sockets. Context B (admin, same * tenant) must see no toast after 3 seconds. - * Protects: P16-3 — WsJwtGuard + per-userId socket map prevent cross-user leakage. + * Protects: WsJwtGuard and the per-userId socket map prevent WebSocket + * notifications from leaking across users in the same tenant. * * Skipped in CI/production builds because SendTestNotificationButton returns * null when NODE_ENV=production (it is a dev-only debug helper). The WebSocket - * infrastructure itself is exercised by the API e2e suite (Phase 17). + * infrastructure itself is exercised by the API e2e suite. */ // In CI the web app is pre-built (next build bakes NODE_ENV=production), // which hides SendTestNotificationButton at the component level. - // The WebSocket infrastructure is covered by the API e2e suite (Phase 17). + // The WebSocket infrastructure is covered by the API e2e suite. testInfo.skip( !!process.env['CI'], 'SendTestNotificationButton is hidden in production Next.js builds (dev-only debug helper)', diff --git a/apps/web/e2e/oauth-google-click-through.spec.ts b/apps/web/e2e/oauth-google-click-through.spec.ts index b8f0723..9bae5c2 100644 --- a/apps/web/e2e/oauth-google-click-through.spec.ts +++ b/apps/web/e2e/oauth-google-click-through.spec.ts @@ -1,5 +1,5 @@ /** - * @fileoverview E2E: Google OAuth click-through (FCM #12, browser layer). + * @fileoverview Google OAuth click-through end-to-end spec — browser layer. * * The full OAuth handshake (token exchange + userinfo fetch) is covered * server-side by `apps/api/test/oauth-link.e2e-spec.ts` using the diff --git a/apps/web/e2e/platform-login.spec.ts b/apps/web/e2e/platform-login.spec.ts index 06e84e4..1bc5271 100644 --- a/apps/web/e2e/platform-login.spec.ts +++ b/apps/web/e2e/platform-login.spec.ts @@ -24,7 +24,7 @@ test.describe('Platform login', () => { /** * Happy path: valid credentials redirect to /platform/tenants. - * Protects: P15-1 — submit → platformLogin() → redirect on success. + * Protects: the platform login form authenticates a valid platform admin and navigates to the platform dashboard. */ test('logs in with valid credentials and redirects to /platform/tenants', async ({ page }) => { await page.getByLabel('Email').fill(PLATFORM_EMAIL); @@ -36,7 +36,7 @@ test.describe('Platform login', () => { /** * Wrong password: form stays visible with an error message. - * Protects: P15-1 — translateAuthError renders INVALID_CREDENTIALS. + * Protects: the platform login form surfaces an error message when invalid credentials are submitted and does not redirect. */ test('shows error on wrong password', async ({ page }) => { await page.getByLabel('Email').fill(PLATFORM_EMAIL); diff --git a/apps/web/e2e/platform-shell.spec.ts b/apps/web/e2e/platform-shell.spec.ts index 6318ce3..8c8fddc 100644 --- a/apps/web/e2e/platform-shell.spec.ts +++ b/apps/web/e2e/platform-shell.spec.ts @@ -29,7 +29,7 @@ async function loginAsPlatformAdmin(page: Page): Promise { test.describe('Platform shell', () => { /** * PLATFORM ADMIN badge is visible in the topbar after login. - * Protects: P15-2 — platform-topbar.tsx renders the "PLATFORM ADMIN" label. + * Protects: the platform shell renders the topbar "PLATFORM ADMIN" label for an authenticated platform admin. */ test('shows PLATFORM ADMIN header after login', async ({ page }) => { await loginAsPlatformAdmin(page); @@ -42,7 +42,7 @@ test.describe('Platform shell', () => { /** * Sidebar contains Tenants and Users navigation links. - * Protects: P15-2 — platform-sidebar.tsx nav items present. + * Protects: the platform shell renders the sidebar navigation links for an authenticated platform admin. */ test('renders Tenants and Users sidebar links', async ({ page }) => { await loginAsPlatformAdmin(page); @@ -53,7 +53,7 @@ test.describe('Platform shell', () => { /** * Visiting /platform/tenants without a session redirects to /platform/login. - * Protects: P15-2 — platform-shell.tsx guards bearer token on mount. + * Protects: the platform shell redirects unauthenticated visitors to the platform login page. */ test('redirects to /platform/login when not authenticated', async ({ page }) => { await page.goto('/platform/tenants'); diff --git a/apps/web/e2e/platform-tenants.spec.ts b/apps/web/e2e/platform-tenants.spec.ts index 2e9e43b..1d3abd6 100644 --- a/apps/web/e2e/platform-tenants.spec.ts +++ b/apps/web/e2e/platform-tenants.spec.ts @@ -27,7 +27,7 @@ async function loginAsPlatformAdmin(page: Page): Promise { test.describe('Platform tenants page', () => { /** * Both seeded tenants are listed in the table. - * Protects: P15-3 — listPlatformTenants() returns data; TenantsTable renders rows. + * Protects: the tenants list page fetches and displays all tenants for an authenticated platform admin. */ test('lists the two seeded tenants', async ({ page }) => { await loginAsPlatformAdmin(page); @@ -38,7 +38,7 @@ test.describe('Platform tenants page', () => { /** * Tenant slug badges are shown for both tenants. - * Protects: P15-3 — slug column renders correctly. + * Protects: the tenants list page displays the slug badge for each tenant row. */ test('displays slug badges', async ({ page }) => { await loginAsPlatformAdmin(page); @@ -51,7 +51,7 @@ test.describe('Platform tenants page', () => { /** * Clicking "View users" for Acme Corp navigates to /platform/users?tenantId=. - * Protects: P15-3 — row/action button navigation to the users page works. + * Protects: the tenants list page allows navigation to the users page for a selected tenant. */ test('navigates to users page when View users is clicked', async ({ page }) => { await loginAsPlatformAdmin(page); diff --git a/apps/web/e2e/platform-users-suspend.spec.ts b/apps/web/e2e/platform-users-suspend.spec.ts index cb15f24..ad15c72 100644 --- a/apps/web/e2e/platform-users-suspend.spec.ts +++ b/apps/web/e2e/platform-users-suspend.spec.ts @@ -45,7 +45,7 @@ async function loginAndGoToAcmeUsers(page: Page): Promise { test.describe('Platform users — suspend/unsuspend', () => { /** * Suspend a seeded member and assert the row's status badge flips to "Suspended". - * Protects: P15-4 — platformUpdateUserStatus PATCH call + optimistic update renders. + * Protects: a platform admin can suspend a user and the status badge reflects the change immediately in the users table. */ test('suspends a seeded member and status badge flips to Suspended', async ({ page }) => { await loginAndGoToAcmeUsers(page); @@ -63,7 +63,7 @@ test.describe('Platform users — suspend/unsuspend', () => { /** * Unsuspend the member after suspending, restoring Active status. - * Protects: P15-4 — toggle from SUSPENDED → ACTIVE via Unsuspend button. + * Protects: a platform admin can unsuspend a previously suspended user and the status badge reverts to Active. */ test('unsuspends a suspended member and status badge flips to Active', async ({ page }) => { await loginAndGoToAcmeUsers(page); @@ -88,7 +88,7 @@ test.describe('Platform users — suspend/unsuspend', () => { /** * The current platform admin's row has a disabled Suspend button. - * Protects: P15-4 — self-suspension prevention (getPlatformAdmin().id comparison). + * Protects: a platform admin cannot suspend their own account — the Suspend button is disabled on their own row. */ test('platform admin row has disabled Suspend button (self-suspension prevention)', async ({ page, diff --git a/apps/web/e2e/register-and-verify.spec.ts b/apps/web/e2e/register-and-verify.spec.ts index a312498..fe2fcd2 100644 --- a/apps/web/e2e/register-and-verify.spec.ts +++ b/apps/web/e2e/register-and-verify.spec.ts @@ -1,7 +1,6 @@ /** - * @fileoverview E2E (FCM #1 + #5): registration form + email verification UI. + * @fileoverview E2E: registration form + email verification UI. * - * Closes the two FCM rows that previously had only API-layer coverage * (`apps/api/test/register-and-verify.e2e-spec.ts`) and no Playwright spec. * Walks the full new-user onboarding journey: * @@ -42,7 +41,7 @@ test.describe('Register + verify email', () => { test('registers a fresh account, verifies the OTP, and signs in', async ({ page }) => { /** - * Full onboarding flow at the browser layer (FCM #1 + #5). The + * Full onboarding flow at the browser layer. The * library returns 201 on register and stays silent on the verify * round-trip; this spec proves the UI surfaces both steps correctly * and that the verified flag actually persists (the sign-in at the @@ -68,7 +67,7 @@ test.describe('Register + verify email', () => { await page.getByLabel(/display name/i).fill(name); await page.getByLabel(/password/i).fill(password); - // Tenant dropdown — defaults to acme (FCM #20 maps the seeded slugs). + // Tenant dropdown — defaults to acme. // The dropdown is a `