72 lines
4.4 KiB
TypeScript
72 lines
4.4 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
import type { DiagramIRV2 } from "../../model/diagram-ir.js";
|
|
import { projectLinkedViews, queryDiagram, runPolicies, simulateFailure } from "./analysis.js";
|
|
|
|
const IR: DiagramIRV2 = { version: 2, provenance: { source: "catalog" }, pages: [{ id: "system", title: "System", nodes: [
|
|
{ id: "internet", label: "Internet", kind: "external", properties: { importance: 10, trust_boundary: "public" }, provenance: { source: "inventory" } },
|
|
{ id: "api", label: "API", kind: "service", properties: { importance: 8, environment: "production", trust_boundary: "private" } },
|
|
{ id: "worker", label: "Worker", kind: "service", properties: { owner: "ops", runtime: "node" } },
|
|
{ id: "db", label: "DB", kind: "database", properties: { owner: "data", trust_boundary: "data" } },
|
|
{ id: "vendor", label: "Vendor", kind: "external" },
|
|
{ id: "orphan", label: "Orphan", kind: "service" },
|
|
], edges: [
|
|
{ id: "bad", source: "internet", target: "db" },
|
|
{ id: "request", source: "internet", target: "api", properties: { protocol: "HTTPS" } },
|
|
{ id: "dispatch", source: "api", target: "worker", kind: "async", properties: { isolates_failure: true } },
|
|
{ id: "write", source: "api", target: "db", kind: "write", properties: { protocol: "TLS" } },
|
|
{ id: "external-call", source: "api", target: "vendor" },
|
|
{ id: "cycle", source: "worker", target: "api" },
|
|
] }] };
|
|
|
|
test("linked view projection is deterministic, preserves model identity, and explains fallbacks", () => {
|
|
const views = projectLinkedViews(IR, ["executive", "system", "deployment", "dataflow", "security"]);
|
|
assert.deepEqual(views.map((view) => view.id), ["executive", "system", "deployment", "dataflow", "security"]);
|
|
assert.deepEqual(views[0].nodes.slice(0, 2).map((node) => node.id), ["internet", "api"]);
|
|
assert.deepEqual(views[0].nodes[0].provenance, { source: "inventory" });
|
|
assert.equal(views[1].fallback, false);
|
|
assert.ok(views[2].nodes.some((node) => node.id === "worker"));
|
|
assert.ok(views[3].nodes.some((node) => node.id === "db"));
|
|
assert.equal(projectLinkedViews({ ...IR, pages: [{ ...IR.pages[0], nodes: IR.pages[0].nodes.map((node) => ({ id: node.id, label: node.label })) }] }, ["security"])[0].fallbackReason?.length! > 0, true);
|
|
assert.deepEqual(projectLinkedViews(IR), projectLinkedViews(IR));
|
|
});
|
|
|
|
test("semantic query filters kind/properties and chooses deterministic shortest path", () => {
|
|
assert.deepEqual(queryDiagram(IR, { kind: "service", properties: { owner: "ops" } }).nodes.map((node) => node.id), ["worker"]);
|
|
const path = queryDiagram(IR, { from: "internet", to: "worker" });
|
|
assert.deepEqual(path.path, ["internet", "api", "worker"]);
|
|
assert.deepEqual(path.edges.map((edge) => edge.id), ["request", "dispatch"]);
|
|
});
|
|
|
|
test("architecture policies distinguish errors and warnings for all lifecycle rules", () => {
|
|
const report = runPolicies(IR);
|
|
assert.ok(report.findings.some((finding) => finding.rule === "no-direct-internet-to-database" && finding.severity === "error"));
|
|
for (const rule of ["no-cycles", "no-orphans", "every-service-has-owner", "production-has-observability", "external-dependencies-have-timeouts", "trust-boundaries-use-protocol"]) {
|
|
assert.ok(report.findings.some((finding) => finding.rule === rule && finding.severity === "warning"), rule);
|
|
}
|
|
assert.equal(report.errors, 1);
|
|
assert.ok(report.warnings >= 6);
|
|
});
|
|
|
|
test("what-if outgoing reachability stops at failure-isolating edges", () => {
|
|
const result = simulateFailure(IR, "internet");
|
|
assert.deepEqual(result.impacted, ["api", "db", "vendor"]);
|
|
assert.deepEqual(result.paths.api, ["internet", "api"]);
|
|
assert.equal(result.paths.worker, undefined);
|
|
assert.throws(() => simulateFailure(IR, "missing"), /unknown node/i);
|
|
});
|
|
|
|
test("semantic operations fail closed on ambiguous page-local IDs", () => {
|
|
const ambiguous: DiagramIRV2 = {
|
|
version: 2,
|
|
pages: [
|
|
{ id: "one", title: "One", nodes: [{ id: "shared", label: "First" }], edges: [] },
|
|
{ id: "two", title: "Two", nodes: [{ id: "shared", label: "Second" }], edges: [] },
|
|
],
|
|
};
|
|
assert.throws(() => queryDiagram(ambiguous, { kind: "service" }), /ambiguous.*shared.*one.*two/i);
|
|
assert.throws(() => projectLinkedViews(ambiguous), /ambiguous.*shared.*one.*two/i);
|
|
assert.throws(() => runPolicies(ambiguous), /ambiguous.*shared.*one.*two/i);
|
|
assert.throws(() => simulateFailure(ambiguous, "shared"), /ambiguous.*shared.*one.*two/i);
|
|
});
|