Track HiveGrid Cloud planning reference
This commit is contained in:
1 parent
b3d5ea6764
commit
e341c5938a
5784 files changed
+1104929
-3
No files matched your search
@@ -0,0 +1,109 @@
|
||||
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<string, unknown>;
|
||||
}
|
||||
|
||||
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("fails when scoped apiKeyHeader operations omit permission metadata", () => {
|
||||
const document = cloneDocument();
|
||||
delete operation(document, "/v1/entities/{entity}/rows", "get")["x-api-key-permissions"];
|
||||
|
||||
const result = validateOpenApiSecurity(document);
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.violations).toContain("GET /v1/entities/{entity}/rows listLegacyEntityRows: scoped apiKeyHeader operation must include x-api-key-permissions");
|
||||
});
|
||||
|
||||
test("fails when service-token metadata operations include scoped permission metadata", () => {
|
||||
const document = cloneDocument();
|
||||
operation(document, "/v1/entities/{entity}", "get")["x-api-key-permissions"] = { credential: "apiKeyHeader" };
|
||||
|
||||
const result = validateOpenApiSecurity(document);
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.violations).toContain("GET /v1/entities/{entity} getLegacyEntityByName: service-token metadata operation must not include x-api-key-permissions");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user