// Mock 'jose' module before importing JwtAuthGuard so Jest doesn't try to parse ESM from node_modules let mockJwtVerify: (token: any) => Promise = async () => { throw new Error('no jwks') }; jest.mock('jose', () => ({ createRemoteJWKSet: () => ({}), jwtVerify: async (token: any, jwks: any, opts: any) => mockJwtVerify(token), })); import { JwtAuthGuard } from '../../src/core/auth/guards/jwt-auth.guard'; import { ExecutionContext } from '@nestjs/common'; import * as jwt from 'jsonwebtoken'; // These tests focus on identitySource being set based on verification path. describe('JwtAuthGuard', () => { let guard: JwtAuthGuard; beforeEach(() => { guard = new JwtAuthGuard(); process.env.RAYLAB_JWT_SECRET = 'test-secret'; delete process.env.AUTHENTIK_JWKS_URI; // ensure JWKS not attempted unless test sets it // reset mock behavior mockJwtVerify = async () => { throw new Error('no jwks') }; }); function makeCtxWithAuthHeader(token: string) { const req: any = { headers: { authorization: `Bearer ${token}` }, cookies: {} }; const ctx: any = { switchToHttp: () => ({ getRequest: () => req }) } as ExecutionContext; return { ctx, req }; } test('internal verification sets identitySource=internal', async () => { const token = jwt.sign({ sub: 'uid', preferred_username: 'u', email: 'e' }, process.env.RAYLAB_JWT_SECRET || 'test-secret'); const { ctx, req } = makeCtxWithAuthHeader(token); const res = await guard.canActivate(ctx); expect(res).toBe(true); expect(req.identity).toBeDefined(); expect((req.identitySource)).toBe('internal'); }); test('external verification sets identitySource=external if JWKS verifies', async () => { process.env.AUTHENTIK_JWKS_URI = 'https://example.com/.well-known/jwks.json'; // set mock to succeed mockJwtVerify = async (token: any) => ({ payload: { sub: 'extsub', preferred_username: 'eu', email: 'ee' } }); const token = 'dummy'; const { ctx, req } = makeCtxWithAuthHeader(token); const res = await guard.canActivate(ctx); expect(res).toBe(true); expect(req.identity).toBeDefined(); expect((req.identitySource)).toBe('external'); }); test('external verification fails then internal succeeds -> identitySource=internal', async () => { process.env.AUTHENTIK_JWKS_URI = 'https://example.com/.well-known/jwks.json'; // make jwks fail mockJwtVerify = async (token: any) => { throw new Error('jwks fail'); }; const token = jwt.sign({ sub: 'uid2', preferred_username: 'u2', email: 'e2' }, process.env.RAYLAB_JWT_SECRET || 'test-secret'); const { ctx, req } = makeCtxWithAuthHeader(token); const res = await guard.canActivate(ctx); expect(res).toBe(true); expect(req.identity).toBeDefined(); expect((req.identitySource)).toBe('internal'); }); });