071faa482c
纳入 AGC Cargo workspace 的统一 rustfmt 检查与格式化入口 完成项目 TypeScript/Prettier 与 Rust 全量格式化 修复 Pingora expected executable 门禁的空白敏感误报 同步开发运维文档与 AGC skill pack 格式化忽略规则
200 lines
5.5 KiB
TypeScript
200 lines
5.5 KiB
TypeScript
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> = {},
|
|
): PuzzleGeneratedImageCandidate {
|
|
return {
|
|
candidateId: 'candidate-1',
|
|
imageSrc: '/candidate-cover.png',
|
|
assetId: 'asset-candidate-cover',
|
|
prompt: '星桥机关',
|
|
sourceType: 'generated',
|
|
selected: true,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function buildLevel(
|
|
overrides: Partial<PuzzleDraftLevel> = {},
|
|
): PuzzleDraftLevel {
|
|
return {
|
|
levelId: 'level-1',
|
|
levelName: '星桥机关',
|
|
pictureDescription: '星桥机关画面',
|
|
candidates: [buildCandidate()],
|
|
selectedCandidateId: null,
|
|
coverImageSrc: null,
|
|
coverAssetId: null,
|
|
generationStatus: 'generating',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function buildDraft(
|
|
overrides: Partial<PuzzleResultDraft> = {},
|
|
): 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> = {},
|
|
): 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> = {},
|
|
): 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);
|
|
});
|
|
});
|