import { test } from "node:test"; import assert from "node:assert/strict"; import { assignmentKey, chatAuthorOf, compileTagRules, effectiveTagsFor, evaluateCompiledRules, evaluateTagRules, isTagId, mergeTagDefs, sanitizeTagsConfig, TAG_ID_RE, TAGS_FILENAME, type CuratedTagDef, } from "./curatedTags"; const evaCollab: CuratedTagDef = { id: "eva-collab", label: "Collab", group: "eva", groupLabel: "Eva", order: 1, rules: [ { id: "meta", kind: "metadata", pattern: "elfpire|elf ?pire ?eva", enabled: true, }, ], }; const evaInChat: CuratedTagDef = { id: "eva-in-chat", label: "In chat", group: "eva", groupLabel: "Eva", order: 2, rules: [ { id: "chat", kind: "chat-author", pattern: "elfpire", enabled: true }, ], }; const evaTopic: CuratedTagDef = { id: "eva-topic", label: "Discussed", group: "eva", groupLabel: "Eva", order: 3, rules: [ { id: "cap", kind: "caption", pattern: "\\belf ?pire\\b", enabled: true }, ], }; const DEFS = [evaCollab, evaInChat, evaTopic]; // ─── constants ─── test("TAGS_FILENAME and TAG_ID_RE are the frozen contract", () => { assert.equal(TAGS_FILENAME, "tags.json"); for (const ok of ["eva-collab", "a", "a.b_c-d", "x9"]) { assert.equal(TAG_ID_RE.test(ok), true, ok); assert.equal(isTagId(ok), true, ok); } for (const bad of ["", "-lead", ".lead", "Eva", "has space", "a/b", "é"]) { assert.equal(TAG_ID_RE.test(bad), false, bad); assert.equal(isTagId(bad), false, bad); } }); test("assignmentKey is channel-first", () => { assert.equal(assignmentKey("legal-mindset", "XZqL6k9IHGA"), "legal-mindset/XZqL6k9IHGA"); }); // ─── sanitizeTagsConfig ─── test("sanitizeTagsConfig returns an empty config for junk, never throws", () => { for (const junk of [null, undefined, 3, "x", [], { tags: 7 }]) { const cfg = sanitizeTagsConfig(junk); assert.deepEqual(cfg.tags, []); assert.deepEqual(cfg.assignments, {}); assert.equal(cfg.version, 1); } }); test("sanitizeTagsConfig drops bad ids and keeps the first of a duplicate", () => { const cfg = sanitizeTagsConfig({ version: 1, tags: [ { id: "Eva-Collab", label: "Collab" }, // lowercased { id: "-nope", label: "bad lead" }, { id: "has space" }, { id: 4 }, "string", { id: "eva-collab", label: "Second wins? no" }, ], }); assert.deepEqual( cfg.tags.map((t) => t.id), ["eva-collab"], ); assert.equal(cfg.tags[0].label, "Collab"); }); test("sanitizeTagsConfig defaults label to the id and drops empty extras", () => { const cfg = sanitizeTagsConfig({ tags: [{ id: "solo", group: " " }] }); assert.deepEqual(cfg.tags[0], { id: "solo", label: "solo" }); }); test("the SITE layer leaves a missing label ABSENT", () => { // An overlay that only recolours must carry no label. Defaulting it to the id // here would make mergeTagDefs rename the tag on that site — the site would // publish "eva-collab" where the corpus says "Collab" — and because the store // sanitizes on read as well as on write, stripping it later cannot help. const site = sanitizeTagsConfig( { tags: [{ id: "eva-collab", color: "#b48ead" }] }, { layer: "site" }, ); assert.deepEqual(site.tags[0], { id: "eva-collab", color: "#b48ead" }); assert.equal("label" in site.tags[0], false); // A site row that DOES set a label keeps it, and is still coerced. const named = sanitizeTagsConfig( { tags: [{ id: "eva-collab", label: " On mic " }] }, { layer: "site" }, ); assert.equal(named.tags[0].label, "On mic"); // The global layer is unchanged: a definition always ends up named. assert.equal( sanitizeTagsConfig({ tags: [{ id: "eva-collab" }] }).tags[0].label, "eva-collab", ); // And it survives a round trip at its own layer (the store sanitizes twice). assert.deepEqual(sanitizeTagsConfig(site, { layer: "site" }), site); }); test("sanitizeTagsConfig drops unusable rules but keeps a non-compiling one disabled", () => { const cfg = sanitizeTagsConfig({ tags: [ { id: "t", rules: [ { id: "ok", kind: "metadata", pattern: "abc" }, { id: "bad-kind", kind: "nope", pattern: "abc" }, { id: "no-pattern", kind: "caption", pattern: " " }, { id: "broken", kind: "caption", pattern: "a(" }, "junk", ], }, ], }); const rules = cfg.tags[0].rules ?? []; assert.deepEqual( rules.map((r) => r.id), ["ok", "broken"], ); assert.equal(rules[0].enabled, true); assert.equal(rules[1].enabled, false); assert.match(String(rules[1].disabledReason), /does not compile/); // The pattern itself is preserved so the operator can fix the typo. assert.equal(rules[1].pattern, "a("); }); test("sanitizeTagsConfig gives an id-less rule a positional id and normalizes dates", () => { const cfg = sanitizeTagsConfig({ tags: [ { id: "t", rules: [ { kind: "metadata", pattern: "x", dateFrom: "2026-01-01", dateTo: "20261231", channels: ["a", " ", 3, " b "], }, ], }, ], }); const rule = (cfg.tags[0].rules ?? [])[0]; assert.equal(rule.id, "r1"); assert.equal(rule.dateFrom, "20260101"); assert.equal(rule.dateTo, "20261231"); assert.deepEqual(rule.channels, ["a", "b"]); }); test("sanitizeTagsConfig keeps well-formed assignments and drops the rest", () => { const cfg = sanitizeTagsConfig({ assignments: { "legal-mindset/XZqL6k9IHGA": { manual: ["eva-collab", "eva-collab", "BAD ID", "eva-topic"], suppressed: ["eva-in-chat"], sources: { "eva-collab": { source: "umtool:elfpire-eva", setAt: "2026-09-21T18:04:11Z" }, "eva-in-chat": { source: "operator", setAt: "2026-09-21T18:05:02Z" }, "not-assigned": { source: "operator", setAt: "2026-09-21T18:05:02Z" }, "eva-topic": { source: "operator" }, }, }, "no-slash": { manual: ["x"] }, "a/b/c": { manual: ["x"] }, "/onlyid": { manual: ["x"] }, "empty/one": { manual: [], suppressed: [] }, "junk/one": 7, }, }); assert.deepEqual(Object.keys(cfg.assignments), ["legal-mindset/XZqL6k9IHGA"]); const a = cfg.assignments["legal-mindset/XZqL6k9IHGA"]; assert.deepEqual(a.manual, ["eva-collab", "eva-topic"]); assert.deepEqual(a.suppressed, ["eva-in-chat"]); // Provenance survives for a pin AND a suppression; an entry for a tag that is // assigned nowhere, or missing setAt, is dropped. assert.deepEqual(Object.keys(a.sources ?? {}).sort(), ["eva-collab", "eva-in-chat"]); // Video ids are case-sensitive: the key is not lowercased. assert.ok("legal-mindset/XZqL6k9IHGA" in cfg.assignments); }); test("sanitizeTagsConfig is idempotent", () => { const once = sanitizeTagsConfig({ tags: [{ id: "t", rules: [{ kind: "caption", pattern: "a(" }] }], assignments: { "c/v": { manual: ["t"] } }, }); assert.deepEqual(sanitizeTagsConfig(JSON.parse(JSON.stringify(once))), once); }); // ─── mergeTagDefs ─── test("the site layer carries no rules, on read and on write", () => { const raw = { tags: [ { id: "eva-collab", rules: [{ id: "site", kind: "metadata", pattern: "extra" }], }, { id: "site-only", label: "Site only", rules: [{ id: "r1", kind: "caption", pattern: "x" }], }, ], }; const site = sanitizeTagsConfig(raw, { layer: "site" }); // Not emptied — DROPPED: no `rules` key at all, so what a site writes has // none and nothing downstream is ever handed a rule that cannot fire. for (const def of site.tags) { assert.equal(Object.prototype.hasOwnProperty.call(def, "rules"), false); } // The same coercion runs on read, so a hand-edited site file cannot smuggle // one back in. assert.deepEqual( sanitizeTagsConfig(JSON.parse(JSON.stringify(site)), { layer: "site" }), site, ); // And the corpus layer is untouched by this: the identical input keeps them. assert.deepEqual( (sanitizeTagsConfig(raw).tags[0].rules ?? []).map((r) => r.id), ["site"], ); }); test("mergeTagDefs overlays presentation fields and cannot add a rule", () => { const merged = mergeTagDefs(DEFS, [ { id: "eva-collab", label: "On mic", groupLabel: "Elfpire Eva", color: "#b48ead", order: 9, hidden: true, // A rule reaching the merge at all means someone bypassed the site // coercion; it is still not appended, because a site rule can never tag // a record and "harmless on a published def" is how it looks like it can. rules: [{ id: "site", kind: "metadata", pattern: "extra", enabled: true }], }, ]); const collab = merged.find((t) => t.id === "eva-collab")!; assert.equal(collab.label, "On mic"); assert.equal(collab.groupLabel, "Elfpire Eva"); assert.equal(collab.color, "#b48ead"); assert.equal(collab.order, 9); assert.equal(collab.hidden, true); assert.deepEqual( (collab.rules ?? []).map((r) => r.id), ["meta"], ); // Untouched fields survive, and the global input is not mutated. assert.equal(collab.group, "eva"); assert.equal(evaCollab.label, "Collab"); assert.deepEqual( (evaCollab.rules ?? []).map((r) => r.id), ["meta"], ); }); test("mergeTagDefs cannot delete a global rule or a global tag", () => { const merged = mergeTagDefs(DEFS, [{ id: "eva-collab", label: "x", rules: [] }]); assert.deepEqual( merged.map((t) => t.id), ["eva-collab", "eva-in-chat", "eva-topic"], ); assert.deepEqual( (merged[0].rules ?? []).map((r) => r.id), ["meta"], ); }); test("a colour-only overlay keeps the corpus label", () => { // The bug this optionality exists for, end to end through both functions. const site = sanitizeTagsConfig( { tags: [{ id: "eva-collab", color: "#b48ead", order: 9 }] }, { layer: "site" }, ); const merged = mergeTagDefs(DEFS, site.tags); const collab = merged.find((t) => t.id === "eva-collab")!; assert.equal(collab.label, "Collab", "the corpus wording, not the id"); assert.equal(collab.color, "#b48ead"); assert.equal(collab.order, 9); // A later corpus rename follows with no site edit at all. const renamed = DEFS.map((d) => d.id === "eva-collab" ? { ...d, label: "On mic" } : d, ); assert.equal( mergeTagDefs(renamed, site.tags).find((t) => t.id === "eva-collab")!.label, "On mic", ); }); test("mergeTagDefs appends a site-only tag whole", () => { const siteOnly: CuratedTagDef = { id: "site-thing", label: "Site thing", rules: [{ id: "r1", kind: "caption", pattern: "zzz", enabled: true }], }; const merged = mergeTagDefs(DEFS, [siteOnly]); assert.equal(merged.length, 4); assert.deepEqual(merged[3], siteOnly); }); // ─── effectiveTagsFor ─── test("effectiveTagsFor unions hits with pins and removes suppressions", () => { assert.deepEqual( effectiveTagsFor(["eva-topic"], { manual: ["eva-collab"] }, DEFS), ["eva-collab", "eva-topic"], ); assert.deepEqual( effectiveTagsFor(["eva-topic", "eva-in-chat"], { suppressed: ["eva-topic"] }, DEFS), ["eva-in-chat"], ); assert.deepEqual(effectiveTagsFor([], undefined, DEFS), []); }); test("effectiveTagsFor lets a pin beat a suppression of the same tag", () => { assert.deepEqual( effectiveTagsFor([], { manual: ["eva-topic"], suppressed: ["eva-topic"] }, DEFS), ["eva-topic"], ); }); test("effectiveTagsFor sorts by def order then id, unknown ids last", () => { const defs: CuratedTagDef[] = [ { id: "zed", label: "Zed", order: 1 }, { id: "alpha", label: "Alpha", order: 2 }, { id: "beta", label: "Beta", order: 2 }, ]; assert.deepEqual( effectiveTagsFor(["beta", "alpha", "zed", "orphan"], undefined, defs), ["zed", "alpha", "beta", "orphan"], ); }); // ─── rule evaluation ─── test("chatAuthorOf reads the `: ` prefix contract from liveChat.ts", () => { assert.equal(chatAuthorOf("ElfpireEva: hello there"), "ElfpireEva"); assert.equal(chatAuthorOf("@user1: msg: with colon"), "@user1"); assert.equal(chatAuthorOf("a message with no author"), null); assert.equal(chatAuthorOf(": leading"), null); assert.equal(chatAuthorOf(""), null); }); const base = { channelSlug: "legal-mindset", id: "v1", uploadDate: "20260301" }; test("metadata rules match title, description and keywords, case-insensitively", () => { assert.deepEqual( evaluateTagRules({ ...base, title: "Stream with ELFPIRE Eva" }, DEFS), ["eva-collab"], ); assert.deepEqual( evaluateTagRules({ ...base, description: "guest: elf pire eva" }, DEFS), ["eva-collab"], ); assert.deepEqual( evaluateTagRules({ ...base, tags: ["vtuber", "elfpire"] }, DEFS), ["eva-collab"], ); assert.deepEqual(evaluateTagRules({ ...base, title: "Just a stream" }, DEFS), []); }); test("chat-author rules match the author prefix only, never the message body", () => { assert.deepEqual( evaluateTagRules( { ...base, chatCues: [{ text: "someone: elfpire is cool" }] }, DEFS, ), [], ); assert.deepEqual( evaluateTagRules( { ...base, chatCues: [{ text: "hi" }, { text: "ElfpireEva: hi chat" }] }, DEFS, ), ["eva-in-chat"], ); }); test("caption rules match cue text", () => { assert.deepEqual( evaluateTagRules( { ...base, captionCues: [{ text: "we were talking about Elf pire yesterday" }] }, DEFS, ), ["eva-topic"], ); assert.deepEqual( evaluateTagRules({ ...base, captionCues: [{ text: "nothing here" }] }, DEFS), [], ); }); test("a caption scan stops at the first hit", () => { let read = 0; const cues = Array.from({ length: 100 }, (_, i) => ({ get text() { read++; return i === 2 ? "elfpire appears" : "filler"; }, })); assert.deepEqual(evaluateTagRules({ ...base, captionCues: cues }, [evaTopic]), [ "eva-topic", ]); assert.equal(read, 3); }); test("channel scope and date range are applied before any regex", () => { const scoped: CuratedTagDef = { id: "scoped", label: "Scoped", rules: [ { id: "r1", kind: "metadata", pattern: "elfpire", channels: ["legal-mindset"], dateFrom: "20260101", dateTo: "20261231", enabled: true, }, ], }; const hit = { ...base, title: "elfpire" }; assert.deepEqual(evaluateTagRules(hit, [scoped]), ["scoped"]); assert.deepEqual(evaluateTagRules({ ...hit, channelSlug: "other" }, [scoped]), []); assert.deepEqual(evaluateTagRules({ ...hit, uploadDate: "20251231" }, [scoped]), []); assert.deepEqual(evaluateTagRules({ ...hit, uploadDate: "20270101" }, [scoped]), []); // A dated rule cannot fire on a record with no upload date. assert.deepEqual(evaluateTagRules({ ...hit, uploadDate: undefined }, [scoped]), []); }); test("an excluded channel never fires, and beats the allow-list", () => { // The shape this exists for: every channel EXCEPT the one that floods the // tag, with no allow-list to keep in sync as channels are added. const wide: CuratedTagDef = { id: "wide", label: "Wide", rules: [ { id: "r1", kind: "metadata", pattern: "elfpire", channelsExclude: ["mommaocco"], enabled: true, }, ], }; const hit = { ...base, title: "elfpire" }; assert.deepEqual(evaluateTagRules(hit, [wide]), ["wide"]); assert.deepEqual(evaluateTagRules({ ...hit, channelSlug: "mommaocco" }, [wide]), []); // A slug in BOTH lists is excluded — the exclusion is the more specific // statement, so it wins rather than the allow-list re-admitting the channel. const both: CuratedTagDef = { ...wide, rules: [ { ...wide.rules![0], channels: [base.channelSlug, "mommaocco"], channelsExclude: ["mommaocco"], }, ], }; assert.deepEqual(evaluateTagRules(hit, [both]), ["wide"]); assert.deepEqual(evaluateTagRules({ ...hit, channelSlug: "mommaocco" }, [both]), []); // And the allow-list still bounds the rule on its own. assert.deepEqual(evaluateTagRules({ ...hit, channelSlug: "other" }, [both]), []); // The exclusion applies to the cue kinds too — they route through the same // ruleApplies, which is exactly what needsCaptionCues/needsChatCues predict. const compiled = compileTagRules([ { id: "cap", label: "Cap", rules: [ { id: "r1", kind: "caption", pattern: "elfpire", channelsExclude: ["mommaocco"], enabled: true, }, ], }, ]); const captionCues = [{ text: "and then elfpire said" }]; assert.deepEqual(evaluateCompiledRules({ ...base, captionCues }, compiled), ["cap"]); assert.deepEqual( evaluateCompiledRules( { ...base, channelSlug: "mommaocco", captionCues }, compiled, ), [], ); }); test("sanitizeTagsConfig cleans channelsExclude like channels, and omits it when empty", () => { const read = (raw: object) => (sanitizeTagsConfig({ tags: [{ id: "t", rules: [{ kind: "metadata", pattern: "x", ...raw }] }], }).tags[0].rules ?? [])[0]; const rule = read({ channelsExclude: ["a", " ", 3, " b "] }); assert.deepEqual(rule.channelsExclude, ["a", "b"]); // Idempotent: sanitizing an already-sanitized rule changes nothing. assert.deepEqual(read(rule), rule); // An empty (or all-junk, or absent) exclude emits no key at all, so a rule // nobody touched round-trips through the store byte-identically. for (const raw of [{}, { channelsExclude: [] }, { channelsExclude: [" ", 7] }]) { assert.equal( Object.prototype.hasOwnProperty.call(read(raw), "channelsExclude"), false, ); } }); test("a disabled rule never fires, and neither does a broken one", () => { const defs = sanitizeTagsConfig({ tags: [ { id: "t", rules: [ { id: "off", kind: "metadata", pattern: "elfpire", enabled: false }, { id: "broken", kind: "metadata", pattern: "a(" }, ], }, ], }).tags; assert.deepEqual(evaluateTagRules({ ...base, title: "elfpire a(" }, defs), []); }); test("compileTagRules partitions by kind and reports emptiness", () => { const compiled = compileTagRules(DEFS); assert.equal(compiled.metadata.length, 1); assert.equal(compiled.chatAuthor.length, 1); assert.equal(compiled.caption.length, 1); assert.equal(compiled.isEmpty, false); const empty = compileTagRules([{ id: "t", label: "T" }]); assert.equal(empty.isEmpty, true); assert.deepEqual(evaluateCompiledRules({ ...base, title: "anything" }, empty), []); }); test("a compiled rule set is reusable across videos (no lastIndex carry-over)", () => { const compiled = compileTagRules(DEFS); for (let i = 0; i < 3; i++) { assert.deepEqual( evaluateCompiledRules({ ...base, id: `v${i}`, title: "elfpire" }, compiled), ["eva-collab"], ); } }); test("all three kinds can fire on one video and the result is sorted", () => { assert.deepEqual( evaluateTagRules( { ...base, title: "elfpire collab", chatCues: [{ text: "ElfpireEva: hi" }], captionCues: [{ text: "elfpire said" }], }, DEFS, ), ["eva-collab", "eva-in-chat", "eva-topic"], ); });