import { UnauthorizedException } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { Test, TestingModule } from '@nestjs/testing'; import { DateTime } from '../../../common/value-objects/date-time/date-time'; import { Status } from '../../../common/value-objects/status/status'; import type { User } from '../../users/user'; import { UsersService } from '../../users/users.service'; import { RevokedAccessTokensRepository } from '../revoked-access-tokens.repository'; import { JwtStrategy } from './jwt.strategy'; describe('JwtStrategy', () => { let strategy: JwtStrategy; let usersService: jest.Mocked< Pick >; let revoked: jest.Mocked>; const now = DateTime.fromUnixMs(1_700_000_000_000); const user: User = { id: 'user-1', username: 'alice', passwordHash: 'hash', privilegeId: null, isSuperadmin: false, status: Status.create('active'), createdAt: now, updatedAt: now, createdBy: 'user-1', updatedBy: 'user-1', privilege: null, employee: null, createdByUser: { id: 'user-1', username: 'alice' }, updatedByUser: { id: 'user-1', username: 'alice' }, }; beforeEach(async () => { usersService = { findById: jest.fn(), assertCanAuthenticate: jest.fn(), }; revoked = { exists: jest.fn() }; const moduleRef: TestingModule = await Test.createTestingModule({ providers: [ JwtStrategy, { provide: ConfigService, useValue: { getOrThrow: () => 'test-secret-at-least-32-characters-long!!', }, }, { provide: UsersService, useValue: usersService }, { provide: RevokedAccessTokensRepository, useValue: revoked }, ], }).compile(); strategy = moduleRef.get(JwtStrategy); }); it('returns AuthUser for a valid access payload', async () => { revoked.exists.mockResolvedValue(false); usersService.findById.mockResolvedValue(user); await expect( strategy.validate({ sub: 'user-1', username: 'alice', jti: 'jti-1', typ: 'access', }), ).resolves.toEqual({ id: 'user-1', username: 'alice', jti: 'jti-1', isSuperadmin: false, }); }); it('maps isSuperadmin from the persisted user', async () => { revoked.exists.mockResolvedValue(false); usersService.findById.mockResolvedValue({ ...user, isSuperadmin: true }); await expect( strategy.validate({ sub: 'user-1', username: 'alice', jti: 'jti-1', typ: 'access', }), ).resolves.toEqual({ id: 'user-1', username: 'alice', jti: 'jti-1', isSuperadmin: true, }); }); it('rejects revoked access tokens', async () => { revoked.exists.mockResolvedValue(true); await expect( strategy.validate({ sub: 'user-1', username: 'alice', jti: 'jti-1', typ: 'access', }), ).rejects.toBeInstanceOf(UnauthorizedException); }); it('rejects non-access token types', async () => { await expect( strategy.validate({ sub: 'user-1', username: 'alice', jti: 'jti-1', typ: 'refresh' as 'access', }), ).rejects.toBeInstanceOf(UnauthorizedException); }); });