syntaxai/tdd.md · main · src / b32_sama_v2_metrics.test.ts
import { describe, expect, test } from "bun:test";
import { computeCoreMetrics } from "./b32_sama_v2_metrics.ts";
import {
WORKING_SET_MAX_LOC,
WORKING_SET_MIN_LOC,
type ProfileSpec,
type SamaV2Input,
} from "./a31_sama_v2.ts";
// Flat fixture profile (one prefix per layer) so the metric tests
// don't depend on the live profile. The Law-check sublayer ordering
// isn't relevant here — these tests target the metrics computation,
// not the conformance verdict.
const FIXTURE_PROFILE: ProfileSpec = {
samaVersion: "2.0",
profile: "metrics-test",
layers: {
0: { sublayers: [{ name: "default", prefix: "p0_", index: 0 }] },
1: { sublayers: [{ name: "default", prefix: "p1_", index: 0 }] },
2: { sublayers: [{ name: "default", prefix: "p2_", index: 0 }] },
3: { sublayers: [{ name: "default", prefix: "p3_", index: 0 }] },
},
};
const mk = (entries: Array<[string, string]>): SamaV2Input => ({
profile: FIXTURE_PROFILE,
files: new Map(entries),
});
// Helper: produce a file with `n` lines of harmless code (so
// split("\n").length === n).
const linesOf = (n: number): string =>
Array.from({ length: n }, (_, i) => `const x${i} = ${i};`).join("\n");
// Helper: a minimal sibling test body for Layer-1/2 fixtures.
const TEST_BODY = 'import { test, expect } from "bun:test"; test("ok", () => { expect(1).toBe(1); });\n';
describe("computeCoreMetrics — graphDepth", () => {
test("empty repo → 0", () => {
const m = computeCoreMetrics(mk([]));
expect(m.graphDepth).toBe(0);
});
test("single file with no imports → 1", () => {
const m = computeCoreMetrics(mk([
["src/p0_a.ts", "export const x = 1;\n"],
]));
expect(m.graphDepth).toBe(1);
});
test("chain p3 → p2 → p1 → p0 → 4", () => {
const m = computeCoreMetrics(mk([
["src/p0_a.ts", "export const x = 1;\n"],
["src/p1_a.ts", `import { x } from "./p0_a.ts";\nexport const y = x;\n`],
["src/p1_a.test.ts", TEST_BODY],
["src/p2_a.ts", `import { y } from "./p1_a.ts";\nexport const z = y;\n`],
["src/p2_a.test.ts", TEST_BODY],
["src/p3_a.ts", `import { z } from "./p2_a.ts";\nexport const w = z;\n`],
]));
expect(m.graphDepth).toBe(4);
});
test("a cycle is bounded (does not infinite-loop)", () => {
// p1_a ↔ p1_b cycle (same-layer; Law would flag it, but graphDepth
// must still terminate with a finite number).
const m = computeCoreMetrics(mk([
["src/p1_a.ts", `import { y } from "./p1_b.ts";\nexport const x = y;\n`],
["src/p1_a.test.ts", TEST_BODY],
["src/p1_b.ts", `import { x } from "./p1_a.ts";\nexport const y = x;\n`],
["src/p1_b.test.ts", TEST_BODY],
]));
expect(Number.isFinite(m.graphDepth)).toBe(true);
expect(m.graphDepth).toBeGreaterThanOrEqual(1);
});
});
describe("computeCoreMetrics — fanByLayer", () => {
test("empty repo → all-zero summaries", () => {
const m = computeCoreMetrics(mk([]));
for (const L of [0, 1, 2, 3] as const) {
expect(m.fanByLayer[L].fanIn).toEqual({ mean: 0, p50: 0, p95: 0, max: 0 });
expect(m.fanByLayer[L].fanOut).toEqual({ mean: 0, p50: 0, p95: 0, max: 0 });
}
});
test("single Layer-0 file with no edges → all zeros at L0", () => {
const m = computeCoreMetrics(mk([
["src/p0_a.ts", "export const x = 1;\n"],
]));
expect(m.fanByLayer[0].fanIn.max).toBe(0);
expect(m.fanByLayer[0].fanOut.max).toBe(0);
});
test("two Layer-1 files importing same Layer-0 → L0.fanIn.max = 2, L1.fanOut.max = 1", () => {
const m = computeCoreMetrics(mk([
["src/p0_a.ts", "export const x = 1;\n"],
["src/p1_a.ts", `import { x } from "./p0_a.ts";\nexport const y = x;\n`],
["src/p1_a.test.ts", TEST_BODY],
["src/p1_b.ts", `import { x } from "./p0_a.ts";\nexport const z = x;\n`],
["src/p1_b.test.ts", TEST_BODY],
]));
expect(m.fanByLayer[0].fanIn.max).toBe(2);
expect(m.fanByLayer[1].fanOut.max).toBe(1);
expect(m.fanByLayer[1].fanIn.max).toBe(0);
});
});
describe("computeCoreMetrics — boundaryRatio", () => {
test("no parse boundaries anywhere → 1.0 (vacuously)", () => {
const m = computeCoreMetrics(mk([
["src/p0_a.ts", "export const x = 1;\n"],
]));
expect(m.boundaryRatio).toBe(1.0);
});
test("JSON.parse only in Layer 2 → 1.0", () => {
const m = computeCoreMetrics(mk([
["src/p2_a.ts", "export const f = (s: string) => JSON.parse(s);\n"],
["src/p2_a.test.ts", TEST_BODY],
]));
expect(m.boundaryRatio).toBe(1.0);
});
test("JSON.parse in Layer 1 and Layer 2 → 0.5", () => {
const m = computeCoreMetrics(mk([
["src/p1_a.ts", "export const f = (s: string) => JSON.parse(s);\n"],
["src/p1_a.test.ts", TEST_BODY],
["src/p2_a.ts", "export const g = (s: string) => JSON.parse(s);\n"],
["src/p2_a.test.ts", TEST_BODY],
]));
expect(m.boundaryRatio).toBe(0.5);
});
test("string literal containing JSON.parse doesn't false-positive", () => {
const m = computeCoreMetrics(mk([
["src/p1_a.ts", `const explainer = "call JSON.parse here";\nexport const x = explainer.length;\n`],
["src/p1_a.test.ts", TEST_BODY],
]));
expect(m.boundaryRatio).toBe(1.0);
});
test("counts every call site, not just every file", () => {
// Two JSON.parse in one Layer-2 file, one in Layer-1 → ratio = 2/3
const m = computeCoreMetrics(mk([
["src/p1_a.ts", "export const f = (s: string) => JSON.parse(s);\n"],
["src/p1_a.test.ts", TEST_BODY],
["src/p2_a.ts", "export const g = (s: string) => JSON.parse(s);\nexport const h = (s: string) => JSON.parse(s);\n"],
["src/p2_a.test.ts", TEST_BODY],
]));
expect(m.boundaryRatio).toBeCloseTo(2 / 3, 6);
});
});
describe("computeCoreMetrics — workingSetFit", () => {
test("empty repo → 1.0", () => {
const m = computeCoreMetrics(mk([]));
expect(m.workingSetFit).toBe(1.0);
});
test("a single 100-line file → 1.0", () => {
const m = computeCoreMetrics(mk([
["src/p0_a.ts", linesOf(100)],
]));
expect(m.workingSetFit).toBe(1.0);
});
test("a 10-line file falls below the min → 0.0", () => {
const m = computeCoreMetrics(mk([
["src/p0_a.ts", linesOf(10)],
]));
expect(m.workingSetFit).toBe(0.0);
});
test("a 600-line file exceeds the max → 0.0", () => {
const m = computeCoreMetrics(mk([
["src/p0_a.ts", linesOf(600)],
]));
expect(m.workingSetFit).toBe(0.0);
});
test("two files: one 100-line (in), one 10-line (out) → 0.5", () => {
const m = computeCoreMetrics(mk([
["src/p0_a.ts", linesOf(100)],
["src/p0_b.ts", linesOf(10)],
]));
expect(m.workingSetFit).toBe(0.5);
});
test("exact bounds are inclusive (50 and 500 count as in the sweet spot)", () => {
const m = computeCoreMetrics(mk([
["src/p0_min.ts", linesOf(WORKING_SET_MIN_LOC)],
["src/p0_max.ts", linesOf(WORKING_SET_MAX_LOC)],
]));
expect(m.workingSetFit).toBe(1.0);
});
test("test files don't count toward the metric (only SAMA source files)", () => {
// One 100-line Layer-1 source + a tiny sibling test. Sibling test
// is 1 line, far below the min, but it's excluded.
const m = computeCoreMetrics(mk([
["src/p1_a.ts", linesOf(100)],
["src/p1_a.test.ts", TEST_BODY],
]));
expect(m.workingSetFit).toBe(1.0);
});
});
describe("computeCoreMetrics — violationCounts", () => {
test("conforming fixture → all counts = 0", () => {
const m = computeCoreMetrics(mk([
["src/p0_a.ts", "export const x = 1;\n"],
]));
expect(m.violationCounts).toEqual({
sorted: 0, architecture: 0, modeledTests: 0, modeledBoundary: 0,
atomic: 0, law: 0, consistency: 0,
});
});
test("Layer-1 file without sibling test → modeledTests = 1", () => {
const m = computeCoreMetrics(mk([
["src/p1_a.ts", "export const y = 1;\n"],
]));
expect(m.violationCounts.modeledTests).toBe(1);
});
test("counts are populated even when overall verdict is conforming (trailing signal shape)", () => {
// Single Layer-0 file → all checks pass → all counts are 0 (not
// missing). This is the §5 contract: keys exist regardless.
const m = computeCoreMetrics(mk([
["src/p0_a.ts", "export const x = 1;\n"],
]));
const keys = Object.keys(m.violationCounts).sort();
expect(keys).toEqual([
"architecture", "atomic", "consistency", "law",
"modeledBoundary", "modeledTests", "sorted",
]);
});
});
describe("computeCoreMetrics — reproducibility", () => {
test("same input → identical output across two runs (deep-equal)", () => {
const input = mk([
["src/p0_a.ts", "export const x = 1;\n"],
["src/p1_a.ts", `import { x } from "./p0_a.ts";\nexport const y = x;\n`],
["src/p1_a.test.ts", TEST_BODY],
["src/p2_a.ts", `import { y } from "./p1_a.ts";\nexport const f = (s: string) => JSON.parse(s);\n`],
["src/p2_a.test.ts", TEST_BODY],
]);
const m1 = computeCoreMetrics(input);
const m2 = computeCoreMetrics(input);
expect(m1).toEqual(m2);
});
});