jest.mock("uuid", () => ({ v4: jest.fn(() => "mocked-uuid"), })); import { Test, TestingModule } from "@nestjs/testing"; jest.mock("@noble/post-quantum/ml-kem.js", () => ({ ml_kem768: { keygen: jest.fn(), encapsulate: jest.fn(), decapsulate: jest.fn(), }, })); jest.mock("jose", () => ({ SignJWT: jest.fn(), jwtVerify: jest.fn(), })); import { BadRequestException, UnauthorizedException } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; import { authenticator } from "otplib"; import * as qrcode from "qrcode"; import { HashingService } from "../crypto/services/hashing.service"; import { JwtService } from "../crypto/services/jwt.service"; import { SessionsService } from "../sessions/sessions.service"; import { UsersService } from "../users/users.service"; import { AuthService } from "./auth.service"; jest.mock("otplib"); jest.mock("qrcode"); jest.mock("../users/users.service"); jest.mock("../sessions/sessions.service"); describe("AuthService", () => { let service: AuthService; const mockUsersService = { findOne: jest.fn(), setTwoFactorSecret: jest.fn(), getTwoFactorSecret: jest.fn(), toggleTwoFactor: jest.fn(), create: jest.fn(), findByEmailHash: jest.fn(), findOneWithPrivateData: jest.fn(), }; const mockHashingService = { hashPassword: jest.fn(), hashEmail: jest.fn(), verifyPassword: jest.fn(), }; const mockJwtService = { generateJwt: jest.fn(), }; const mockSessionsService = { createSession: jest.fn(), refreshSession: jest.fn(), }; const mockConfigService = { get: jest.fn(), }; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ AuthService, { provide: UsersService, useValue: mockUsersService }, { provide: HashingService, useValue: mockHashingService }, { provide: JwtService, useValue: mockJwtService }, { provide: SessionsService, useValue: mockSessionsService }, { provide: ConfigService, useValue: mockConfigService }, ], }).compile(); service = module.get(AuthService); }); it("should be defined", () => { expect(service).toBeDefined(); }); describe("generateTwoFactorSecret", () => { it("should generate a 2FA secret", async () => { const userId = "user-id"; const user = { username: "testuser" }; mockUsersService.findOne.mockResolvedValue(user); (authenticator.generateSecret as jest.Mock).mockReturnValue("secret"); (authenticator.keyuri as jest.Mock).mockReturnValue("otpauth://..."); (qrcode.toDataURL as jest.Mock).mockResolvedValue( "data:image/png;base64,...", ); const result = await service.generateTwoFactorSecret(userId); expect(result).toEqual({ secret: "secret", qrCodeDataUrl: "data:image/png;base64,...", }); expect(mockUsersService.setTwoFactorSecret).toHaveBeenCalledWith( userId, "secret", ); }); it("should throw UnauthorizedException if user not found", async () => { mockUsersService.findOne.mockResolvedValue(null); await expect(service.generateTwoFactorSecret("invalid")).rejects.toThrow( UnauthorizedException, ); }); }); describe("enableTwoFactor", () => { it("should enable 2FA", async () => { const userId = "user-id"; const token = "123456"; mockUsersService.getTwoFactorSecret.mockResolvedValue("secret"); (authenticator.verify as jest.Mock).mockReturnValue(true); const result = await service.enableTwoFactor(userId, token); expect(result).toEqual({ message: "2FA enabled successfully" }); expect(mockUsersService.toggleTwoFactor).toHaveBeenCalledWith(userId, true); }); it("should throw BadRequestException if 2FA not initiated", async () => { mockUsersService.getTwoFactorSecret.mockResolvedValue(null); await expect(service.enableTwoFactor("user-id", "token")).rejects.toThrow( BadRequestException, ); }); it("should throw BadRequestException if token is invalid", async () => { mockUsersService.getTwoFactorSecret.mockResolvedValue("secret"); (authenticator.verify as jest.Mock).mockReturnValue(false); await expect(service.enableTwoFactor("user-id", "invalid")).rejects.toThrow( BadRequestException, ); }); }); describe("register", () => { it("should register a user", async () => { const dto = { username: "test", email: "test@example.com", password: "Password1!", }; mockHashingService.hashPassword.mockResolvedValue("hashed-password"); mockHashingService.hashEmail.mockResolvedValue("hashed-email"); mockUsersService.create.mockResolvedValue({ uuid: "new-user-id" }); const result = await service.register(dto); expect(result).toEqual({ message: "User registered successfully", userId: "new-user-id", }); }); }); describe("login", () => { it("should login a user", async () => { const dto = { email: "test@example.com", password: "Password1!" }; const user = { uuid: "user-id", username: "test", passwordHash: "hash", isTwoFactorEnabled: false, }; mockHashingService.hashEmail.mockResolvedValue("hashed-email"); mockUsersService.findByEmailHash.mockResolvedValue(user); mockHashingService.verifyPassword.mockResolvedValue(true); mockJwtService.generateJwt.mockResolvedValue("access-token"); mockSessionsService.createSession.mockResolvedValue({ refreshToken: "refresh-token", }); const result = await service.login(dto); expect(result).toEqual({ message: "User logged in successfully", access_token: "access-token", refresh_token: "refresh-token", }); }); it("should return requires2FA if 2FA is enabled", async () => { const dto = { email: "test@example.com", password: "password" }; const user = { uuid: "user-id", username: "test", passwordHash: "hash", isTwoFactorEnabled: true, }; mockHashingService.hashEmail.mockResolvedValue("hashed-email"); mockUsersService.findByEmailHash.mockResolvedValue(user); mockHashingService.verifyPassword.mockResolvedValue(true); const result = await service.login(dto); expect(result).toEqual({ message: "2FA required", requires2FA: true, userId: "user-id", }); }); it("should throw UnauthorizedException for invalid credentials", async () => { mockUsersService.findByEmailHash.mockResolvedValue(null); await expect(service.login({ email: "x", password: "y" })).rejects.toThrow( UnauthorizedException, ); }); }); describe("verifyTwoFactorLogin", () => { it("should verify 2FA login", async () => { const userId = "user-id"; const token = "123456"; const user = { uuid: userId, username: "test", isTwoFactorEnabled: true }; mockUsersService.findOneWithPrivateData.mockResolvedValue(user); mockUsersService.getTwoFactorSecret.mockResolvedValue("secret"); (authenticator.verify as jest.Mock).mockReturnValue(true); mockJwtService.generateJwt.mockResolvedValue("access-token"); mockSessionsService.createSession.mockResolvedValue({ refreshToken: "refresh-token", }); const result = await service.verifyTwoFactorLogin(userId, token); expect(result).toEqual({ message: "User logged in successfully (2FA)", access_token: "access-token", refresh_token: "refresh-token", }); }); }); describe("refresh", () => { it("should refresh tokens", async () => { const refreshToken = "old-refresh"; const session = { userId: "user-id", refreshToken: "new-refresh" }; const user = { uuid: "user-id", username: "test" }; mockSessionsService.refreshSession.mockResolvedValue(session); mockUsersService.findOne.mockResolvedValue(user); mockJwtService.generateJwt.mockResolvedValue("new-access"); const result = await service.refresh(refreshToken); expect(result).toEqual({ access_token: "new-access", refresh_token: "new-refresh", }); }); }); });