import { describe, expect, test } from 'vitest'; import type { PuzzleAnchorPack, PuzzleDraftLevel, PuzzleGeneratedImageCandidate, PuzzleResultDraft, } from '../../../packages/shared/src/contracts/puzzleAgentDraft'; import type { PuzzleAgentSessionSnapshot } from '../../../packages/shared/src/contracts/puzzleAgentSession'; import { hasRecoverableGeneratedPuzzleDraft, normalizeRecoveredPuzzleDraftSession, } from './platformPuzzleDraftRecoveryModel'; function buildAnchorPack(): PuzzleAnchorPack { const item = { key: 'theme', label: '主题', value: '星桥机关', status: 'confirmed' as const, }; return { themePromise: item, visualSubject: item, visualMood: item, compositionHooks: item, tagsAndForbidden: item, }; } function buildCandidate( overrides: Partial = {}, ): PuzzleGeneratedImageCandidate { return { candidateId: 'candidate-1', imageSrc: '/candidate-cover.png', assetId: 'asset-candidate-cover', prompt: '星桥机关', sourceType: 'generated', selected: true, ...overrides, }; } function buildLevel(overrides: Partial = {}): PuzzleDraftLevel { return { levelId: 'level-1', levelName: '星桥机关', pictureDescription: '星桥机关画面', candidates: [buildCandidate()], selectedCandidateId: null, coverImageSrc: null, coverAssetId: null, generationStatus: 'generating', ...overrides, }; } function buildDraft(overrides: Partial = {}): PuzzleResultDraft { const anchorPack = buildAnchorPack(); return { workTitle: '星桥拼图', workDescription: '修复星桥机关。', levelName: '星桥机关', summary: '把碎片拼回原位。', themeTags: ['星桥', '机关', '修复'], forbiddenDirectives: [], creatorIntent: null, anchorPack, candidates: [], selectedCandidateId: null, coverImageSrc: null, coverAssetId: null, generationStatus: 'generating', levels: [buildLevel()], ...overrides, }; } function buildSession( overrides: Partial = {}, ): PuzzleAgentSessionSnapshot { const anchorPack = buildAnchorPack(); return { sessionId: 'puzzle-session-1', seedText: '星桥', currentTurn: 1, progressPercent: 100, stage: 'draft_ready', anchorPack, draft: buildDraft(), messages: [], lastAssistantReply: null, publishedProfileId: null, suggestedActions: [], resultPreview: null, updatedAt: '2026-06-01T10:00:00.000Z', ...overrides, }; } function withCompleteLevelAssets( overrides: Partial = {}, ): PuzzleDraftLevel { return buildLevel({ levelSceneImageSrc: '/level-scene.png', uiSpritesheetImageSrc: '/ui-spritesheet.png', levelBackgroundImageSrc: '/level-background.png', ...overrides, }); } describe('platformPuzzleDraftRecoveryModel', () => { test('normalizes and marks recovered puzzle draft ready when asset pack is complete', () => { const normalized = normalizeRecoveredPuzzleDraftSession( buildSession({ draft: buildDraft({ levels: [withCompleteLevelAssets()], }), }), ); expect(hasRecoverableGeneratedPuzzleDraft(normalized)).toBe(true); expect(normalized.draft).toMatchObject({ coverImageSrc: '/candidate-cover.png', coverAssetId: 'asset-candidate-cover', selectedCandidateId: 'candidate-1', generationStatus: 'ready', }); expect(normalized.draft?.levels?.[0]).toMatchObject({ coverImageSrc: '/candidate-cover.png', coverAssetId: 'asset-candidate-cover', selectedCandidateId: 'candidate-1', generationStatus: 'ready', }); }); test('keeps half-finished draft generating when only cover candidate exists', () => { const normalized = normalizeRecoveredPuzzleDraftSession(buildSession()); expect(hasRecoverableGeneratedPuzzleDraft(normalized)).toBe(false); expect(normalized.draft).toMatchObject({ coverImageSrc: '/candidate-cover.png', generationStatus: 'generating', }); expect(normalized.draft?.levels?.[0]).toMatchObject({ coverImageSrc: '/candidate-cover.png', generationStatus: 'generating', }); }); test('requires level scene, ui spritesheet and level background assets together', () => { expect( hasRecoverableGeneratedPuzzleDraft( buildSession({ draft: buildDraft({ coverImageSrc: '/draft-cover.png', levels: [ withCompleteLevelAssets({ uiSpritesheetImageSrc: null, uiSpritesheetImageObjectKey: null, }), ], }), }), ), ).toBe(false); }); test('accepts object keys as recovered asset references', () => { expect( hasRecoverableGeneratedPuzzleDraft( buildSession({ draft: buildDraft({ coverImageSrc: '/draft-cover.png', levels: [ buildLevel({ levelSceneImageObjectKey: 'level-scene.png', uiSpritesheetImageObjectKey: 'ui-spritesheet.png', levelBackgroundImageObjectKey: 'level-background.png', }), ], }), }), ), ).toBe(true); }); test('leaves sessions without draft unchanged and unrecoverable', () => { const session = buildSession({ draft: null }); expect(normalizeRecoveredPuzzleDraftSession(session)).toBe(session); expect(hasRecoverableGeneratedPuzzleDraft(session)).toBe(false); }); });