From 4c12c5c5cbc49f85fbb1c1f3fd1d2b20a1252acf Mon Sep 17 00:00:00 2001 From: Mathis HERRIOT <197931332+0x485254@users.noreply.github.com> Date: Tue, 20 Jan 2026 13:45:27 +0100 Subject: [PATCH] test(api-keys): add unit tests for ApiKeysController and ApiKeysRepository with mocked dependencies --- .../src/api-keys/api-keys.controller.spec.ts | 95 +++++++++++++++++++ .../repositories/api-keys.repository.spec.ts | 79 +++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 backend/src/api-keys/api-keys.controller.spec.ts create mode 100644 backend/src/api-keys/repositories/api-keys.repository.spec.ts diff --git a/backend/src/api-keys/api-keys.controller.spec.ts b/backend/src/api-keys/api-keys.controller.spec.ts new file mode 100644 index 0000000..1b71aa1 --- /dev/null +++ b/backend/src/api-keys/api-keys.controller.spec.ts @@ -0,0 +1,95 @@ +jest.mock("uuid", () => ({ + v4: jest.fn(() => "mocked-uuid"), +})); + +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().mockReturnValue({ + setProtectedHeader: jest.fn().mockReturnThis(), + setIssuedAt: jest.fn().mockReturnThis(), + setExpirationTime: jest.fn().mockReturnThis(), + sign: jest.fn().mockResolvedValue("mocked-jwt"), + }), + jwtVerify: jest.fn(), +})); + +import { Test, TestingModule } from "@nestjs/testing"; +import { AuthGuard } from "../auth/guards/auth.guard"; +import { AuthenticatedRequest } from "../common/interfaces/request.interface"; +import { ApiKeysController } from "./api-keys.controller"; +import { ApiKeysService } from "./api-keys.service"; + +describe("ApiKeysController", () => { + let controller: ApiKeysController; + let service: ApiKeysService; + + const mockApiKeysService = { + create: jest.fn(), + findAll: jest.fn(), + revoke: jest.fn(), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [ApiKeysController], + providers: [{ provide: ApiKeysService, useValue: mockApiKeysService }], + }) + .overrideGuard(AuthGuard) + .useValue({ canActivate: () => true }) + .compile(); + + controller = module.get(ApiKeysController); + service = module.get(ApiKeysService); + }); + + it("should be defined", () => { + expect(controller).toBeDefined(); + }); + + describe("create", () => { + it("should call service.create", async () => { + const req = { user: { sub: "user-uuid" } } as AuthenticatedRequest; + const dto = { name: "Key Name", expiresAt: "2026-01-20T12:00:00Z" }; + await controller.create(req, dto); + expect(service.create).toHaveBeenCalledWith( + "user-uuid", + "Key Name", + new Date(dto.expiresAt), + ); + }); + + it("should call service.create without expiresAt", async () => { + const req = { user: { sub: "user-uuid" } } as AuthenticatedRequest; + const dto = { name: "Key Name" }; + await controller.create(req, dto); + expect(service.create).toHaveBeenCalledWith( + "user-uuid", + "Key Name", + undefined, + ); + }); + }); + + describe("findAll", () => { + it("should call service.findAll", async () => { + const req = { user: { sub: "user-uuid" } } as AuthenticatedRequest; + await controller.findAll(req); + expect(service.findAll).toHaveBeenCalledWith("user-uuid"); + }); + }); + + describe("revoke", () => { + it("should call service.revoke", async () => { + const req = { user: { sub: "user-uuid" } } as AuthenticatedRequest; + await controller.revoke(req, "key-id"); + expect(service.revoke).toHaveBeenCalledWith("user-uuid", "key-id"); + }); + }); +}); diff --git a/backend/src/api-keys/repositories/api-keys.repository.spec.ts b/backend/src/api-keys/repositories/api-keys.repository.spec.ts new file mode 100644 index 0000000..c4837e9 --- /dev/null +++ b/backend/src/api-keys/repositories/api-keys.repository.spec.ts @@ -0,0 +1,79 @@ +import { Test, TestingModule } from "@nestjs/testing"; +import { DatabaseService } from "../../database/database.service"; +import { ApiKeysRepository } from "./api-keys.repository"; + +describe("ApiKeysRepository", () => { + let repository: ApiKeysRepository; + let _databaseService: DatabaseService; + + const mockDb = { + insert: jest.fn().mockReturnThis(), + values: jest.fn().mockReturnThis(), + select: jest.fn().mockReturnThis(), + from: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + update: jest.fn().mockReturnThis(), + set: jest.fn().mockReturnThis(), + returning: jest.fn().mockReturnThis(), + limit: jest.fn().mockReturnThis(), + execute: jest.fn(), + }; + + const wrapWithThen = (obj: any) => { + obj.then = function (onFulfilled: any) { + const result = this.execute(); + return Promise.resolve(result).then(onFulfilled); + }; + return obj; + }; + wrapWithThen(mockDb); + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + ApiKeysRepository, + { provide: DatabaseService, useValue: { db: mockDb } }, + ], + }).compile(); + + repository = module.get(ApiKeysRepository); + _databaseService = module.get(DatabaseService); + }); + + it("should create an api key", async () => { + (mockDb.execute as jest.Mock).mockResolvedValue([{ id: "1" }]); + await repository.create({ + userId: "u1", + name: "n", + prefix: "p", + keyHash: "h", + }); + expect(mockDb.insert).toHaveBeenCalled(); + }); + + it("should find all keys for user", async () => { + (mockDb.execute as jest.Mock).mockResolvedValue([{ id: "1" }]); + const result = await repository.findAll("u1"); + expect(result).toHaveLength(1); + }); + + it("should revoke a key", async () => { + (mockDb.execute as jest.Mock).mockResolvedValue([ + { id: "1", isActive: false }, + ]); + const result = await repository.revoke("u1", "k1"); + expect(result[0].isActive).toBe(false); + }); + + it("should find active by hash", async () => { + (mockDb.execute as jest.Mock).mockResolvedValue([{ id: "1" }]); + const result = await repository.findActiveByKeyHash("h"); + expect(result.id).toBe("1"); + }); + + it("should update last used", async () => { + (mockDb.execute as jest.Mock).mockResolvedValue([{ id: "1" }]); + await repository.updateLastUsed("1"); + expect(mockDb.update).toHaveBeenCalled(); + }); +});