154 lines
7.7 KiB
JavaScript
154 lines
7.7 KiB
JavaScript
import { mkdtemp, mkdir, writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
failureCount,
|
|
gates,
|
|
impersonationScopeImpersonationScopeInventoryPatterns,
|
|
impersonationScopeImpersonationScopeInventoryScanRoots,
|
|
renderReport,
|
|
reportPath,
|
|
runImpersonationScopeImpersonationScopeInventory,
|
|
} from "./verify-impersonation-scope-baseline.mjs";
|
|
|
|
const forbiddenCommandPattern = /playwright|cypress|browser|e2e/i;
|
|
|
|
describe("Impersonation Scope baseline runner configuration", () => {
|
|
it("defines the required non-E2E gates and report path", () => {
|
|
expect(reportPath).toMatch(/\.tc\/baselines\/impersonation-scope\/baseline-health\.md$/);
|
|
expect(gates.map((gate) => gate.id)).toEqual(["typecheck", "lint", "ci-baseline", "impersonationScope-impersonation-scope-inventory"]);
|
|
expect(gates.some((gate) => [gate.command, ...(gate.args ?? [])].some((arg) => forbiddenCommandPattern.test(arg)))).toBe(false);
|
|
expect(impersonationScopeImpersonationScopeInventoryScanRoots).toEqual(["apps/web/app", "prisma/schema", "docs/security", "scripts", "package.json"]);
|
|
});
|
|
|
|
it("defines impersonation scope inventory term buckets", () => {
|
|
expect(impersonationScopeImpersonationScopeInventoryPatterns.map(({ id }) => id)).toEqual([
|
|
"impersonation",
|
|
"ttl",
|
|
"step-up",
|
|
"mfa",
|
|
"audit",
|
|
"destructive-confirm",
|
|
"session-actor",
|
|
"ui-marker",
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe("Impersonation Scope baseline runner failure counting", () => {
|
|
it("returns zero for clean exits and read-only inventory gates", () => {
|
|
expect(failureCount("typecheck", "warning only", "", 0)).toBe(0);
|
|
expect(failureCount("impersonationScope-impersonation-scope-inventory", "total matches: 12", "", 1)).toBe(0);
|
|
});
|
|
|
|
it("counts failed shell diagnostics conservatively", () => {
|
|
expect(failureCount("typecheck", "src/a.ts(1,1): error TS2307: Cannot find module", "", 1)).toBe(1);
|
|
expect(failureCount("lint", "✖ 4 problems (3 errors, 1 warning)", "", 1)).toBe(3);
|
|
expect(failureCount("ci-baseline", "Summary: 4 passed, 0 observed, 2 failed", "", 1)).toBe(2);
|
|
expect(failureCount("ci-baseline", "CI baseline crashed", "", 1)).toBe(1);
|
|
expect(failureCount("lint", "malformed lint failure", "", 1)).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe("Impersonation Scope impersonation scope inventory scan", () => {
|
|
it("recognizes required impersonation inventory roots and session-related matches in the repository", async () => {
|
|
const inventory = await runImpersonationScopeImpersonationScopeInventory(process.cwd());
|
|
const allMatches = inventory.matches.join("\n");
|
|
|
|
expect(inventory.roots).toEqual(expect.arrayContaining(["apps/web/app", "prisma/schema", "docs/security"]));
|
|
expect(inventory.counts.impersonation + inventory.counts.audit + inventory.counts["session-actor"] + inventory.counts["ui-marker"]).toBeGreaterThan(0);
|
|
expect(allMatches).toMatch(/app\//);
|
|
});
|
|
|
|
it("scans configured roots, counts impersonation terms, and ignores dependency/generated/TC paths", async () => {
|
|
const root = await mkdtemp(join(tmpdir(), "impersonationScope-baseline-inventory-"));
|
|
await mkdir(join(root, "apps/web/app"), { recursive: true });
|
|
await mkdir(join(root, "prisma", "schema"), { recursive: true });
|
|
await mkdir(join(root, "docs", "security"), { recursive: true });
|
|
await mkdir(join(root, "scripts", "node_modules", "ignored"), { recursive: true });
|
|
await mkdir(join(root, "scripts", ".tc"), { recursive: true });
|
|
await mkdir(join(root, "scripts", "generated"), { recursive: true });
|
|
|
|
await writeFile(join(root, "apps/web/app", "auth.ts"), "impersonation TTL step-up MFA audit destructive confirm session actor UI marker", "utf8");
|
|
await writeFile(join(root, "prisma", "schema", "auth.prisma"), "impersonatingFromUserId fromUser toUser audit log max age", "utf8");
|
|
await writeFile(join(root, "docs", "security", "impersonation.md"), "acting as user requires two-factor reauthentication and confirmation dialog banner", "utf8");
|
|
await writeFile(join(root, "scripts", "audit.mjs"), "EventsService records security event for currentUser with timeout", "utf8");
|
|
await writeFile(join(root, "package.json"), "{\"description\":\"assume user session notice expiration\"}", "utf8");
|
|
await writeFile(join(root, "scripts", "node_modules", "ignored", "ignored.mjs"), "impersonation TTL audit", "utf8");
|
|
await writeFile(join(root, "scripts", ".tc", "ignored.mjs"), "impersonation TTL audit", "utf8");
|
|
await writeFile(join(root, "scripts", "generated", "ignored.mjs"), "impersonation TTL audit", "utf8");
|
|
|
|
const inventory = await runImpersonationScopeImpersonationScopeInventory(root);
|
|
const allMatches = inventory.matches.join("\n");
|
|
|
|
expect(inventory.roots).toEqual(["apps/web/app", "prisma/schema", "docs/security", "scripts", "package.json"]);
|
|
expect(inventory.missingRoots).toEqual([]);
|
|
expect(inventory.filesScanned).toBe(5);
|
|
expect(inventory.counts).toMatchObject({
|
|
impersonation: 3,
|
|
ttl: 4,
|
|
"step-up": 2,
|
|
mfa: 2,
|
|
audit: 4,
|
|
"destructive-confirm": 2,
|
|
"session-actor": 5,
|
|
"ui-marker": 3,
|
|
});
|
|
expect(inventory.totalMatches).toBe(25);
|
|
expect(allMatches).toContain("apps/web/app/auth.ts");
|
|
expect(allMatches).not.toContain("node_modules/ignored/ignored.mjs");
|
|
expect(allMatches).not.toContain(".tc/ignored.mjs");
|
|
expect(allMatches).not.toContain("generated/ignored.mjs");
|
|
});
|
|
|
|
it("records absent impersonation roots without failing the read-only inventory gate", async () => {
|
|
const root = await mkdtemp(join(tmpdir(), "impersonationScope-baseline-missing-"));
|
|
const inventory = await runImpersonationScopeImpersonationScopeInventory(root);
|
|
|
|
expect(inventory.roots).toEqual([]);
|
|
expect(inventory.missingRoots).toEqual(impersonationScopeImpersonationScopeInventoryScanRoots);
|
|
expect(inventory.filesScanned).toBe(0);
|
|
expect(inventory.totalMatches).toBe(0);
|
|
expect(Object.values(inventory.counts).every((count) => count === 0)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("Impersonation Scope report rendering", () => {
|
|
it("renders gate metadata, fingerprints, and inventory counts", () => {
|
|
const report = renderReport([
|
|
{ id: "typecheck", command: "pnpm run typecheck", exitCode: 2, durationMs: 1500, failureCount: 7, fingerprint: { hash: "abc123def456", excerpt: "error TS2322" } },
|
|
{
|
|
id: "impersonationScope-impersonation-scope-inventory",
|
|
command: "read-only Impersonation Scope impersonation scope inventory",
|
|
exitCode: 0,
|
|
durationMs: 42,
|
|
failureCount: 0,
|
|
inventory: {
|
|
roots: ["apps/web/app"],
|
|
missingRoots: ["docs/security"],
|
|
filesScanned: 1,
|
|
counts: { impersonation: 1, ttl: 2 },
|
|
totalMatches: 3,
|
|
matches: [],
|
|
},
|
|
fingerprint: { hash: "fed456abc123", excerpt: "impersonation: 1" },
|
|
},
|
|
]);
|
|
|
|
expect(report).toContain("# Impersonation Scope Baseline Health");
|
|
expect(report).toContain("non-E2E baseline gates for Impersonation Scope baseline");
|
|
expect(report).toContain("impersonation/ttl/step-up/mfa/audit/destructive-confirm/session-actor/ui-marker references");
|
|
expect(report).toContain("## typecheck");
|
|
expect(report).toContain("## impersonationScope-impersonation-scope-inventory");
|
|
expect(report).toContain("- Exitcode: 2");
|
|
expect(report).toContain("- Failure Count: 7");
|
|
expect(report).toContain("- Output Fingerprint: abc123def456");
|
|
expect(report).toContain("- Inventory Roots: apps/web/app");
|
|
expect(report).toContain("- Missing Roots Skipped: docs/security");
|
|
expect(report).toContain(" - impersonation: 1");
|
|
expect(report).toContain("- Total Inventory Matches: 3");
|
|
});
|
|
});
|