717 lines
20 KiB
TypeScript
717 lines
20 KiB
TypeScript
import { describe, expect, test } from 'vitest';
|
|
|
|
import type {
|
|
JumpHopWorkSummaryResponse,
|
|
} from '../../../packages/shared/src/contracts/jumpHop';
|
|
import type {
|
|
PuzzleAnchorPack,
|
|
PuzzleResultDraft,
|
|
} from '../../../packages/shared/src/contracts/puzzleAgentDraft';
|
|
import type { PuzzleAgentSessionSnapshot } from '../../../packages/shared/src/contracts/puzzleAgentSession';
|
|
import type {
|
|
SquareHoleResultDraft,
|
|
SquareHoleSessionSnapshot,
|
|
} from '../../../packages/shared/src/contracts/squareHoleAgent';
|
|
import type {
|
|
VisualNovelResultDraft,
|
|
VisualNovelWorkDetail,
|
|
} from '../../../packages/shared/src/contracts/visualNovel';
|
|
import type {
|
|
WoodenFishAudioAsset,
|
|
WoodenFishImageAsset,
|
|
WoodenFishSessionSnapshotResponse,
|
|
WoodenFishWorkProfileResponse,
|
|
WoodenFishWorkspaceCreateRequest,
|
|
WoodenFishWorkSummaryResponse,
|
|
} from '../../../packages/shared/src/contracts/woodenFish';
|
|
import {
|
|
buildJumpHopPendingSession,
|
|
buildPuzzleRuntimeWorkFromSession,
|
|
buildSquareHoleProfileFromSession,
|
|
buildVisualNovelSessionFromWorkDetail,
|
|
buildWoodenFishGeneratingWorkSummary,
|
|
buildWoodenFishPendingSession,
|
|
buildWoodenFishSessionFromWorkDetail,
|
|
} from './platformMiniGameSessionMappingModel';
|
|
|
|
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 buildPuzzleDraft(
|
|
overrides: Partial<PuzzleResultDraft> = {},
|
|
): PuzzleResultDraft {
|
|
const anchorPack = buildAnchorPack();
|
|
return {
|
|
workTitle: '星桥拼图',
|
|
workDescription: '修复星桥机关。',
|
|
levelName: '星桥机关',
|
|
summary: '把星桥碎片拼回原位。',
|
|
themeTags: ['星桥'],
|
|
forbiddenDirectives: [],
|
|
creatorIntent: null,
|
|
anchorPack,
|
|
candidates: [],
|
|
selectedCandidateId: null,
|
|
coverImageSrc: '/puzzle-cover.png',
|
|
coverAssetId: 'asset-cover',
|
|
generationStatus: 'ready',
|
|
levels: [
|
|
{
|
|
levelId: 'level-1',
|
|
levelName: '星桥机关',
|
|
pictureDescription: '星桥',
|
|
candidates: [],
|
|
selectedCandidateId: null,
|
|
coverImageSrc: '/puzzle-level-cover.png',
|
|
coverAssetId: 'asset-level-cover',
|
|
generationStatus: 'ready',
|
|
},
|
|
],
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function buildPuzzleSession(
|
|
overrides: Partial<PuzzleAgentSessionSnapshot> = {},
|
|
): PuzzleAgentSessionSnapshot {
|
|
const draft = buildPuzzleDraft();
|
|
return {
|
|
sessionId: 'puzzle-session-12345678',
|
|
seedText: '星桥',
|
|
currentTurn: 1,
|
|
progressPercent: 100,
|
|
stage: 'ready_to_publish',
|
|
anchorPack: draft.anchorPack,
|
|
draft,
|
|
messages: [],
|
|
lastAssistantReply: null,
|
|
publishedProfileId: null,
|
|
suggestedActions: [],
|
|
resultPreview: {
|
|
draft,
|
|
blockers: [],
|
|
qualityFindings: [],
|
|
publishReady: true,
|
|
},
|
|
updatedAt: '2026-06-01T10:00:00.000Z',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function buildJumpHopSummary(
|
|
overrides: Partial<JumpHopWorkSummaryResponse> = {},
|
|
): JumpHopWorkSummaryResponse {
|
|
return {
|
|
runtimeKind: 'jump-hop',
|
|
workId: 'jump-hop-work-1',
|
|
profileId: 'jump-hop-profile-1',
|
|
ownerUserId: 'user-1',
|
|
sourceSessionId: ' jump-hop-session-1 ',
|
|
themeText: '云阶机关',
|
|
workTitle: '云阶跳跃',
|
|
workDescription: '越过云阶。',
|
|
themeTags: ['云阶'],
|
|
difficulty: 'standard',
|
|
stylePreset: 'paper-toy',
|
|
coverImageSrc: '/jump-hop-cover.png',
|
|
publicationStatus: 'draft',
|
|
playCount: 0,
|
|
updatedAt: '2026-06-01T11:00:00.000Z',
|
|
publishedAt: null,
|
|
publishReady: false,
|
|
generationStatus: 'generating',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function buildSquareHoleDraft(
|
|
overrides: Partial<SquareHoleResultDraft> = {},
|
|
): SquareHoleResultDraft {
|
|
return {
|
|
profileId: 'square-hole-profile-1',
|
|
gameName: '星桥方洞',
|
|
themeText: '星桥机关',
|
|
twistRule: '只允许相同颜色形状入洞',
|
|
summary: '把星桥机关里的形状送入正确孔洞。',
|
|
tags: ['星桥', '机关'],
|
|
coverImageSrc: '/square-hole-cover.png',
|
|
backgroundPrompt: '星桥机关背景',
|
|
backgroundImageSrc: '/square-hole-background.png',
|
|
shapeOptions: [
|
|
{
|
|
optionId: 'shape-1',
|
|
shapeKind: 'star',
|
|
label: '星形',
|
|
targetHoleId: 'hole-1',
|
|
imagePrompt: '星形积木',
|
|
imageSrc: '/shape-star.png',
|
|
},
|
|
],
|
|
holeOptions: [
|
|
{
|
|
holeId: 'hole-1',
|
|
holeKind: 'star-hole',
|
|
label: '星形洞',
|
|
imagePrompt: '星形洞口',
|
|
imageSrc: '/hole-star.png',
|
|
},
|
|
],
|
|
shapeCount: 6,
|
|
difficulty: 3,
|
|
publishReady: true,
|
|
blockers: [],
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function buildSquareHoleSession(
|
|
overrides: Partial<SquareHoleSessionSnapshot> = {},
|
|
): SquareHoleSessionSnapshot {
|
|
return {
|
|
sessionId: 'square-hole-session-1',
|
|
currentTurn: 2,
|
|
progressPercent: 100,
|
|
stage: 'draft_ready',
|
|
anchorPack: {
|
|
theme: {
|
|
key: 'theme',
|
|
label: '主题',
|
|
value: '星桥机关',
|
|
status: 'confirmed',
|
|
},
|
|
twistRule: {
|
|
key: 'twistRule',
|
|
label: '扭转规则',
|
|
value: '只允许相同颜色形状入洞',
|
|
status: 'confirmed',
|
|
},
|
|
shapeCount: {
|
|
key: 'shapeCount',
|
|
label: '形状数量',
|
|
value: '6',
|
|
status: 'confirmed',
|
|
},
|
|
difficulty: {
|
|
key: 'difficulty',
|
|
label: '难度',
|
|
value: '3',
|
|
status: 'confirmed',
|
|
},
|
|
},
|
|
config: {
|
|
themeText: '星桥机关',
|
|
twistRule: '只允许相同颜色形状入洞',
|
|
shapeCount: 6,
|
|
difficulty: 3,
|
|
shapeOptions: [],
|
|
holeOptions: [],
|
|
backgroundPrompt: '星桥机关背景',
|
|
coverImageSrc: null,
|
|
backgroundImageSrc: null,
|
|
},
|
|
draft: buildSquareHoleDraft(),
|
|
messages: [],
|
|
lastAssistantReply: null,
|
|
publishedProfileId: null,
|
|
updatedAt: '2026-06-01T12:30:00.000Z',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function buildVisualNovelDraft(
|
|
overrides: Partial<VisualNovelResultDraft> = {},
|
|
): VisualNovelResultDraft {
|
|
return {
|
|
profileId: 'visual-novel-profile-1',
|
|
workTitle: '雪线电台',
|
|
workDescription: '旧电台牵出雪夜列车谜案。',
|
|
workTags: ['雪夜', '电台'],
|
|
coverImageSrc: '/visual-novel-cover.png',
|
|
sourceMode: 'idea',
|
|
sourceAssetIds: ['asset-source-1'],
|
|
world: {
|
|
title: '北境终点线',
|
|
summary: '边境小城与旧电台。',
|
|
background: '十二年前的雪崩留下夜间广播。',
|
|
premise: '玩家需要在日出前找出列车停摆的原因。',
|
|
literaryStyle: '克制冷光感。',
|
|
playerRole: '临时广播员',
|
|
defaultTone: '安静紧张',
|
|
},
|
|
characters: [
|
|
{
|
|
characterId: 'vn-char-1',
|
|
name: '林遥',
|
|
gender: '女',
|
|
role: 'main',
|
|
appearance: '灰色长外套。',
|
|
personality: '谨慎敏锐。',
|
|
tone: '短句多。',
|
|
background: '旧电台夜班实习生。',
|
|
relationshipToPlayer: '临时搭档',
|
|
imageAssets: [],
|
|
defaultExpression: 'calm',
|
|
isPlayerVisible: false,
|
|
},
|
|
],
|
|
scenes: [
|
|
{
|
|
sceneId: 'vn-scene-1',
|
|
name: '风雪站台',
|
|
description: '站灯忽明忽暗。',
|
|
backgroundImageSrc: null,
|
|
musicSrc: null,
|
|
ambientSoundSrc: null,
|
|
availability: 'opening',
|
|
phaseIds: ['vn-phase-1'],
|
|
},
|
|
],
|
|
storyPhases: [
|
|
{
|
|
phaseId: 'vn-phase-1',
|
|
title: '重启站台',
|
|
goal: '确认列车为何停在废弃站台。',
|
|
summary: '玩家抵达风雪站台。',
|
|
entryCondition: '开场进入',
|
|
exitCondition: '找到车长日志',
|
|
sceneIds: ['vn-scene-1'],
|
|
characterIds: ['vn-char-1'],
|
|
suggestedChoices: ['检查广播柜'],
|
|
},
|
|
],
|
|
opening: {
|
|
sceneId: 'vn-scene-1',
|
|
narration: '雪落得很慢。',
|
|
speakerCharacterId: 'vn-char-1',
|
|
firstDialogue: '你听见了吗?',
|
|
initialChoices: [
|
|
{
|
|
choiceId: 'vn-choice-1',
|
|
text: '靠近广播柜。',
|
|
actionHint: 'inspect_radio',
|
|
},
|
|
],
|
|
},
|
|
runtimeConfig: {
|
|
textModeEnabled: true,
|
|
defaultTextMode: false,
|
|
maxHistoryEntries: 80,
|
|
maxAssistantStepCountPerTurn: 8,
|
|
allowFreeTextAction: true,
|
|
allowHistoryRegeneration: true,
|
|
attributePanelMode: 'template_config',
|
|
saveArchiveEnabled: true,
|
|
},
|
|
publishReady: true,
|
|
validationIssues: [],
|
|
updatedAt: '2026-06-01T13:00:00.000Z',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function buildVisualNovelWorkDetail(
|
|
overrides: Partial<VisualNovelWorkDetail> = {},
|
|
): VisualNovelWorkDetail {
|
|
const draft = buildVisualNovelDraft();
|
|
return {
|
|
workId: 'visual-novel-work-1',
|
|
summary: {
|
|
runtimeKind: 'visual-novel',
|
|
profileId: 'visual-novel-profile-1',
|
|
ownerUserId: 'user-visual-novel-1',
|
|
title: draft.workTitle,
|
|
description: draft.workDescription,
|
|
coverImageSrc: draft.coverImageSrc,
|
|
tags: draft.workTags,
|
|
publishStatus: 'draft',
|
|
publishReady: draft.publishReady,
|
|
playCount: 0,
|
|
updatedAt: '2026-06-01T13:30:00.000Z',
|
|
publishedAt: null,
|
|
},
|
|
sourceSessionId: ' visual-novel-session-1 ',
|
|
authorDisplayName: '视觉小说作者',
|
|
sourceAssetIds: draft.sourceAssetIds,
|
|
draft,
|
|
createdAt: '2026-06-01T12:50:00.000Z',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
const woodenFishImageAsset: WoodenFishImageAsset = {
|
|
assetId: 'asset-hit',
|
|
imageSrc: '/hit.png',
|
|
imageObjectKey: 'hit.png',
|
|
assetObjectId: 'asset-object-hit',
|
|
generationProvider: 'test',
|
|
prompt: '木鱼',
|
|
width: 512,
|
|
height: 512,
|
|
};
|
|
|
|
const woodenFishAudioAsset: WoodenFishAudioAsset = {
|
|
assetId: 'asset-sound',
|
|
audioSrc: '/hit.mp3',
|
|
audioObjectKey: 'hit.mp3',
|
|
assetObjectId: 'asset-object-sound',
|
|
source: 'test',
|
|
};
|
|
|
|
function buildWoodenFishSummary(
|
|
overrides: Partial<WoodenFishWorkSummaryResponse> = {},
|
|
): WoodenFishWorkSummaryResponse {
|
|
return {
|
|
runtimeKind: 'wooden-fish',
|
|
workId: 'wooden-fish-work-1',
|
|
profileId: 'wooden-fish-profile-1',
|
|
ownerUserId: 'user-1',
|
|
sourceSessionId: ' wooden-fish-session-1 ',
|
|
workTitle: '星灯木鱼',
|
|
workDescription: '敲亮星灯。',
|
|
themeTags: ['星灯'],
|
|
coverImageSrc: '/wooden-fish-cover.png',
|
|
publicationStatus: 'draft',
|
|
playCount: 0,
|
|
updatedAt: '2026-06-01T12:00:00.000Z',
|
|
publishedAt: null,
|
|
publishReady: false,
|
|
generationStatus: 'generating',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function buildWoodenFishWorkProfile(
|
|
overrides: Partial<WoodenFishWorkProfileResponse> = {},
|
|
): WoodenFishWorkProfileResponse {
|
|
const summary = buildWoodenFishSummary();
|
|
const draft = {
|
|
templateId: 'wooden-fish',
|
|
templateName: '敲木鱼',
|
|
profileId: summary.profileId,
|
|
workTitle: summary.workTitle,
|
|
workDescription: summary.workDescription,
|
|
themeTags: summary.themeTags,
|
|
hitObjectPrompt: '星灯',
|
|
hitObjectReferenceImageSrc: null,
|
|
hitSoundPrompt: null,
|
|
floatingWords: ['功德 +1'],
|
|
hitObjectAsset: woodenFishImageAsset,
|
|
backgroundAsset: null,
|
|
backButtonAsset: null,
|
|
hitSoundAsset: woodenFishAudioAsset,
|
|
coverImageSrc: summary.coverImageSrc,
|
|
generationStatus: summary.generationStatus,
|
|
};
|
|
return {
|
|
summary,
|
|
draft,
|
|
hitObjectAsset: woodenFishImageAsset,
|
|
backgroundAsset: null,
|
|
backButtonAsset: null,
|
|
hitSoundAsset: woodenFishAudioAsset,
|
|
floatingWords: ['功德 +1'],
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function buildWoodenFishSession(
|
|
overrides: Partial<WoodenFishSessionSnapshotResponse> = {},
|
|
): WoodenFishSessionSnapshotResponse {
|
|
const summary = buildWoodenFishSummary();
|
|
return {
|
|
sessionId: 'wooden-fish-session-1',
|
|
ownerUserId: 'user-1',
|
|
status: 'generating',
|
|
draft: buildWoodenFishWorkProfile({ summary }).draft,
|
|
createdAt: '2026-06-01T11:59:00.000Z',
|
|
updatedAt: '2026-06-01T12:00:00.000Z',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function buildWoodenFishCreatePayload(
|
|
overrides: Partial<WoodenFishWorkspaceCreateRequest> = {},
|
|
): WoodenFishWorkspaceCreateRequest {
|
|
return {
|
|
templateId: 'wooden-fish',
|
|
workTitle: '表单星灯木鱼',
|
|
workDescription: '表单里敲亮星灯。',
|
|
themeTags: ['表单星灯'],
|
|
hitObjectPrompt: '星灯',
|
|
hitObjectReferenceImageSrc: null,
|
|
hitSoundPrompt: null,
|
|
floatingWords: ['功德 +1'],
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('platformMiniGameSessionMappingModel', () => {
|
|
test('builds a draft puzzle runtime work from a session', () => {
|
|
expect(
|
|
buildPuzzleRuntimeWorkFromSession(buildPuzzleSession(), {
|
|
userId: 'user-1',
|
|
displayName: '玩家一号',
|
|
}),
|
|
).toMatchObject({
|
|
workId: 'puzzle-work-12345678',
|
|
profileId: 'puzzle-profile-12345678',
|
|
ownerUserId: 'user-1',
|
|
sourceSessionId: 'puzzle-session-12345678',
|
|
authorDisplayName: '玩家一号',
|
|
workTitle: '星桥拼图',
|
|
coverImageSrc: '/puzzle-cover.png',
|
|
publicationStatus: 'draft',
|
|
publishedAt: null,
|
|
publishReady: true,
|
|
});
|
|
});
|
|
|
|
test('prefers published puzzle profile id when present', () => {
|
|
expect(
|
|
buildPuzzleRuntimeWorkFromSession(
|
|
buildPuzzleSession({
|
|
publishedProfileId: 'published-puzzle-profile',
|
|
}),
|
|
{},
|
|
),
|
|
).toMatchObject({
|
|
profileId: 'published-puzzle-profile',
|
|
workId: 'puzzle-work-12345678',
|
|
ownerUserId: 'current-user',
|
|
authorDisplayName: '玩家',
|
|
});
|
|
});
|
|
|
|
test('returns null for puzzle runtime work without draft or cover', () => {
|
|
expect(
|
|
buildPuzzleRuntimeWorkFromSession(
|
|
buildPuzzleSession({
|
|
draft: null,
|
|
}),
|
|
{},
|
|
),
|
|
).toBeNull();
|
|
expect(
|
|
buildPuzzleRuntimeWorkFromSession(
|
|
buildPuzzleSession({
|
|
draft: buildPuzzleDraft({ coverImageSrc: ' ' }),
|
|
}),
|
|
{},
|
|
),
|
|
).toBeNull();
|
|
});
|
|
|
|
test('builds jump hop pending session from work summary', () => {
|
|
expect(buildJumpHopPendingSession(buildJumpHopSummary())).toEqual({
|
|
sessionId: 'jump-hop-session-1',
|
|
ownerUserId: 'user-1',
|
|
status: 'generating',
|
|
draft: {
|
|
templateId: 'jump-hop',
|
|
templateName: '跳一跳',
|
|
profileId: 'jump-hop-profile-1',
|
|
themeText: '云阶机关',
|
|
workTitle: '云阶跳跃',
|
|
workDescription: '越过云阶。',
|
|
themeTags: ['云阶'],
|
|
difficulty: 'standard',
|
|
stylePreset: 'paper-toy',
|
|
characterPrompt: '',
|
|
tilePrompt: '',
|
|
endMoodPrompt: null,
|
|
characterAsset: null,
|
|
tileAtlasAsset: null,
|
|
tileAssets: [],
|
|
path: null,
|
|
coverComposite: '/jump-hop-cover.png',
|
|
generationStatus: 'generating',
|
|
},
|
|
createdAt: '2026-06-01T11:00:00.000Z',
|
|
updatedAt: '2026-06-01T11:00:00.000Z',
|
|
});
|
|
});
|
|
|
|
test('builds square hole draft profile from session', () => {
|
|
expect(buildSquareHoleProfileFromSession(buildSquareHoleSession())).toEqual({
|
|
workId: 'square-hole-profile-1',
|
|
profileId: 'square-hole-profile-1',
|
|
ownerUserId: 'current-user',
|
|
sourceSessionId: 'square-hole-session-1',
|
|
gameName: '星桥方洞',
|
|
themeText: '星桥机关',
|
|
twistRule: '只允许相同颜色形状入洞',
|
|
summary: '把星桥机关里的形状送入正确孔洞。',
|
|
tags: ['星桥', '机关'],
|
|
coverImageSrc: '/square-hole-cover.png',
|
|
backgroundPrompt: '星桥机关背景',
|
|
backgroundImageSrc: '/square-hole-background.png',
|
|
shapeOptions: buildSquareHoleDraft().shapeOptions,
|
|
holeOptions: buildSquareHoleDraft().holeOptions,
|
|
shapeCount: 6,
|
|
difficulty: 3,
|
|
publicationStatus: 'draft',
|
|
playCount: 0,
|
|
updatedAt: '2026-06-01T12:30:00.000Z',
|
|
publishedAt: null,
|
|
publishReady: true,
|
|
});
|
|
});
|
|
|
|
test('returns null for square hole profile without session draft or profile id', () => {
|
|
expect(buildSquareHoleProfileFromSession(null)).toBeNull();
|
|
expect(
|
|
buildSquareHoleProfileFromSession(
|
|
buildSquareHoleSession({
|
|
draft: null,
|
|
}),
|
|
),
|
|
).toBeNull();
|
|
expect(
|
|
buildSquareHoleProfileFromSession(
|
|
buildSquareHoleSession({
|
|
draft: buildSquareHoleDraft({ profileId: '' }),
|
|
}),
|
|
),
|
|
).toBeNull();
|
|
});
|
|
|
|
test('builds visual novel recovered session from work detail', () => {
|
|
const work = buildVisualNovelWorkDetail();
|
|
|
|
expect(buildVisualNovelSessionFromWorkDetail(work)).toEqual({
|
|
sessionId: 'visual-novel-session-1',
|
|
ownerUserId: 'user-visual-novel-1',
|
|
sourceMode: 'idea',
|
|
status: 'ready',
|
|
messages: [],
|
|
draft: work.draft,
|
|
pendingAction: null,
|
|
createdAt: '2026-06-01T12:50:00.000Z',
|
|
updatedAt: '2026-06-01T13:30:00.000Z',
|
|
});
|
|
});
|
|
|
|
test('falls back visual novel recovered session id to work id', () => {
|
|
expect(
|
|
buildVisualNovelSessionFromWorkDetail(
|
|
buildVisualNovelWorkDetail({
|
|
sourceSessionId: ' ',
|
|
workId: 'visual-novel-work-fallback',
|
|
}),
|
|
).sessionId,
|
|
).toBe('visual-novel-work-fallback');
|
|
});
|
|
|
|
test('builds wooden fish pending session from work summary', () => {
|
|
expect(buildWoodenFishPendingSession(buildWoodenFishSummary())).toEqual({
|
|
sessionId: 'wooden-fish-session-1',
|
|
ownerUserId: 'user-1',
|
|
status: 'generating',
|
|
draft: {
|
|
templateId: 'wooden-fish',
|
|
templateName: '敲木鱼',
|
|
profileId: 'wooden-fish-profile-1',
|
|
workTitle: '星灯木鱼',
|
|
workDescription: '敲亮星灯。',
|
|
themeTags: ['星灯'],
|
|
hitObjectPrompt: '',
|
|
hitObjectReferenceImageSrc: null,
|
|
hitSoundPrompt: null,
|
|
floatingWords: ['功德 +1'],
|
|
hitObjectAsset: null,
|
|
backgroundAsset: null,
|
|
backButtonAsset: null,
|
|
hitSoundAsset: null,
|
|
coverImageSrc: '/wooden-fish-cover.png',
|
|
generationStatus: 'generating',
|
|
},
|
|
createdAt: '2026-06-01T12:00:00.000Z',
|
|
updatedAt: '2026-06-01T12:00:00.000Z',
|
|
});
|
|
});
|
|
|
|
test('builds wooden fish generating work summary from session and payload', () => {
|
|
expect(
|
|
buildWoodenFishGeneratingWorkSummary(
|
|
buildWoodenFishSession(),
|
|
buildWoodenFishCreatePayload(),
|
|
),
|
|
).toEqual({
|
|
runtimeKind: 'wooden-fish',
|
|
workId: 'wooden-fish-session-1',
|
|
profileId: 'wooden-fish-session-1',
|
|
ownerUserId: 'user-1',
|
|
sourceSessionId: 'wooden-fish-session-1',
|
|
workTitle: '表单星灯木鱼',
|
|
workDescription: '表单里敲亮星灯。',
|
|
themeTags: ['表单星灯'],
|
|
coverImageSrc: '/wooden-fish-cover.png',
|
|
publicationStatus: 'draft',
|
|
playCount: 0,
|
|
updatedAt: '2026-06-01T12:00:00.000Z',
|
|
publishedAt: null,
|
|
publishReady: false,
|
|
generationStatus: 'generating',
|
|
});
|
|
|
|
expect(
|
|
buildWoodenFishGeneratingWorkSummary(
|
|
buildWoodenFishSession({
|
|
draft: null,
|
|
createdAt: '2026-06-01T11:59:00.000Z',
|
|
}),
|
|
null,
|
|
),
|
|
).toMatchObject({
|
|
workTitle: '敲木鱼',
|
|
workDescription: '',
|
|
themeTags: ['敲木鱼'],
|
|
coverImageSrc: null,
|
|
updatedAt: '2026-06-01T12:00:00.000Z',
|
|
});
|
|
});
|
|
|
|
test('builds wooden fish recovered session with summary, fallback and profile id priority', () => {
|
|
expect(
|
|
buildWoodenFishSessionFromWorkDetail(
|
|
buildWoodenFishWorkProfile({
|
|
summary: buildWoodenFishSummary({
|
|
sourceSessionId: null,
|
|
}),
|
|
}),
|
|
buildWoodenFishSummary({
|
|
sourceSessionId: ' fallback-session ',
|
|
}),
|
|
),
|
|
).toMatchObject({
|
|
sessionId: 'fallback-session',
|
|
ownerUserId: 'user-1',
|
|
status: 'generating',
|
|
});
|
|
|
|
expect(
|
|
buildWoodenFishSessionFromWorkDetail(
|
|
buildWoodenFishWorkProfile({
|
|
summary: buildWoodenFishSummary({
|
|
sourceSessionId: null,
|
|
}),
|
|
}),
|
|
null,
|
|
).sessionId,
|
|
).toBe('wooden-fish-profile-1');
|
|
});
|
|
});
|