import test from 'node:test' import assert from 'node:assert/strict' import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises' import os from 'node:os' import path from 'node:path' import { expandRuntimeAssetInventory } from '../scripts/runtime-asset-inventory.mjs' const createWorkspace = async () => { const workspace = await mkdtemp(path.join(os.tmpdir(), 'jiapu-runtime-assets-')) await mkdir(path.join(workspace, 'design-pipeline', 'manifests'), { recursive: true }) return workspace } const writeManifest = async (workspace, name, value) => { const filePath = path.join(workspace, 'design-pipeline', 'manifests', name) await writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8') return filePath } const directAsset = (id, output) => ({ id, output, width: 96, height: 96, alpha: true, bytes: 100, sha256: 'a'.repeat(64), provenance: 'committed-binary', rebuildable: false, }) const runtimeManifest = (imports = [], assets = []) => ({ schemaVersion: 1, kind: 'runtime-asset-inventory', scope: 'auth', imports, assets, }) test('展开直接资产与单一导入且不复制物理规格', async (t) => { const workspace = await createWorkspace() t.after(() => rm(workspace, { recursive: true, force: true })) const imported = await writeManifest( workspace, 'shared.json', runtimeManifest([], [directAsset('shared', 'static/assets/shared.png')]), ) const root = await writeManifest( workspace, 'auth.json', runtimeManifest(['design-pipeline/manifests/shared.json'], [directAsset('auth', 'static/assets/auth.png')]), ) const result = await expandRuntimeAssetInventory(root, workspace) assert.deepEqual(result.assets.map(({ id }) => id).sort(), ['auth', 'shared']) assert(result.manifests.includes(imported)) }) test('拒绝缺失的导入文件', async (t) => { const workspace = await createWorkspace() t.after(() => rm(workspace, { recursive: true, force: true })) const root = await writeManifest(workspace, 'auth.json', runtimeManifest(['design-pipeline/manifests/missing.json'])) await assert.rejects(() => expandRuntimeAssetInventory(root, workspace), /missing manifest/i) }) test('拒绝循环导入', async (t) => { const workspace = await createWorkspace() t.after(() => rm(workspace, { recursive: true, force: true })) const first = await writeManifest(workspace, 'first.json', runtimeManifest(['design-pipeline/manifests/second.json'])) await writeManifest(workspace, 'second.json', runtimeManifest(['design-pipeline/manifests/first.json'])) await assert.rejects(() => expandRuntimeAssetInventory(first, workspace), /import cycle/i) }) test('拒绝同一清单被重复导入', async (t) => { const workspace = await createWorkspace() t.after(() => rm(workspace, { recursive: true, force: true })) await writeManifest(workspace, 'shared.json', runtimeManifest([], [directAsset('shared', 'static/assets/shared.png')])) const root = await writeManifest( workspace, 'auth.json', runtimeManifest([ 'design-pipeline/manifests/shared.json', 'design-pipeline/manifests/shared.json', ]), ) await assert.rejects(() => expandRuntimeAssetInventory(root, workspace), /duplicate import/i) }) test('拒绝不同清单拥有同一正式输出', async (t) => { const workspace = await createWorkspace() t.after(() => rm(workspace, { recursive: true, force: true })) await writeManifest(workspace, 'shared.json', runtimeManifest([], [directAsset('shared', 'static/assets/same.png')])) const root = await writeManifest( workspace, 'auth.json', runtimeManifest( ['design-pipeline/manifests/shared.json'], [directAsset('auth', 'static/assets/same.png')], ), ) await assert.rejects(() => expandRuntimeAssetInventory(root, workspace), /duplicate output/i) }) test('拒绝展开图中的重复资产 id', async (t) => { const workspace = await createWorkspace() t.after(() => rm(workspace, { recursive: true, force: true })) await writeManifest(workspace, 'shared.json', runtimeManifest([], [directAsset('same-id', 'static/assets/shared.png')])) const root = await writeManifest( workspace, 'auth.json', runtimeManifest( ['design-pipeline/manifests/shared.json'], [directAsset('same-id', 'static/assets/auth.png')], ), ) await assert.rejects(() => expandRuntimeAssetInventory(root, workspace), /duplicate asset id/i) }) test('直接资产只接受 committed-binary 且拒绝未知字段', async (t) => { const workspace = await createWorkspace() t.after(() => rm(workspace, { recursive: true, force: true })) const wrongProvenance = directAsset('wrong-provenance', 'static/assets/wrong.png') wrongProvenance.provenance = 'manual-copy' const wrongRoot = await writeManifest(workspace, 'wrong.json', runtimeManifest([], [wrongProvenance])) await assert.rejects(() => expandRuntimeAssetInventory(wrongRoot, workspace), /committed-binary/i) const unknownField = directAsset('unknown-field', 'static/assets/unknown.png') unknownField.runtimeSelector = '.page' const unknownRoot = await writeManifest(workspace, 'unknown.json', runtimeManifest([], [unknownField])) await assert.rejects(() => expandRuntimeAssetInventory(unknownRoot, workspace), /unknown field/i) }) test('运行时清单本身拒绝未知字段', async (t) => { const workspace = await createWorkspace() t.after(() => rm(workspace, { recursive: true, force: true })) const manifest = runtimeManifest() manifest.consumerList = [] const root = await writeManifest(workspace, 'auth.json', manifest) await assert.rejects(() => expandRuntimeAssetInventory(root, workspace), /unknown field/i) })