Repository Public
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