import assert from 'node:assert/strict'; import test from 'node:test'; import { listForbiddenBundledResourceFiles } from './macos-bundle-policy.mjs'; import { assertMacosAppMatchesChannelIdentity, assertManifestArtifactMatchesExpected, listStaleMacosArtifacts, readMacosAppInfoIdentity, } from './macos-release-identity.mjs'; test('allows only the production node_modules subtrees in the macOS bundle', () => { assert.deepEqual( listForbiddenBundledResourceFiles([ 'game-runtime/node/node_modules', 'game-runtime/node/node_modules/npm/bin/npm-cli.js', 'claude-agent/node_modules', 'claude-agent/node_modules/@anthropic-ai', 'claude-agent/node_modules/@anthropic-ai/claude-agent-sdk/sdk.mjs', 'claude-agent/node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64/claude', ]), [], ); assert.deepEqual( listForbiddenBundledResourceFiles([ 'claude-agent/node_modules/unexpected/index.mjs', 'claude-agent/node_modules/@anthropic-ai/unexpected/index.mjs', 'plugins/agc-cocos-editor/node_modules/leftover/index.mjs', 'game-runtime/node/.env.local', 'claude-agent/auth.json', 'claude-agent/target/debug/claude', 'claude-agent/tool.exe', 'claude-agent/tool.dll', ]), [ 'claude-agent/node_modules/unexpected/index.mjs', 'claude-agent/node_modules/@anthropic-ai/unexpected/index.mjs', 'plugins/agc-cocos-editor/node_modules/leftover/index.mjs', 'game-runtime/node/.env.local', 'claude-agent/auth.json', 'claude-agent/target/debug/claude', 'claude-agent/tool.exe', 'claude-agent/tool.dll', ], ); }); const DEV_IDENTITY = { productName: '陶泥儿开发版', identifier: 'world.genarrative.ai-game-creator', }; function plist({ version, identifier, name }) { return ` CFBundleShortVersionString${version} CFBundleIdentifier${identifier} CFBundleName${name} CFBundleDisplayName${name} `; } test('reads version, identifier and product name from Info.plist text', () => { const identity = readMacosAppInfoIdentity( plist({ version: '0.1.154', ...DEV_IDENTITY, name: DEV_IDENTITY.productName, }), ); assert.deepEqual(identity, { version: '0.1.154', identifier: DEV_IDENTITY.identifier, name: DEV_IDENTITY.productName, displayName: DEV_IDENTITY.productName, }); }); test('accepts the app bundle that matches this channel and version', () => { assert.doesNotThrow(() => assertMacosAppMatchesChannelIdentity({ identity: readMacosAppInfoIdentity( plist({ version: '0.1.154', identifier: DEV_IDENTITY.identifier, name: DEV_IDENTITY.productName, }), ), expectedVersion: '0.1.154', expectedProductName: DEV_IDENTITY.productName, expectedIdentifier: DEV_IDENTITY.identifier, }), ); }); // 回归:线上 dev-mac/0.1.142 清单实际指向 0.1.139 的 release 身份包。 test('rejects the release-identity bundle that shipped in the dev-mac channel', () => { const shipped = readMacosAppInfoIdentity( plist({ version: '0.1.139', identifier: 'world.genarrative.ai-game-creator.release', name: '陶泥儿 Release', }), ); assert.throws( () => assertMacosAppMatchesChannelIdentity({ identity: shipped, expectedVersion: '0.1.142', expectedProductName: DEV_IDENTITY.productName, expectedIdentifier: DEV_IDENTITY.identifier, }), /版本不一致:产物 0\.1\.139,本轮清单 0\.1\.142[\s\S]*bundle identifier 不一致[\s\S]*产品名不一致/u, ); }); test('rejects a bundle whose version is right but channel identity is wrong', () => { assert.throws( () => assertMacosAppMatchesChannelIdentity({ identity: readMacosAppInfoIdentity( plist({ version: '0.1.154', identifier: 'world.genarrative.ai-game-creator.release', name: '陶泥儿 Release', }), ), expectedVersion: '0.1.154', expectedProductName: DEV_IDENTITY.productName, expectedIdentifier: DEV_IDENTITY.identifier, }), /bundle identifier 不一致/u, ); }); test('lists every stale mac artifact so the bundle directory cannot leak into the manifest', () => { const files = [ '/bundle/macos/陶泥儿 Release.app.tar.gz', '/bundle/macos/陶泥儿 Release.app.tar.gz.sig', '/bundle/macos/陶泥儿开发版_0.1.139_aarch64.dmg', '/bundle/macos/陶泥儿开发版_0.1.139_aarch64.dmg.sha256', '/bundle/macos/陶泥儿开发版.app/Contents/Info.plist', ]; assert.deepEqual(listStaleMacosArtifacts(files), files.slice(0, 4)); }); test('rejects a manifest that selected a different artifact than this build wrote', () => { assert.throws( () => assertManifestArtifactMatchesExpected({ artifactPath: '/bundle/macos/陶泥儿 Release.app.tar.gz', expectedPath: '/bundle/macos/陶泥儿开发版.app.tar.gz', }), /清单选中的更新包不是本轮产物/u, ); assert.doesNotThrow(() => assertManifestArtifactMatchesExpected({ artifactPath: '/bundle/macos/陶泥儿开发版.app.tar.gz', expectedPath: '/bundle/macos/陶泥儿开发版.app.tar.gz', }), ); });