import { describe, expect, test } from "vitest"; import { openApiDocument } from "../apps/api/src/openapi.js"; import { renderOpenApiSecurityGuardResult, validateOpenApiSecurity, type OpenApiDocument } from "./openapi-guard.js"; function cloneDocument(): OpenApiDocument { return structuredClone(openApiDocument) as OpenApiDocument; } function operation(document: OpenApiDocument, path: string, method: string) { const pathItem = document.paths?.[path]; if (!pathItem) throw new Error(`Missing fixture path ${path}`); const candidate = pathItem[method]; if (typeof candidate !== "object" || candidate === null || Array.isArray(candidate)) { throw new Error(`Missing fixture operation ${method.toUpperCase()} ${path}`); } return candidate as Record; } describe("openapi security guard", () => { test("passes the current OpenAPI document without mutating it", () => { const document = cloneDocument(); const before = JSON.stringify(document); const result = validateOpenApiSecurity(document); expect(result).toEqual({ ok: true, violations: [] }); expect(JSON.stringify(document)).toEqual(before); expect(renderOpenApiSecurityGuardResult(result)).toMatch(/^PASS openapi-security/); }); test("fails when an operation references an undeclared security scheme", () => { const document = cloneDocument(); operation(document, "/v1/session/current-user", "get").security = [{ missingScheme: [] }]; const result = validateOpenApiSecurity(document); expect(result.ok).toBe(false); expect(result.violations).toContain("GET /v1/session/current-user getCurrentUserSession: references undeclared security scheme 'missingScheme'"); }); test("fails when a protected operation omits non-empty security", () => { const document = cloneDocument(); delete operation(document, "/v1/session/current-user", "get").security; const result = validateOpenApiSecurity(document); expect(result.ok).toBe(false); expect(result.violations).toContain("GET /v1/session/current-user getCurrentUserSession: protected operation must declare non-empty security"); }); test("fails when the document has no operation surface", () => { const result = validateOpenApiSecurity({ components: { securitySchemes: { bearerAuth: {}, apiKeyHeader: {} } }, paths: {} }); expect(result.ok).toBe(false); expect(result.violations).toContain("OpenAPI document must define at least one path"); expect(result.violations).toContain("OpenAPI document must define at least one operation"); }); test("fails when required security schemes are missing", () => { const document = cloneDocument(); document.components = { securitySchemes: {} }; const result = validateOpenApiSecurity(document); expect(result.ok).toBe(false); expect(result.violations).toContain("OpenAPI document must declare security scheme 'bearerAuth'"); expect(result.violations).toContain("OpenAPI document must declare security scheme 'apiKeyHeader'"); }); test("enforces public and optional-anonymous allowlist behavior", () => { const publicDocument = cloneDocument(); operation(publicDocument, "/healthz", "get").security = [{ bearerAuth: [] }]; expect(validateOpenApiSecurity(publicDocument).violations).toContain( "GET /healthz getHealthz: public allowlisted operation must omit security or declare security: []" ); const optionalDocument = cloneDocument(); expect(validateOpenApiSecurity(optionalDocument).ok).toBe(true); const anonymousProtectedDocument = cloneDocument(); operation(anonymousProtectedDocument, "/v1/session/current-user", "get").security = [{ bearerAuth: [] }, {}]; expect(validateOpenApiSecurity(anonymousProtectedDocument).violations).toContain( "GET /v1/session/current-user getCurrentUserSession: anonymous security requirement is only allowed for optional-auth allowlisted operations" ); }); test("keeps the global analytics overview public without making the ingest token a read credential", () => { const document = cloneDocument(); operation(document, "/v1/analytics/public", "get").security = [{ analyticsToken: [] }]; expect(validateOpenApiSecurity(document).violations).toContain( "GET /v1/analytics/public getPublicAnalyticsOverview: public allowlisted operation must omit security or declare security: []" ); }); test("documents Aircall body-token authentication without inventing a header security scheme", () => { const document = cloneDocument(); expect(validateOpenApiSecurity(document).ok).toBe(true); operation(document, "/v1/webhooks/telephony/aircall", "post").security = [{ bearerAuth: [] }]; expect(validateOpenApiSecurity(document).violations).toContain( "POST /v1/webhooks/telephony/aircall receiveAircallTelephonyWebhook: request-body credential operation must omit security or declare security: []" ); operation(document, "/v1/webhooks/telephony/aircall", "post").security = []; operation(document, "/v1/webhooks/admin-telephony/aircall", "post").security = [{ bearerAuth: [] }]; expect(validateOpenApiSecurity(document).violations).toContain( "POST /v1/webhooks/admin-telephony/aircall receiveAdminAircallTelephonyWebhook: request-body credential operation must omit security or declare security: []" ); }); });