import { Test } from '@nestjs/testing'; import { DiscoveryModule, DiscoveryService, MetadataScanner, Reflector, } from '@nestjs/core'; import { PATH_METADATA } from '@nestjs/common/constants'; import { AppModule } from '../src/app.module'; import { IS_PUBLIC_KEY } from '../src/auth/decorators/public.decorator'; import { REQUIRE_PERMISSION_KEY } from '../src/auth/decorators/require-permission.decorator'; import { ANY_AUTHENTICATED_USER_KEY } from '../src/auth/decorators/any-authenticated-user.decorator'; import { PERMISSION_CATALOG } from '../src/auth/permissions/permission-catalog'; /** * Mirrors PermissionsGuard's own runtime contract exactly (see * src/auth/guards/permissions.guard.ts): every non-public route must carry * either @RequirePermission or @AnyAuthenticatedUser, or the guard denies it * at runtime regardless of what this test says. This test exists to catch * the mistake at build time - with a clear message pointing at the missing * decorator - rather than as a live production 403 discovered by a user, but * a route that fails it would also fail closed live, not open. */ describe('Authorization coverage (e2e)', () => { it('every non-public route requires a permission or an explicit any-authenticated-user opt-in', async () => { const moduleRef = await Test.createTestingModule({ imports: [AppModule, DiscoveryModule], }).compile(); const discovery = moduleRef.get(DiscoveryService); const reflector = moduleRef.get(Reflector); const scanner = moduleRef.get(MetadataScanner); const catalogKeys = new Set( PERMISSION_CATALOG.map((entry) => entry.key), ); const uncoveredRoutes: string[] = []; const invalidPermissionKeys: string[] = []; for (const wrapper of discovery.getControllers()) { const instance = wrapper.instance as object | undefined; if (!instance) { continue; } const constructor = instance.constructor; const controllerName = constructor.name; const prototype = Object.getPrototypeOf(instance) as object; const isPublicClass = reflector.get(IS_PUBLIC_KEY, constructor) === true; for (const methodName of scanner.getAllMethodNames(prototype)) { const handler = (prototype as Record unknown>)[ methodName ]; const isRouteHandler = Reflect.getMetadata(PATH_METADATA, handler) !== undefined; if (!isRouteHandler) { continue; } const routeKey = `${controllerName}.${methodName}`; const isPublicMethod = reflector.get(IS_PUBLIC_KEY, handler) === true; if (isPublicClass || isPublicMethod) { continue; } const permissions = reflector.getAllAndOverride( REQUIRE_PERMISSION_KEY, [handler, constructor], ); const hasPermissions = Array.isArray(permissions) && permissions.length > 0; const isAnyAuthenticatedUser = reflector.getAllAndOverride(ANY_AUTHENTICATED_USER_KEY, [ handler, constructor, ]) === true; if (!hasPermissions && !isAnyAuthenticatedUser) { uncoveredRoutes.push(routeKey); } if (hasPermissions) { for (const key of permissions) { if (!catalogKeys.has(key)) { invalidPermissionKeys.push(`${routeKey} -> ${key}`); } } } } } expect(uncoveredRoutes).toEqual([]); expect(invalidPermissionKeys).toEqual([]); await moduleRef.close(); }); });