Files
stack/apps/api/src/stitcher/stitcher.security.spec.ts
Jason Woltje 10b49c4afb
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/pr/woodpecker Pipeline failed
fix(tests): Resolve pipeline #243 test failures
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>
2026-02-06 12:15:21 -06:00

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);
});
});
});