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).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, }); }); });