ms-ai-architect/tests/kb-update/test-registry-migrate.test.mjs
Kjell Tore Guttormsen d3af3f73c8 feat(ms-ai-architect): Sesjon 2 - ai-foundry->foundry registry-remap
Microsoft rebranet Azure AI Foundry -> Microsoft Foundry; doc-URLer flyttet
/azure/ai-foundry/ -> /azure/foundry/. Ny lib/registry-migrate.mjs (rene
funksjoner remapKey + migrateRegistry, 10 enhetstester) + one-shot
migrate-ai-foundry.mjs (backup via lib/backup.mjs + atomisk saveRegistry).

Remap: 164 ai-foundry-URLer -> foundry (2 probede DEAD-sider far eksplisitte
mal: evaluation-evaluators -> built-in-evaluators; agent-service ->
agents/overview). Remappede entries resettes til status=unpolled/lastmod=null
sa neste poll re-evaluerer. 1 kollisjon merget (ref_files unionert).
Schema-migrering: authority_source+course lagt til alle 1343 entries.
Idempotent. Registry er gitignored (regenererbar) - kun kode committes.

kb-update 66 tester gronne, validate 239.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01REiKFhP4w6xGXXqWKpPCJJ
2026-06-19 21:09:00 +02:00

123 lines
5.4 KiB
JavaScript

// 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
});