import { describe, it, expect, beforeEach, vi } from "vitest"; import { Test, TestingModule } from "@nestjs/testing"; import { UnauthorizedException } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; import { StitcherController } from "./stitcher.controller"; import { StitcherService } from "./stitcher.service"; import { ApiKeyGuard } from "../common/guards/api-key.guard"; /** * Security tests for StitcherController * * These tests verify that all stitcher endpoints require authentication * and reject requests without valid API keys. */ describe("StitcherController - Security", () => { let controller: StitcherController; let guard: ApiKeyGuard; const mockService = { handleWebhook: vi.fn(), dispatchJob: vi.fn(), }; const mockConfigService = { get: vi.fn().mockReturnValue("test-api-key-12345"), }; beforeEach(async () => { vi.clearAllMocks(); const module: TestingModule = await Test.createTestingModule({ controllers: [StitcherController], providers: [ { provide: StitcherService, useValue: mockService }, { provide: ConfigService, useValue: mockConfigService }, ApiKeyGuard, ], }).compile(); controller = module.get(StitcherController); guard = module.get(ApiKeyGuard); }); describe("Authentication Requirements", () => { it("should have ApiKeyGuard applied to controller", () => { const guards = Reflect.getMetadata("__guards__", StitcherController); expect(guards).toBeDefined(); expect(guards).toContain(ApiKeyGuard); }); it("POST /stitcher/webhook should require authentication", () => { const mockContext = { switchToHttp: () => ({ getRequest: () => ({ headers: {} }), }), }; expect(() => guard.canActivate(mockContext as never)).toThrow(UnauthorizedException); }); it("POST /stitcher/dispatch should require authentication", () => { const mockContext = { switchToHttp: () => ({ getRequest: () => ({ headers: {} }), }), }; expect(() => guard.canActivate(mockContext as never)).toThrow(UnauthorizedException); }); }); describe("Valid Authentication", () => { it("should allow requests with valid API key", () => { const mockContext = { switchToHttp: () => ({ getRequest: () => ({ headers: { "x-api-key": "test-api-key-12345" }, }), }), }; const result = guard.canActivate(mockContext as never); expect(result).toBe(true); }); it("should reject requests with invalid API key", () => { const mockContext = { switchToHttp: () => ({ getRequest: () => ({ headers: { "x-api-key": "wrong-api-key" }, }), }), }; expect(() => guard.canActivate(mockContext as never)).toThrow(UnauthorizedException); expect(() => guard.canActivate(mockContext as never)).toThrow("Invalid API key"); }); it("should reject requests with empty API key", () => { const mockContext = { switchToHttp: () => ({ getRequest: () => ({ headers: { "x-api-key": "" }, }), }), }; expect(() => guard.canActivate(mockContext as never)).toThrow(UnauthorizedException); expect(() => guard.canActivate(mockContext as never)).toThrow("No API key provided"); }); }); describe("Webhook Security", () => { it("should prevent unauthorized webhook submissions", () => { const mockContext = { switchToHttp: () => ({ getRequest: () => ({ headers: {}, body: { issueNumber: "42", repository: "malicious/repo", action: "assigned", }, }), }), }; expect(() => guard.canActivate(mockContext as never)).toThrow(UnauthorizedException); }); }); });