test: add comprehensive unit tests for core services

Added unit tests for the `api-keys`, `auth`, `categories`, `contents`, `favorites`, `media`, and `purge` services to improve test coverage and ensure core functionality integrity.
This commit is contained in:
Mathis HERRIOT
2026-01-08 16:22:23 +01:00
parent cc2823db7d
commit 399bdab86c
13 changed files with 1502 additions and 0 deletions

View File

@@ -0,0 +1,253 @@
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 { CryptoService } from "../crypto/crypto.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("../crypto/crypto.service");
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 mockCryptoService = {
hashPassword: jest.fn(),
hashEmail: jest.fn(),
verifyPassword: jest.fn(),
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: CryptoService, useValue: mockCryptoService },
{ provide: SessionsService, useValue: mockSessionsService },
{ provide: ConfigService, useValue: mockConfigService },
],
}).compile();
service = module.get<AuthService>(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: "password",
};
mockCryptoService.hashPassword.mockResolvedValue("hashed-password");
mockCryptoService.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: "password" };
const user = {
uuid: "user-id",
username: "test",
passwordHash: "hash",
isTwoFactorEnabled: false,
};
mockCryptoService.hashEmail.mockResolvedValue("hashed-email");
mockUsersService.findByEmailHash.mockResolvedValue(user);
mockCryptoService.verifyPassword.mockResolvedValue(true);
mockCryptoService.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,
};
mockCryptoService.hashEmail.mockResolvedValue("hashed-email");
mockUsersService.findByEmailHash.mockResolvedValue(user);
mockCryptoService.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);
mockCryptoService.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);
mockCryptoService.generateJwt.mockResolvedValue("new-access");
const result = await service.refresh(refreshToken);
expect(result).toEqual({
access_token: "new-access",
refresh_token: "new-refresh",
});
});
});
});

View File

@@ -0,0 +1,69 @@
import { Test, TestingModule } from "@nestjs/testing";
import { DatabaseService } from "../database/database.service";
import { RbacService } from "./rbac.service";
describe("RbacService", () => {
let service: RbacService;
const mockDb = {
select: jest.fn().mockReturnThis(),
from: jest.fn().mockReturnThis(),
innerJoin: jest.fn().mockReturnThis(),
where: jest.fn(),
};
beforeEach(async () => {
jest.clearAllMocks();
const module: TestingModule = await Test.createTestingModule({
providers: [
RbacService,
{
provide: DatabaseService,
useValue: {
db: mockDb,
},
},
],
}).compile();
service = module.get<RbacService>(RbacService);
});
it("should be defined", () => {
expect(service).toBeDefined();
});
describe("getUserRoles", () => {
it("should return user roles", async () => {
const userId = "user-id";
const mockRoles = [{ slug: "admin" }, { slug: "user" }];
mockDb.where.mockResolvedValue(mockRoles);
const result = await service.getUserRoles(userId);
expect(result).toEqual(["admin", "user"]);
expect(mockDb.select).toHaveBeenCalled();
expect(mockDb.from).toHaveBeenCalled();
expect(mockDb.innerJoin).toHaveBeenCalled();
});
});
describe("getUserPermissions", () => {
it("should return unique user permissions", async () => {
const userId = "user-id";
const mockPermissions = [
{ slug: "read" },
{ slug: "write" },
{ slug: "read" }, // Duplicate
];
mockDb.where.mockResolvedValue(mockPermissions);
const result = await service.getUserPermissions(userId);
expect(result).toEqual(["read", "write"]);
expect(mockDb.select).toHaveBeenCalled();
expect(mockDb.from).toHaveBeenCalled();
expect(mockDb.innerJoin).toHaveBeenCalledTimes(2);
});
});
});