import { expect, test } from 'vitest'; import type { CustomWorldAgentOperationRecord, CustomWorldAgentSessionSnapshot, } from '../../packages/shared/src/contracts/customWorldAgent'; import { buildAgentDraftFoundationGenerationProgress, buildAgentDraftFoundationSettingText, isDraftFoundationOperationRunning, } from './customWorldAgentGenerationProgress'; const baseOperation: CustomWorldAgentOperationRecord = { operationId: 'operation-1', type: 'draft_foundation', status: 'running', phaseLabel: '生成场景角色', phaseDetail: '正在生成场景角色第 1 / 1 批,当前已完成 0/4。', progress: 38, error: null, }; const baseSession: CustomWorldAgentSessionSnapshot = { sessionId: 'session-1', currentTurn: 8, anchorContent: { worldPromise: { hook: '海雾、旧灯塔和失控航路交织的边缘群岛', differentiator: '每次借路都要向海雾付出新的代价。', desiredExperience: '压抑、悬疑、潮湿', }, playerFantasy: { playerRole: '玩家刚回到群岛,准备调查父亲沉船的真相。', corePursuit: '查清沉船夜和禁航区异动的因果。', fearOfLoss: '再失去唯一还敢接近真相的人。', }, themeBoundary: null, playerEntryPoint: null, coreConflict: null, keyRelationships: [], hiddenLines: null, iconicElements: { iconicMotifs: ['会移动的海雾'], institutionsOrArtifacts: ['旧灯塔'], hardRules: [], }, }, progressPercent: 100, lastAssistantReply: '八锚点已经收束完成,可以进入游戏设定草稿生成。', stage: 'foundation_review', focusCardId: null, creatorIntent: { sourceMode: 'card', worldHook: '海雾、旧灯塔和失控航路交织的边缘群岛', themeKeywords: ['海雾', '灯塔', '旧航路'], toneDirectives: ['压抑', '悬疑'], playerPremise: '玩家刚回到群岛,准备调查父亲沉船的真相。', openingSituation: '首夜就有陌生船只在禁航区点灯。', coreConflicts: ['航运公会与守灯会争夺航路控制权'], keyFactions: [], keyCharacters: [], keyLandmarks: [], iconicElements: ['会移动的海雾'], forbiddenDirectives: [], rawSettingText: '', }, creatorIntentReadiness: { isReady: true, completedKeys: [], missingKeys: [], }, anchorPack: null, lockState: null, draftProfile: null, messages: [ { id: 'message-1', role: 'user', kind: 'chat', text: '我想做一个被海雾吞没的旧航路世界。', createdAt: '2026-04-14T10:00:00.000Z', relatedOperationId: null, }, ], draftCards: [], pendingClarifications: [], suggestedActions: [], recommendedReplies: [], qualityFindings: [], assetCoverage: { roleAssets: [], sceneAssets: [], allRoleAssetsReady: false, allSceneAssetsReady: false, }, updatedAt: '2026-04-14T10:00:00.000Z', }; test('maps running draft_foundation operation to refined generation progress steps', () => { const progress = buildAgentDraftFoundationGenerationProgress( baseOperation, 1_000, 5_000, ); expect(progress).not.toBeNull(); expect(progress?.phaseId).toBe('story-outline'); expect(progress?.batchLabel).toBe('生成场景角色'); expect(progress?.overallProgress).toBe(38); expect(progress?.elapsedMs).toBe(4_000); expect(progress?.estimatedRemainingMs).toBeGreaterThan(0); expect(progress?.steps).toHaveLength(13); expect(progress?.steps.map((step) => step.status)).toEqual([ 'completed', 'completed', 'completed', 'active', 'pending', 'pending', 'pending', 'pending', 'pending', 'pending', 'pending', 'pending', 'pending', ]); expect(isDraftFoundationOperationRunning(baseOperation)).toBe(true); }); test('calculates elapsed time from operation startedAt before local fallback', () => { const progress = buildAgentDraftFoundationGenerationProgress( { ...baseOperation, startedAt: '1970-01-01T00:00:01.000Z', }, 4_000, 6_000, ); expect(progress?.elapsedMs).toBe(5_000); }); test('maps auto asset phases to refined generation progress steps', () => { const progress = buildAgentDraftFoundationGenerationProgress( { ...baseOperation, phaseLabel: '生成幕背景图', phaseDetail: '正在生成幕背景图 3/6:潮汐码头 · 封锁加压。', progress: 99, }, 1_000, 5_000, ); expect(progress?.phaseId).toBe('act-backgrounds'); expect(progress?.batchLabel).toBe('生成幕背景图'); expect(progress?.steps.filter((step) => step.status === 'completed')).toHaveLength( 10, ); expect(progress?.steps[10]?.status).toBe('active'); }); test('marks all refined progress steps complete when draft foundation finishes', () => { const progress = buildAgentDraftFoundationGenerationProgress( { ...baseOperation, status: 'completed', phaseLabel: '世界底稿已生成', phaseDetail: '第一版世界底稿和 6 张草稿卡已经整理完成。', progress: 100, }, 1_000, 5_000, ); expect(progress?.phaseId).toBe('workspace'); expect(progress?.estimatedRemainingMs).toBe(0); expect(progress?.steps.every((step) => step.status === 'completed')).toBe( true, ); }); test('keeps failed draft foundation progress on explicit failure state instead of pretending it is still compiling cards', () => { const progress = buildAgentDraftFoundationGenerationProgress( { ...baseOperation, status: 'failed', phaseLabel: '底稿生成失败', phaseDetail: '角色主形象补齐失败,但世界底稿尚未完成写回。', progress: 100, error: 'dashscope timeout', }, 1_000, 5_000, ); expect(progress?.phaseId).toBe('failed'); expect(progress?.phaseLabel).toBe('底稿生成失败'); expect(progress?.phaseDetail).toContain('角色主形象补齐失败'); expect(progress?.overallProgress).toBeLessThan(100); expect(progress?.estimatedRemainingMs).toBeNull(); expect(progress?.steps.some((step) => step.label === '编译草稿卡')).toBe(true); expect(progress?.steps.some((step) => step.status === 'active')).toBe(false); expect(progress?.steps.filter((step) => step.status === 'completed').length).toBeGreaterThan(0); }); test('estimates draft generation wait time from phase duration model instead of linear progress', () => { const progress = buildAgentDraftFoundationGenerationProgress( { ...baseOperation, phaseLabel: '生成幕背景图', phaseDetail: '正在生成幕背景图 1/6:潮汐码头。', progress: 98, updatedAt: '1970-01-01T00:00:01.000Z', }, 1_000, 6_000, ); expect(progress?.estimatedRemainingMs).toBeGreaterThan(80_000); expect(progress?.estimatedRemainingMs).toBeLessThan(140_000); }); test('builds readable draft setting text from creator intent first', () => { const settingText = buildAgentDraftFoundationSettingText(baseSession); expect(settingText).toContain('世界一句话'); expect(settingText).toContain('玩家开局'); expect(settingText).toContain('标志元素'); }); test('falls back to anchor content when creator intent is unavailable', () => { const settingText = buildAgentDraftFoundationSettingText({ ...baseSession, creatorIntent: null, }); expect(settingText).toContain('世界承诺'); expect(settingText).toContain('玩家幻想'); });