Fixed 27 test failures by addressing several categories of issues: Security spec tests (coordinator-integration, stitcher): - Changed async test assertions to synchronous since ApiKeyGuard.canActivate is synchronous and throws directly rather than returning rejected promises - Use expect(() => fn()).toThrow() instead of await expect(fn()).rejects.toThrow() Federation controller tests: - Added CsrfGuard and WorkspaceGuard mock overrides to test module - Set DEFAULT_WORKSPACE_ID environment variable for handleIncomingConnection tests - Added proper afterEach cleanup for environment variable restoration Federation service tests: - Updated RSA key generation tests to use Vitest 4.x timeout syntax (second argument as options object, not third argument) Prisma service tests: - Replaced vi.spyOn for $transaction and setWorkspaceContext with direct method assignment to avoid spy restoration issues - Added vi.clearAllMocks() in afterEach to properly reset between tests Integration tests (job-events, fulltext-search): - Added conditional skip when DATABASE_URL is not set to prevent failures in environments without database access Remaining 7 failures are pre-existing fulltext-search integration tests that require specific PostgreSQL triggers not present in test database. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
132 lines
3.9 KiB
TypeScript
132 lines
3.9 KiB
TypeScript
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>(StitcherController);
|
|
guard = module.get<ApiKeyGuard>(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);
|
|
});
|
|
});
|
|
});
|