1
This commit is contained in:
146
packages/shared/src/contracts/rpgContracts.test.ts
Normal file
146
packages/shared/src/contracts/rpgContracts.test.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import type { CustomWorldAgentSessionSnapshot } from './customWorldAgentSession';
|
||||
import type { CustomWorldResultPreviewEnvelope } from './customWorldResultPreview';
|
||||
import type { CustomWorldWorkSummary } from './customWorldWorkSummary';
|
||||
|
||||
import {
|
||||
createRpgAgentFoundationDraftProfileFixture,
|
||||
createRpgAgentSupportedActionsFixture,
|
||||
createRpgAgentSessionFixture,
|
||||
createRpgCreationAnchorContentFixture,
|
||||
createRpgCreationPreviewEnvelopeFixture,
|
||||
createRpgCreationPublishedProfileFixture,
|
||||
createRpgCreationWorksResponseFixture,
|
||||
createRpgWorldLibraryEntryFixture,
|
||||
} from './rpgCreationFixtures';
|
||||
|
||||
describe('RPG 创作共享契约 fixture', () => {
|
||||
test('旧命名兼容分文件可以直接承接新 fixture 的类型消费', () => {
|
||||
const legacySession: CustomWorldAgentSessionSnapshot =
|
||||
createRpgAgentSessionFixture();
|
||||
const legacyPreview: CustomWorldResultPreviewEnvelope =
|
||||
createRpgCreationPreviewEnvelopeFixture();
|
||||
const legacyWork: CustomWorldWorkSummary =
|
||||
createRpgCreationWorksResponseFixture().items[0]!;
|
||||
|
||||
expect(legacySession.stage).toBe('ready_to_publish');
|
||||
expect(legacySession.resultPreview?.source).toBe(legacyPreview.source);
|
||||
expect(legacyWork.status).toBe('draft');
|
||||
});
|
||||
|
||||
test('anchor fixture 与 foundation draft fixture 保持最小创作真相源对应关系', () => {
|
||||
const anchors = createRpgCreationAnchorContentFixture();
|
||||
const draftProfile = createRpgAgentFoundationDraftProfileFixture();
|
||||
|
||||
expect(anchors.worldPromise?.hook).toContain('旧航路群岛');
|
||||
expect(draftProfile.worldHook).toContain('旧航路群岛');
|
||||
expect(draftProfile.playableNpcs).toHaveLength(1);
|
||||
expect(draftProfile.storyNpcs).toHaveLength(1);
|
||||
expect(draftProfile.sceneChapters[0]?.acts[0]?.backgroundImageSrc).toContain(
|
||||
'/generated-custom-world-scenes/landmark-1/scene-act-1/scene.png',
|
||||
);
|
||||
});
|
||||
|
||||
test('session fixture 同时暴露 supportedActions 与 resultPreview', () => {
|
||||
const session = createRpgAgentSessionFixture();
|
||||
|
||||
expect(session.sessionId).toBe('rpg-session-fixture');
|
||||
expect(session.stage).toBe('ready_to_publish');
|
||||
expect(session.checkpoints?.[0]?.checkpointId).toBe(
|
||||
'checkpoint-foundation-v1',
|
||||
);
|
||||
expect(session.supportedActions?.map((entry) => entry.action)).toEqual(
|
||||
expect.arrayContaining(['draft_foundation', 'generate_role_assets', 'publish_world']),
|
||||
);
|
||||
expect(session.resultPreview?.source).toBe('session_preview');
|
||||
expect(session.resultPreview?.blockers).toEqual([]);
|
||||
});
|
||||
|
||||
test('preview fixture 保持预览来源、质量结论与 profile 载体三层边界', () => {
|
||||
const preview = createRpgCreationPreviewEnvelopeFixture();
|
||||
|
||||
expect(preview.source).toBe('session_preview');
|
||||
expect(preview.preview.previewId).toBe('preview-fixture-1');
|
||||
expect(preview.preview.sessionId).toBe('rpg-session-fixture');
|
||||
expect(preview.qualityFindings?.[0]).toMatchObject({
|
||||
severity: 'info',
|
||||
code: 'scene_asset_ready',
|
||||
});
|
||||
});
|
||||
|
||||
test('supported actions fixture 明确区分可执行能力矩阵,而不是让前端自行猜测按钮状态', () => {
|
||||
const supportedActions = createRpgAgentSupportedActionsFixture();
|
||||
|
||||
expect(supportedActions).toEqual([
|
||||
{ action: 'draft_foundation', enabled: true },
|
||||
{ action: 'generate_role_assets', enabled: true },
|
||||
{ action: 'publish_world', enabled: true },
|
||||
]);
|
||||
});
|
||||
|
||||
test('published profile fixture 能稳定承载作品库与结果页所需的封面、场景幕与角色资产字段', () => {
|
||||
const profile = createRpgCreationPublishedProfileFixture();
|
||||
|
||||
expect(profile.id).toBe('rpg-profile-fixture');
|
||||
expect(profile.playableNpcs).toHaveLength(1);
|
||||
expect(profile.landmarks).toHaveLength(1);
|
||||
expect(profile.sceneChapterBlueprints).toHaveLength(1);
|
||||
expect(
|
||||
(profile.sceneChapterBlueprints as Array<{ acts?: Array<{ backgroundImageSrc?: string }> }>)[0]
|
||||
?.acts?.[0]?.backgroundImageSrc,
|
||||
).toContain('/generated-custom-world-scenes/landmark-1/scene-act-1/scene.png');
|
||||
});
|
||||
|
||||
test('regression: session preview 与 published profile 需要共同保留角色动作资产和分幕背景字段', () => {
|
||||
const session = createRpgAgentSessionFixture();
|
||||
const publishedProfile = createRpgCreationPublishedProfileFixture();
|
||||
const preview = createRpgCreationPreviewEnvelopeFixture();
|
||||
|
||||
expect(
|
||||
((session.draftProfile as { playableNpcs?: Array<{ animationMap?: { run?: { basePath?: string } } }> })
|
||||
.playableNpcs?.[0]?.animationMap?.run?.basePath ?? ''),
|
||||
).toContain('/generated-characters/playable-1/animations/run');
|
||||
expect(
|
||||
((preview.preview.playableNpcs as Array<{ generatedAnimationSetId?: string }>)[0]
|
||||
?.generatedAnimationSetId ?? ''),
|
||||
).toBe('animation-set-playable-1');
|
||||
expect(
|
||||
((publishedProfile.sceneChapterBlueprints as Array<{
|
||||
acts?: Array<{ backgroundAssetId?: string }>;
|
||||
}>)[0]?.acts?.[0]?.backgroundAssetId ?? ''),
|
||||
).toBe('scene-asset-runtime');
|
||||
});
|
||||
|
||||
test('works fixture 与 library fixture 对齐同一 published profile', () => {
|
||||
const works = createRpgCreationWorksResponseFixture();
|
||||
const libraryEntry = createRpgWorldLibraryEntryFixture();
|
||||
|
||||
const publishedWork = works.items.find((entry) => entry.status === 'published');
|
||||
|
||||
expect(publishedWork?.profileId).toBe(libraryEntry.profileId);
|
||||
expect(publishedWork?.title).toBe(libraryEntry.worldName);
|
||||
expect(publishedWork?.canEnterWorld).toBe(true);
|
||||
expect(libraryEntry.profile.id).toBe(libraryEntry.profileId);
|
||||
});
|
||||
|
||||
test('regression: works fixture 需要稳定保留草稿与发布态的作品门槛字段', () => {
|
||||
const works = createRpgCreationWorksResponseFixture();
|
||||
const draftWork = works.items.find((entry) => entry.status === 'draft');
|
||||
const publishedWork = works.items.find((entry) => entry.status === 'published');
|
||||
|
||||
expect(draftWork).toMatchObject({
|
||||
stage: 'ready_to_publish',
|
||||
stageLabel: '准备发布',
|
||||
canResume: true,
|
||||
canEnterWorld: false,
|
||||
roleVisualReadyCount: 2,
|
||||
roleAnimationReadyCount: 2,
|
||||
});
|
||||
expect(publishedWork).toMatchObject({
|
||||
stage: 'published',
|
||||
stageLabel: '已发布',
|
||||
canResume: false,
|
||||
canEnterWorld: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user