// tests/kb-update/test-registry-migrate.test.mjs // Unit tests for scripts/kb-update/lib/registry-migrate.mjs — the one-shot // ai-foundry -> foundry URL remap + reserved-schema migration. // Microsoft rebranded "Azure AI Foundry" -> "Microsoft Foundry"; URLs moved // /azure/ai-foundry/ -> /azure/foundry/. Two probed pages are DEAD on a naive // prefix swap and need explicit targets (verified in Sesjon 1). import { test } from 'node:test'; import assert from 'node:assert/strict'; import { remapKey, migrateRegistry } from '../../scripts/kb-update/lib/registry-migrate.mjs'; const AF = 'https://learn.microsoft.com/azure/ai-foundry/'; const FD = 'https://learn.microsoft.com/azure/foundry/'; test('remapKey — prefix swap ai-foundry -> foundry', () => { assert.equal(remapKey(AF + 'openai/how-to/manage-costs'), FD + 'openai/how-to/manage-costs'); assert.equal(remapKey(AF + 'responsible-ai/openai/content-filter'), FD + 'responsible-ai/openai/content-filter'); }); test('remapKey — two probed DEAD pages get explicit targets', () => { assert.equal( remapKey('https://learn.microsoft.com/azure/ai-foundry/concepts/evaluation-evaluators'), 'https://learn.microsoft.com/azure/foundry/concepts/built-in-evaluators' ); assert.equal( remapKey('https://learn.microsoft.com/azure/ai-foundry/agent-service'), 'https://learn.microsoft.com/azure/foundry/agents/overview' ); }); test('remapKey — non-ai-foundry URLs untouched', () => { const other = 'https://learn.microsoft.com/azure/machine-learning/overview'; assert.equal(remapKey(other), other); // already-foundry URL is left as-is assert.equal(remapKey(FD + 'what-is-azure-ai-foundry'), FD + 'what-is-azure-ai-foundry'); }); function fixtureRegistry() { return { version: 1, created_at: '2026-01-01', last_poll: '2026-06-19T11:00:00Z', sitemap_state: { 'azure_en-us_7': { lastmod: '2026-06-01', checked_at: '2026-06-19' } }, urls: { [AF + 'openai/how-to/manage-costs']: { sitemap_lastmod: null, reference_files: ['skills/a.md'], status: 'not_in_sitemap', }, [AF + 'agent-service']: { sitemap_lastmod: null, reference_files: ['skills/b.md'], status: 'not_in_sitemap', }, // already-tracked non-foundry URL must survive untouched 'https://learn.microsoft.com/azure/search/rag': { sitemap_lastmod: '2026-05-10', reference_files: ['skills/c.md'], status: 'tracked', }, }, }; } test('migrateRegistry — remaps ai-foundry keys and resets their tracking', () => { const { registry } = migrateRegistry(fixtureRegistry()); // keys remapped assert.ok(registry.urls[FD + 'openai/how-to/manage-costs'], 'prefix-swap key missing'); assert.ok(registry.urls[FD + 'agents/overview'], 'DEAD-remap key missing'); assert.ok(!registry.urls[AF + 'openai/how-to/manage-costs'], 'old ai-foundry key still present'); // remapped entries reset so the next poll re-evaluates const e = registry.urls[FD + 'openai/how-to/manage-costs']; assert.equal(e.status, 'unpolled'); assert.equal(e.sitemap_lastmod, null); assert.deepEqual(e.reference_files, ['skills/a.md']); }); test('migrateRegistry — non-ai-foundry entry preserved verbatim (plus reserved fields)', () => { const { registry } = migrateRegistry(fixtureRegistry()); const e = registry.urls['https://learn.microsoft.com/azure/search/rag']; assert.equal(e.status, 'tracked'); assert.equal(e.sitemap_lastmod, '2026-05-10'); assert.deepEqual(e.reference_files, ['skills/c.md']); }); test('migrateRegistry — reserved schema fields present on every entry', () => { const { registry } = migrateRegistry(fixtureRegistry()); for (const [url, e] of Object.entries(registry.urls)) { assert.ok('authority_source' in e, `authority_source missing on ${url}`); assert.ok('course' in e, `course missing on ${url}`); assert.equal(e.authority_source, null); assert.equal(e.course, null); } }); test('migrateRegistry — top-level registry metadata preserved', () => { const { registry } = migrateRegistry(fixtureRegistry()); assert.equal(registry.version, 1); assert.equal(registry.created_at, '2026-01-01'); assert.ok(registry.sitemap_state['azure_en-us_7']); }); test('migrateRegistry — collision on same target merges reference_files (union, sorted)', () => { const reg = { version: 1, created_at: null, last_poll: null, sitemap_state: {}, urls: { // both map to FD + agents/overview (one via DEAD remap, one via prefix swap) [AF + 'agent-service']: { sitemap_lastmod: null, reference_files: ['skills/z.md', 'skills/a.md'], status: 'not_in_sitemap' }, [AF + 'agents/overview']: { sitemap_lastmod: null, reference_files: ['skills/a.md', 'skills/m.md'], status: 'not_in_sitemap' }, }, }; const { registry, stats } = migrateRegistry(reg); const e = registry.urls[FD + 'agents/overview']; assert.deepEqual(e.reference_files, ['skills/a.md', 'skills/m.md', 'skills/z.md']); assert.equal(Object.keys(registry.urls).length, 1); assert.ok(stats.collisions >= 1); }); test('migrateRegistry — does not mutate the input registry', () => { const input = fixtureRegistry(); migrateRegistry(input); assert.ok(input.urls[AF + 'openai/how-to/manage-costs'], 'input was mutated'); }); test('migrateRegistry — stats count remaps and DEAD remaps', () => { const { stats } = migrateRegistry(fixtureRegistry()); assert.equal(stats.remapped, 2); assert.equal(stats.deadRemapped, 1); // agent-service is the only DEAD one in the fixture });