import { test } from "node:test"; import assert from "node:assert/strict"; import { METADATA_VALUE_DISPLAY_CHARS, NOTHING_MEANINGFUL, metadataHistoryView, relativeAgo, valueView, } from "./metadataHistoryView"; import type { MetadataHistoryEntry } from "../lib/metadataHistory"; function e(over: Partial): MetadataHistoryEntry { return { at: "2026-09-26T12:00:00.000Z", by: "prefetch", from: { sha256: "a".repeat(64), bytes: 10 }, to: { sha256: "b".repeat(64), bytes: 11 }, changed: {}, added: {}, removed: {}, counters: {}, volatile: ["epoch", "formats"], ...over, }; } const NOW = Date.parse("2026-09-26T12:05:00.000Z"); test("no history, or an empty one, draws nothing", () => { assert.equal(metadataHistoryView(null, NOW), null); assert.equal(metadataHistoryView({ entries: [] }, NOW), null); }); test("the line: count, how lately, by whom, and what moved", () => { const v = metadataHistoryView( { entries: [ e({ at: "2026-09-25T12:00:00.000Z" }), e({ at: "2026-09-26T12:00:00.000Z", changed: { title: { from: "Old", to: "New" } }, counters: { view_count: [10, 12] }, }), ], }, NOW, ); assert.ok(v); assert.equal(v.count, 2); assert.equal(v.line, "Metadata rewritten 2× · last 5m ago by prefetch: title"); // Newest first. assert.deepEqual( v.entries.map((x) => x.at), ["2026-09-26T12:00:00.000Z", "2026-09-25T12:00:00.000Z"], ); assert.equal(v.entries[0].atLabel, "2026-09-26 12:00 UTC"); assert.deepEqual( v.entries[0].changes.map((c) => [c.key, c.kind, c.from?.text, c.to?.text]), [ ["title", "changed", "Old", "New"], ["view_count", "counter", "10", "12"], ], ); assert.equal(v.entries[1].summary, NOTHING_MEANINGFUL); }); test("counters alone are named; an all-volatile rewrite says nothing meaningful", () => { const counters = metadataHistoryView( { entries: [e({ counters: { view_count: [1, 2], like_count: [null, 3] } })] }, NOW, )!; assert.equal( counters.line, "Metadata rewritten 1× · last 5m ago by prefetch: only counters (view_count, like_count)", ); assert.equal(counters.entries[0].changes[1].from, null); const none = metadataHistoryView({ entries: [e({})] }, NOW)!; assert.match(none.line, /: nothing meaningful \(formats only\)$/); assert.deepEqual(none.entries[0].volatile, ["epoch", "formats"]); }); test("added and removed keys, requestedBy, and the unreadable case", () => { const v = metadataHistoryView( { entries: [ e({ by: "audio-check", requestedBy: "umtool", added: { chapters: [{ title: "Intro" }] }, removed: { availability: "public" }, }), e({ unparseable: ["from"], volatile: [] }), ], }, NOW, )!; assert.equal(v.entries[1].summary, "chapters, availability"); assert.equal(v.entries[1].requestedBy, "umtool"); assert.deepEqual( v.entries[1].changes.map((c) => [c.kind, c.from?.text ?? null, c.to?.text ?? null]), [ ["added", null, '[{"title":"Intro"}]'], ["removed", "public", null], ], ); assert.equal(v.entries[0].summary, "unreadable metadata (no diff)"); assert.equal(v.entries[0].unparseable, true); }); test("long values are cut at 300 with the whole text kept; digests read as such", () => { const long = "d".repeat(METADATA_VALUE_DISPLAY_CHARS + 50); const cut = valueView(long); assert.equal(cut.truncated, true); assert.equal(cut.text.length, METADATA_VALUE_DISPLAY_CHARS + 1); assert.ok(cut.text.endsWith("…")); assert.equal(cut.full, long); const short = valueView("short"); assert.deepEqual(short, { text: "short", full: "short", truncated: false }); const digest = valueView({ sha256: "c".repeat(64), bytes: 20000 }); assert.match(digest.text, /^\(20000 bytes, too long to keep; sha256 cccccccccccc…\)$/); }); test("relativeAgo steps s → m → h → d, and a future stamp is just now", () => { assert.equal(relativeAgo(4_000), "4s ago"); assert.equal(relativeAgo(5 * 60_000), "5m ago"); assert.equal(relativeAgo(3 * 3_600_000), "3h ago"); assert.equal(relativeAgo(2 * 86_400_000), "2d ago"); assert.equal(relativeAgo(-10), "just now"); });