import { expect, test } from 'vitest'; import type { CustomWorldAgentSessionSnapshot } from '../../../packages/shared/src/contracts/customWorldAgent'; import { buildRpgCreationPreviewFromResultPreview, buildRpgCreationPreviewFromResultView, buildRpgCreationPreviewFromSession, } from './rpgCreationPreviewAdapter'; const sessionWithPreview: CustomWorldAgentSessionSnapshot = { sessionId: 'session-preview-1', currentTurn: 3, anchorContent: { worldPromise: null, playerFantasy: null, themeBoundary: null, playerEntryPoint: null, coreConflict: null, keyRelationships: null, hiddenLines: null, iconicElements: null, }, progressPercent: 100, lastAssistantReply: '第一版世界底稿已经准备好了。', stage: 'object_refining', focusCardId: null, creatorIntent: null, creatorIntentReadiness: { isReady: true, completedKeys: [], missingKeys: [], }, anchorPack: null, lockState: null, draftProfile: { id: 'draft-profile-1', settingText: '草稿 profile 直接进入游戏。', name: '只作为 fallback 的本地草稿名', subtitle: 'fallback', summary: 'fallback', tone: 'fallback', playerGoal: 'fallback', templateWorldType: 'WUXIA', majorFactions: [], coreConflicts: [], attributeSchema: { id: 'schema:draft:test', worldId: 'custom:草稿', schemaVersion: 1, generatedFrom: { worldType: 'CUSTOM', worldName: '只作为 fallback 的本地草稿名', settingSummary: '草稿 profile 直接进入游戏。', tone: 'fallback', conflictCore: '验证草稿直读链路', }, slots: [ { slotId: 'axis_a', name: '稿骨', }, { slotId: 'axis_b', name: '稿步', }, { slotId: 'axis_c', name: '稿识', }, { slotId: 'axis_d', name: '稿魄', }, { slotId: 'axis_e', name: '稿契', }, { slotId: 'axis_f', name: '稿澜', }, ], }, playableNpcs: [ { id: 'draft-playable-1', name: '草稿角色', title: '直读测试', role: '可扮演角色', description: '从 draftProfile 直接进入角色选择页。', backstory: '草稿角色的背景不经过 resultPreview 转换。', personality: '直接、清醒', motivation: '验证草稿直读链路', combatStyle: '以直读链路破局', initialAffinity: 18, relationshipHooks: ['来自草稿'], tags: ['draft-profile'], skills: [], initialItems: [], imageSrc: '/generated-characters/draft-playable-1/portrait.png', }, ], storyNpcs: [], items: [], landmarks: [], }, messages: [], draftCards: [ { id: 'world-foundation', kind: 'world', title: '世界底稿', subtitle: '阶段三预览', summary: '测试服务端 result preview 优先级。', status: 'warning', linkedIds: [], warningCount: 0, }, ], pendingClarifications: [], suggestedActions: [], recommendedReplies: [], qualityFindings: [], assetCoverage: { roleAssets: [], sceneAssets: [], allRoleAssetsReady: false, allSceneAssetsReady: false, }, resultPreview: { source: 'session_preview', preview: { id: 'preview-profile-1', settingText: '被海雾吞没的旧航路群岛', name: '服务端结果预览', subtitle: '优先于前端 fallback', summary: '结果页应该优先消费 session.resultPreview。', tone: '压抑、潮湿、悬疑', playerGoal: '查清沉船与禁航区异动的真相。', templateWorldType: 'WUXIA', majorFactions: ['守灯会', '航运公会'], coreConflicts: ['守灯会与航运公会争夺旧航路控制权'], playableNpcs: [], storyNpcs: [], items: [], landmarks: [], generationMode: 'full', generationStatus: 'complete', sessionId: 'session-preview-1', }, generatedAt: '2026-04-21T10:00:00.000Z', qualityFindings: [], blockers: [], }, updatedAt: '2026-04-21T10:00:00.000Z', }; test('buildRpgCreationPreviewFromResultPreview normalizes server preview envelope', () => { const profile = buildRpgCreationPreviewFromResultPreview( sessionWithPreview.resultPreview, ); expect(profile?.name).toBe('服务端结果预览'); expect(profile?.subtitle).toBe('优先于前端 fallback'); expect(profile?.id).toBe('preview-profile-1'); expect(profile?.settingText).toBe('被海雾吞没的旧航路群岛'); }); test('buildRpgCreationPreviewFromSession prefers server result preview', () => { const profile = buildRpgCreationPreviewFromSession(sessionWithPreview); expect(profile?.name).toBe('服务端结果预览'); expect(profile?.summary).toBe('结果页应该优先消费 session.resultPreview。'); expect(profile?.id).toBe('preview-profile-1'); expect(profile?.playableNpcs).toEqual([]); }); test('buildRpgCreationPreviewFromSession no longer reads draft legacy result profile', () => { const profile = buildRpgCreationPreviewFromSession({ ...sessionWithPreview, resultPreview: null, draftProfile: { ...sessionWithPreview.draftProfile, legacyResultProfile: { ...sessionWithPreview.resultPreview!.preview, id: 'legacy-result-profile-1', name: '草稿内嵌结果页', summary: 'resultPreview 缺失时继续使用 draft 内嵌的结果页快照。', }, }, }); expect(profile).toBeNull(); }); test('buildRpgCreationPreviewFromResultView consumes backend-selected profile', () => { const profile = buildRpgCreationPreviewFromResultView({ session: { ...sessionWithPreview, resultPreview: null, }, profile: { ...sessionWithPreview.resultPreview!.preview, id: 'backend-selected-profile-1', name: '后端结果页真相', summary: 'legacy 兼容只允许在后端 result-view 内完成。', }, profileSource: 'draft_profile', targetStage: 'custom-world-result', generationViewSource: null, resultViewSource: 'agent-draft', canAutosaveLibrary: true, canSyncResultProfile: true, publishReady: false, canEnterWorld: false, blockerCount: 0, recoveryAction: 'open_result', }); expect(profile?.name).toBe('后端结果页真相'); expect(profile?.summary).toBe('legacy 兼容只允许在后端 result-view 内完成。'); expect(profile?.id).toBe('backend-selected-profile-1'); }); test('buildRpgCreationPreviewFromSession does not treat draftProfile as runtime profile', () => { const profile = buildRpgCreationPreviewFromSession({ ...sessionWithPreview, resultPreview: null, }); expect(profile).toBeNull(); });