import assert from 'node:assert/strict'; import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import path from 'node:path'; import test from 'node:test'; import { loadPathAliasManifest, parsePathAliasManifest, resolveChangelogPath, transitionAliasedFrontendState, validatePathAliases, } from '../scripts/changelog-path-aliases.mjs'; import { parseFrontmatter } from '../scripts/validate-changelog-frontmatter.mjs'; const OLD_5216 = 'changelogs-v2/2026-07/24_5216_派车看板补充槽位接送路线与就绪摘要-修改接口-前端待处理-管理后台.md'; const CURRENT_5216 = 'changelogs-v2/2026-07/24_5216_派车看板补充槽位接送路线与就绪摘要-修改接口-管理后台.md'; function document({ canonicalPath = '', status = 'claimed', owner = 'frontend-team', frontendRef = '' } = {}) { const canonicalLine = canonicalPath ? `canonical_path: "${canonicalPath}"\n` : ''; return `--- schema: "hl-changelog/v2" ticket: "5216" title: "路径兼容测试" consumer: "admin" change_type: "修改接口" backend_status: "deployed" gateway_status: "verified" frontend_status: "${status}" frontend_owner: "${owner}" frontend_ref: "${frontendRef}" target_release: "" verified_at: "" ${canonicalLine}updated_at: "2026-07-26" base: "dev-v3" --- # 路径兼容测试 ## 变更接口 - 无业务接口变化。 ## 验证证据 - 自动化测试。 `; } function createFixture() { const root = mkdtempSync(path.join(tmpdir(), 'hl-changelog-alias-')); const manifest = { schema: 'hl-changelog-path-aliases/v1', aliases: [{ ticket: '5216', alias: OLD_5216, canonical: CURRENT_5216, reason: '测试旧路径兼容', }], }; writeFileSync(path.join(root, 'changelog-path-aliases.json'), JSON.stringify(manifest, null, 2)); for (const repoPath of [OLD_5216, CURRENT_5216]) { const absolute = path.join(root, ...repoPath.split('/')); mkdirSync(path.dirname(absolute), { recursive: true }); writeFileSync(absolute, document({ canonicalPath: repoPath === OLD_5216 ? CURRENT_5216 : '', })); } return root; } test('repository #5216 compatibility entry resolves to its canonical document', () => { const manifest = loadPathAliasManifest(); assert.equal(resolveChangelogPath(OLD_5216, manifest), CURRENT_5216); assert.equal(resolveChangelogPath(CURRENT_5216, manifest), CURRENT_5216); assert.deepEqual(validatePathAliases().errors, []); }); test('rejects unsafe paths, duplicate aliases and alias chains', () => { assert.throws(() => parsePathAliasManifest(JSON.stringify({ schema: 'hl-changelog-path-aliases/v1', aliases: [{ ticket: '5216', alias: '../outside.md', canonical: CURRENT_5216, reason: 'unsafe', }], })), /POSIX|规范路径/); assert.throws(() => parsePathAliasManifest(JSON.stringify({ schema: 'hl-changelog-path-aliases/v1', aliases: [ { ticket: '5216', alias: OLD_5216, canonical: CURRENT_5216, reason: 'one' }, { ticket: '5216', alias: OLD_5216, canonical: CURRENT_5216, reason: 'two' }, ], })), /重复/); }); test('frontend status transition updates alias group once and is idempotent', () => { const root = createFixture(); try { const first = transitionAliasedFrontendState(root, OLD_5216, { status: 'implemented', owner: 'frontend-team', frontendRef: 'mmg/hl-ui@abc1234', }, { now: new Date('2026-07-26T04:00:00Z'), write: true, }); assert.deepEqual(new Set(first.changedPaths), new Set([OLD_5216, CURRENT_5216])); assert.deepEqual(validatePathAliases(root).errors, []); for (const repoPath of [OLD_5216, CURRENT_5216]) { const text = readFileSync(path.join(root, ...repoPath.split('/')), 'utf8'); const { metadata } = parseFrontmatter(text); assert.equal(metadata.frontend_status, 'implemented'); assert.equal(metadata.frontend_owner, 'frontend-team'); assert.equal(metadata.frontend_ref, 'mmg/hl-ui@abc1234'); } const second = transitionAliasedFrontendState(root, OLD_5216, { status: 'implemented', owner: 'frontend-team', frontendRef: 'mmg/hl-ui@abc1234', }, { now: new Date('2026-07-26T04:00:00Z'), write: true, }); assert.deepEqual(second.changedPaths, []); } finally { rmSync(root, { recursive: true, force: true }); } });