Files
Genarrative/apps/ai-game-creator-shell/tests/projectResourceProjectionModel.test.ts
T
menghao d1a47c0fe7
Project CI / Frontend tests (pull_request) Successful in 3m43s
Project CI / Repository checks (pull_request) Failing after 1m27s
Project CI / Backend tests (pull_request) Successful in 4m26s
Project CI / Native shell tests (pull_request) Failing after 7m40s
实现游戏工作台P0/P1可运行版本能力
冻结工作台运行版本、会话、参数、资源与消息跨端合同
新增可信 Runtime 验证后的不可变可运行版本登记与快照校验
支持 Tauri 客户端选择、切换并运行历史版本快照
收紧普通预览授权边界并同步资源高亮与明确错误展示
补充前后端测试、PRD、技术方案和项目决策记录
2026-08-04 15:21:55 +08:00

231 lines
6.8 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
createGameCreationAppManifest,
type RunnableGameVersion,
} from '../../../packages/shared/src/contracts/gameCreationApp';
import { projectResourcesFromReadModels } from '../src/view/project-development/resourceProjectionModel';
function runnableVersionFixture(
projectId: string,
versionId: string,
projectRevision: number,
overrides: Partial<RunnableGameVersion> = {},
): RunnableGameVersion {
return {
schemaVersion: 'game-creator-runnable-version.v1',
versionId,
projectId,
parentVersionId: null,
projectRevision,
artifactPath: `.agent/runnable-versions/${versionId}/artifact`,
artifactSha256: 'a'.repeat(64),
entryPath: 'game/index.html',
resourceBindings: [],
createdReason: 'initial',
validation: {
staticSmokePassed: true,
previewValidatePassed: true,
playtestPassed: true,
agentId: 'program-agent',
runId: `run-${projectRevision}`,
reportPath: `.agent/runtime/browser-validations/program-agent/run-${projectRevision}/1/validation.json`,
},
createdAt: projectRevision * 100,
...overrides,
};
}
describe('项目资源投影', () => {
it('只把明确资源投影到固定分类,未知任务产物不会伪装成项目版本', () => {
const manifest = createGameCreationAppManifest(
'resource-projection',
'资源投影测试',
);
const designTask = manifest.tasks.find(
(task) => task.id === 'design-foundation',
);
const audioTask = manifest.tasks.find(
(task) => task.id === 'audio-asset-plan',
);
if (!designTask || !audioTask) {
throw new Error('缺少资源投影测试任务');
}
designTask.status = 'completed';
designTask.artifacts = [
'memory/game-design.md',
'assets/ui-preview.svg',
'build/game.bundle',
];
audioTask.status = 'completed';
audioTask.artifacts = ['audio/unregistered-bgm.wav'];
manifest.assets = [
{
id: 'registered-bgm',
kind: 'bgm',
mediaType: 'audio/wav',
localPath: 'audio/registered-bgm.wav',
source: { kind: 'generated' },
},
{
id: 'character-animation',
kind: 'character-animation',
mediaType: 'application/json',
localPath: 'assets/character-animation.json',
source: { kind: 'generated' },
},
{
id: 'unknown-binary',
kind: 'binary',
mediaType: 'application/octet-stream',
localPath: 'build/game.bundle',
source: { kind: 'generated' },
},
];
manifest.runnableVersions = [
runnableVersionFixture('resource-projection', 'version-1', 7, {
resourceBindings: [
{ slotId: 'background-music', resourceId: 'registered-bgm' },
],
}),
];
manifest.currentRunnableVersionId = 'version-1';
const resources = projectResourcesFromReadModels(
manifest,
[
{
fileName: 'rules.yaml',
mediaType: 'application/yaml',
localPath: 'imports/rules.yaml',
status: 'imported',
},
{
fileName: 'voice.ogg',
mediaType: 'audio/ogg',
localPath: 'imports/voice.ogg',
status: 'imported',
},
{
fileName: 'archive.zip',
mediaType: 'application/zip',
localPath: 'imports/archive.zip',
status: 'imported',
},
],
[
{
agentId: 'design-foundation',
runId: 'final-run',
label: '玩法策划 Agent',
title: '玩法文档回执',
content: '回执正文',
updatedAt: 1,
},
],
);
expect(resources.map(({ id, category }) => ({ id, category }))).toEqual(
expect.arrayContaining([
{
id: 'task:design-foundation:memory/game-design.md',
category: 'document',
},
{
id: 'task:design-foundation:assets/ui-preview.svg',
category: 'art',
},
{ id: 'asset:registered-bgm', category: 'audio' },
{ id: 'asset:character-animation', category: 'art' },
{ id: 'attachment:imports/rules.yaml', category: 'document' },
{ id: 'attachment:imports/voice.ogg', category: 'audio' },
{
id: 'agent-result:design-foundation:final-run',
category: 'document',
},
{ id: 'version:version-1', category: 'version' },
]),
);
expect(
resources.some(({ id }) =>
[
'task:design-foundation:build/game.bundle',
'task:audio-asset-plan:audio/unregistered-bgm.wav',
'asset:unknown-binary',
'attachment:imports/archive.zip',
].includes(id),
),
).toBe(false);
expect(
resources.filter(({ category }) => category === 'version'),
).toHaveLength(1);
});
it('显示名称变化不会改变正式资源身份', () => {
const manifest = createGameCreationAppManifest(
'stable-resource-id',
'稳定资源身份测试',
);
manifest.runnableVersions = [
runnableVersionFixture('stable-resource-id', 'stable-version', 1),
];
manifest.currentRunnableVersionId = 'stable-version';
const first = projectResourcesFromReadModels(
manifest,
[],
[
{
agentId: 'design-foundation',
runId: 'stable-run',
label: '策划 Agent',
title: '旧标题',
content: '正文',
updatedAt: 1,
},
],
);
const second = projectResourcesFromReadModels(
manifest,
[],
[
{
agentId: 'design-foundation',
runId: 'stable-run',
label: '策划 Agent',
title: '新标题',
content: '正文',
updatedAt: 2,
},
],
);
expect(first.map(({ id }) => id)).toEqual(second.map(({ id }) => id));
});
it('只从 manifest 投影版本并保留直接父子关系', () => {
const manifest = createGameCreationAppManifest(
'version-projection',
'版本投影测试',
);
manifest.runnableVersions = [
runnableVersionFixture('version-projection', 'version-root', 2),
runnableVersionFixture('version-projection', 'version-child', 3, {
parentVersionId: 'version-root',
createdReason: 'agent-revision',
}),
];
manifest.currentRunnableVersionId = 'version-child';
const versions = projectResourcesFromReadModels(manifest, [], []).filter(
(resource) => resource.category === 'version',
);
expect(versions.map((version) => version.label)).toEqual([
'版本 1',
'版本 2',
]);
expect(versions[0]?.version?.childVersionIds).toEqual(['version-child']);
expect(versions[1]?.version?.parentVersionId).toBe('version-root');
});
});