import assert from "node:assert/strict"; import path from "node:path"; import test from "node:test"; import { collectRealmSpellIds, expandCoaSpellIds, resolveCoaSourcePaths, sharedRealmSpellIds, } from "./coa-source-policy.mjs"; test("CoA payload closure includes effect-168 links, cycles and absent IDs without overlay fallback", () => { const buffer = Buffer.alloc(20 + 3 * 234 * 4); buffer.write("WDBC"); buffer.writeUInt32LE(3, 4); buffer.writeUInt32LE(234, 8); buffer.writeUInt32LE(234 * 4, 12); const write = (row, field, value) => buffer.writeUInt32LE(value, 20 + (row * 234 + field) * 4); write(0, 0, 100); write(0, 71, 168); write(0, 110, 200); write(1, 0, 200); write(1, 116, 300); write(2, 0, 300); write(2, 116, 100); write(2, 117, 400); assert.deepEqual([...expandCoaSpellIds(buffer, [100])], [100, 200, 300, 400]); assert.throws(() => expandCoaSpellIds(Buffer.from("invalid"), [100]), /234-field/); }); test("CoA source resolution selects global patch-T and patch-M while classic keeps Area52 priority", () => { const root = path.resolve("fixture/client-current"); const legacy = path.resolve("fixture/legacy"); const exists = new Set([ path.join(root, "area-52", "patch-D", "DBFilesClient", "Spell.dbc"), path.join(root, "area-52", "patch-D", "DBFilesClient", "SkillLineAbility.dbc"), path.join(root, "patch-T", "DBFilesClient", "Spell.dbc"), path.join(root, "patch-M", "DBFilesClient", "SkillLineAbility.dbc"), path.join(legacy, "patch-T", "DBFilesClient", "Spell.dbc"), path.join(legacy, "patch-M", "DBFilesClient", "SkillLineAbility.dbc"), ]); const sources = resolveCoaSourcePaths({ extractedClientRoot: root, legacyExtractedRoot: legacy, exists: (candidate) => exists.has(candidate), }); assert.equal(sources.classicSpellDbcPath, path.join(root, "area-52", "patch-D", "DBFilesClient", "Spell.dbc")); assert.equal(sources.classicSkillLineAbilityDbcPath, path.join(root, "area-52", "patch-D", "DBFilesClient", "SkillLineAbility.dbc")); assert.equal(sources.coaSpellDbcPath, path.join(root, "patch-T", "DBFilesClient", "Spell.dbc")); assert.equal(sources.coaSkillLineAbilityDbcPath, path.join(root, "patch-M", "DBFilesClient", "SkillLineAbility.dbc")); }); test("CoA sources fall back only to global legacy files, never Area52", () => { const root = path.resolve("fixture/client-current"); const legacy = path.resolve("fixture/legacy"); const exists = new Set([ path.join(root, "area-52", "patch-D", "DBFilesClient", "Spell.dbc"), path.join(root, "area-52", "patch-D", "DBFilesClient", "SkillLineAbility.dbc"), path.join(legacy, "patch-T", "DBFilesClient", "Spell.dbc"), path.join(legacy, "patch-M", "DBFilesClient", "SkillLineAbility.dbc"), ]); const sources = resolveCoaSourcePaths({ extractedClientRoot: root, legacyExtractedRoot: legacy, exists: (candidate) => exists.has(candidate), }); assert.equal(sources.coaSpellDbcPath, path.join(legacy, "patch-T", "DBFilesClient", "Spell.dbc")); assert.equal(sources.coaSkillLineAbilityDbcPath, path.join(legacy, "patch-M", "DBFilesClient", "SkillLineAbility.dbc")); }); test("classic SkillLineAbility follows the selected Spell.dbc when that sibling exists", () => { const root = path.resolve("fixture/client-current"); const legacy = path.resolve("fixture/legacy"); const exists = new Set([ path.join(root, "area-52", "patch-D", "DBFilesClient", "SkillLineAbility.dbc"), path.join(root, "patch-T", "DBFilesClient", "Spell.dbc"), path.join(root, "patch-T", "DBFilesClient", "SkillLineAbility.dbc"), path.join(root, "patch-M", "DBFilesClient", "SkillLineAbility.dbc"), path.join(legacy, "patch-T", "DBFilesClient", "Spell.dbc"), path.join(legacy, "patch-M", "DBFilesClient", "SkillLineAbility.dbc"), ]); const sources = resolveCoaSourcePaths({ extractedClientRoot: root, legacyExtractedRoot: legacy, exists: (candidate) => exists.has(candidate), }); assert.equal(sources.classicSpellDbcPath, path.join(root, "patch-T", "DBFilesClient", "Spell.dbc")); assert.equal(sources.classicSkillLineAbilityDbcPath, path.join(root, "patch-T", "DBFilesClient", "SkillLineAbility.dbc")); }); test("realm spell ownership protects classic references and includes CoA tree ranks", () => { const { classicIds, coaIds } = collectRealmSpellIds({ classicClassIds: [11], coaClassIds: [27], classSpellRows: [ { id: 100, classId: 11 }, { id: 200, classId: 27 }, { id: 300, classId: 27 }, { id: 400, classId: 11 }, { id: 400, classId: 27 }, ], classicRankCandidates: [{ id: 101 }], coaRankCandidates: [{ id: 201 }], coaEntrySpellIds: [300, 301], }); assert.deepEqual([...classicIds].sort(), [100, 101, 400]); assert.deepEqual([...coaIds].sort(), [200, 201, 300, 301, 400]); assert.ok(classicIds.has(100) && coaIds.has(300) && classicIds.has(400) && coaIds.has(400)); assert.deepEqual(sharedRealmSpellIds(classicIds, coaIds), [400]); assert.deepEqual(sharedRealmSpellIds(new Set([401]), new Set([401])), [401]); });