SPB Git

Repository Public

inventory.test.ts 5.7 KB 105 lines typescript

1import { describe, expect, it } from "vitest";2import nodesJson from "../../data/nodes.json";3import { aggregate, facetValues, filterNodes, publicNodes, sortNodes } from "@/lib/inventory";4import { nodeSchema, type NodeRecord } from "@/lib/schemas";56const base: NodeRecord = {7  id: "node-01", number: 1, publicName: "A", family: "Mac Studio", modelIdentifier: null, chip: "Apple M4 Max", cpuCores: 16, cpuCoreLayout: null, gpuCores: 40,8  unifiedMemoryGB: 64, storageGB: 995, networkInterface: null, role: ["worker"], lifecycle: "active", rack: null, shelf: null, addedAt: null,9  description: { en: "", fr: "" }, imageIds: [], verifiedAt: "2026-11-09", source: null, publicVisibility: true,10};11const mk = (o: Partial<NodeRecord>): NodeRecord => ({ ...base, ...o });12const fixture = [13  mk({ id: "node-01", number: 1, publicName: "A" }),14  mk({ id: "node-02", number: 2, publicName: "b", family: "Mac mini", chip: "Apple M4", cpuCores: 10, gpuCores: 10, unifiedMemoryGB: 16, storageGB: 245, role: ["services"], rack: "C" }),15  mk({ id: "node-03", number: 3, publicName: "C", family: "MacBook Pro", gpuCores: null, unifiedMemoryGB: 36, lifecycle: "offline", verifiedAt: "2026-09-10", role: ["portable", "worker"] }),16  mk({ id: "node-04", number: 4, publicName: "D", lifecycle: "planned", unifiedMemoryGB: 512, cpuCores: 32 }),17  mk({ id: "node-05", number: 5, publicName: "E", lifecycle: "retired", unifiedMemoryGB: 128 }),18  mk({ id: "node-06", number: 6, publicName: "F", publicVisibility: false, unifiedMemoryGB: 256 }),19];2021describe("aggregate", () => {22  const a = aggregate(fixture);23  it("counts only visible active/offline/maintenance machines", () => {24    expect(a.documented).toBe(3);25    expect(a.active).toBe(2);26  });27  it("excludes planned, retired and hidden hardware from every total", () => {28    expect(a.unifiedMemoryGB).toBe(64 + 16 + 36);29    expect(a.cpuCores).toBe(16 + 10 + 16);30  });31  it("withholds a total when one counted record is incomplete instead of publishing a partial sum", () => {32    expect(a.gpuCores).toBeNull();33  });34  it("groups by family and reports the most recent verification date", () => {35    expect(a.byFamily.map((f) => f.family).sort()).toEqual(["Mac Studio", "Mac mini", "MacBook Pro"]);36    expect(a.lastVerifiedAt).toBe("2026-11-09");37    expect(a.byLifecycle).toMatchObject({ active: 2, offline: 1, planned: 1, retired: 1 });38  });39  it("returns zero totals for an empty inventory", () => {40    expect(aggregate([])).toMatchObject({ documented: 0, cpuCores: 0, unifiedMemoryGB: 0, lastVerifiedAt: null });41  });42});4344describe("filterNodes", () => {45  it("filters by family, chip, role, lifecycle and rack", () => {46    expect(filterNodes(fixture, { family: "Mac mini" }).map((n) => n.id)).toEqual(["node-02"]);47    expect(filterNodes(fixture, { chip: "Apple M4" }).map((n) => n.id)).toEqual(["node-02"]);48    expect(filterNodes(fixture, { role: "portable" }).map((n) => n.id)).toEqual(["node-03"]);49    expect(filterNodes(fixture, { lifecycle: "offline" }).map((n) => n.id)).toEqual(["node-03"]);50    expect(filterNodes(fixture, { rack: "C" }).map((n) => n.id)).toEqual(["node-02"]);51    expect(filterNodes(fixture, { rack: "none" })).toHaveLength(5);52  });53  it("treats memory as a minimum and searches names case-insensitively", () => {54    expect(filterNodes(fixture.slice(0, 3), { memory: "36" }).map((n) => n.id)).toEqual(["node-01", "node-03"]);55    expect(filterNodes(fixture, { q: "B" }).map((n) => n.id)).toContain("node-02");56  });57  it("combines filters and ignores empty ones", () => {58    expect(filterNodes(fixture, { family: "Mac Studio", lifecycle: "active", q: "" }).map((n) => n.id)).toEqual(["node-01", "node-06"]);59    expect(filterNodes(fixture, {})).toHaveLength(fixture.length);60  });61});6263describe("sortNodes", () => {64  it("sorts numerically in both directions and keeps unknown values last", () => {65    const three = fixture.slice(0, 3);66    expect(sortNodes(three, "gpu", "asc").map((n) => n.id)).toEqual(["node-02", "node-01", "node-03"]);67    expect(sortNodes(three, "gpu", "desc").map((n) => n.id)).toEqual(["node-01", "node-02", "node-03"]);68  });69  it("is stable: ties fall back to the node number", () => {70    expect(sortNodes(fixture, "family", "asc").filter((n) => n.family === "Mac Studio").map((n) => n.number)).toEqual([1, 4, 5, 6]);71    expect(sortNodes(fixture, "name").map((n) => n.publicName)).toEqual(["A", "b", "C", "D", "E", "F"]);72  });73  it("does not mutate its input", () => {74    const copy = [...fixture];75    sortNodes(fixture, "memory", "desc");76    expect(fixture).toEqual(copy);77  });78});7980describe("published inventory", () => {81  const real = publicNodes(nodesJson.map((n) => nodeSchema.parse(n)));82  it("has one valid record per machine with unique ids, numbers and names", () => {83    expect(new Set(real.map((n) => n.id)).size).toBe(real.length);84    expect(new Set(real.map((n) => n.number)).size).toBe(real.length);85    expect(new Set(real.map((n) => n.publicName)).size).toBe(real.length);86  });87  it("carries provenance on every record", () => {88    for (const n of real) {89      expect(n.verifiedAt, n.id).not.toBeNull();90      expect(n.source, n.id).toBeTruthy();91    }92  });93  it("leaks no address, serial number or domain name", () => {94    const blob = JSON.stringify(real);95    expect(blob).not.toMatch(/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/);96    expect(blob).not.toMatch(/maclustr\.io|serial|[0-9a-f]{2}(:[0-9a-f]{2}){5}/i);97  });98  it("computes totals that match the records", () => {99    const a = aggregate(real);100    expect(a.documented).toBe(real.filter((n) => ["active", "offline", "maintenance"].includes(n.lifecycle)).length);101    expect(a.unifiedMemoryGB).toBe(real.reduce((s, n) => s + (n.unifiedMemoryGB ?? 0), 0));102    expect(facetValues(real).families.length).toBeGreaterThan(1);103  });104});105
Files main
Menu

Theme