231 lines
7.7 KiB
TypeScript
231 lines
7.7 KiB
TypeScript
import { describe, expect, test } from 'vitest';
|
|
|
|
import {
|
|
BARK_BATTLE_ASSET_SLOTS,
|
|
BARK_BATTLE_DIFFICULTY_PRESETS,
|
|
type BarkBattleDraftConfig,
|
|
type BarkBattleDraftConfigUpdateRequest,
|
|
type BarkBattleFinishResponse,
|
|
type BarkBattleGeneratedImageAsset,
|
|
type BarkBattleImageAssetGenerateRequest,
|
|
type BarkBattlePersonalBestSummary,
|
|
type BarkBattleWorkStats,
|
|
} from './barkBattle';
|
|
|
|
describe('Bark Battle shared contracts', () => {
|
|
test('default draft config fixture uses normal difficulty and v1 description fields', () => {
|
|
const draft: BarkBattleDraftConfig = {
|
|
draftId: 'draft-bark-1',
|
|
workId: 'work-bark-1',
|
|
configVersion: 2,
|
|
rulesetVersion: 'bark-battle-ruleset-v1',
|
|
title: '汪汪声浪挑战',
|
|
description: '轻配置草稿',
|
|
themeDescription: '傍晚城市公园里的声浪擂台',
|
|
playerImageDescription: '戴红围巾的柯基主角',
|
|
opponentImageDescription: '蓝色运动头带的哈士奇对手',
|
|
onomatopoeia: ['轰汪!', '嗷呜!', '咚咚!'],
|
|
playerCharacterImageSrc: '/generated-bark-battle/player/image.png',
|
|
opponentCharacterImageSrc: 'https://example.test/opponent.png',
|
|
uiBackgroundImageSrc: '/generated-bark-battle/ui/background.png',
|
|
difficultyPreset: 'normal',
|
|
updatedAt: '2026-05-13T03:00:00.000Z',
|
|
};
|
|
|
|
expect(BARK_BATTLE_DIFFICULTY_PRESETS).toEqual(['easy', 'normal', 'hard']);
|
|
expect(draft.difficultyPreset).toBe('normal');
|
|
expect(Object.keys(draft)).toEqual([
|
|
'draftId',
|
|
'workId',
|
|
'configVersion',
|
|
'rulesetVersion',
|
|
'title',
|
|
'description',
|
|
'themeDescription',
|
|
'playerImageDescription',
|
|
'opponentImageDescription',
|
|
'onomatopoeia',
|
|
'playerCharacterImageSrc',
|
|
'opponentCharacterImageSrc',
|
|
'uiBackgroundImageSrc',
|
|
'difficultyPreset',
|
|
'updatedAt',
|
|
]);
|
|
expect(draft.playerCharacterImageSrc).toContain('/generated-bark-battle/');
|
|
expect('barkSoundSrc' in draft).toBe(false);
|
|
expect('leaderboardEnabled' in draft).toBe(false);
|
|
});
|
|
|
|
test('draft config update contract persists generated image slots only', () => {
|
|
const update: BarkBattleDraftConfigUpdateRequest = {
|
|
draftId: 'draft-bark-1',
|
|
workId: 'BB-12345678',
|
|
configVersion: 2,
|
|
rulesetVersion: 'bark-battle-ruleset-v1',
|
|
title: '汪汪声浪挑战',
|
|
description: '轻配置草稿',
|
|
themeDescription: '傍晚城市公园里的声浪擂台',
|
|
playerImageDescription: '戴红围巾的柯基主角',
|
|
opponentImageDescription: '蓝色运动头带的哈士奇对手',
|
|
onomatopoeia: ['轰!', '燃起来!', '破阵!'],
|
|
playerCharacterImageSrc: '/generated-bark-battle/player/image.png',
|
|
opponentCharacterImageSrc: '/generated-bark-battle/opponent/image.png',
|
|
uiBackgroundImageSrc: '/generated-bark-battle/ui/background.png',
|
|
difficultyPreset: 'normal',
|
|
};
|
|
|
|
expect(Object.keys(update)).toEqual([
|
|
'draftId',
|
|
'workId',
|
|
'configVersion',
|
|
'rulesetVersion',
|
|
'title',
|
|
'description',
|
|
'themeDescription',
|
|
'playerImageDescription',
|
|
'opponentImageDescription',
|
|
'onomatopoeia',
|
|
'playerCharacterImageSrc',
|
|
'opponentCharacterImageSrc',
|
|
'uiBackgroundImageSrc',
|
|
'difficultyPreset',
|
|
]);
|
|
expect('barkSoundSrc' in update).toBe(false);
|
|
expect('leaderboardEnabled' in update).toBe(false);
|
|
});
|
|
|
|
test('image generation contract uses dedicated Bark Battle slots and backend prompt result', () => {
|
|
expect(BARK_BATTLE_ASSET_SLOTS).toEqual([
|
|
'player-character',
|
|
'opponent-character',
|
|
'ui-background',
|
|
]);
|
|
|
|
const request: BarkBattleImageAssetGenerateRequest = {
|
|
slot: 'opponent-character',
|
|
draftId: 'bark-battle-draft-1',
|
|
config: {
|
|
title: '汪汪冠军杯',
|
|
description: '',
|
|
themeDescription: '霓虹公园擂台',
|
|
playerImageDescription: '红围巾柴犬',
|
|
opponentImageDescription: '蓝头带哈士奇',
|
|
onomatopoeia: ['轰汪!', '炸场!', '冲啊!'],
|
|
difficultyPreset: 'normal',
|
|
},
|
|
};
|
|
const response: BarkBattleGeneratedImageAsset = {
|
|
imageSrc: '/generated-bark-battle-assets/draft/opponent/image.webp',
|
|
assetId: 'asset-1',
|
|
sourceType: 'generated',
|
|
model: 'gpt-image-2',
|
|
size: '1024*1024',
|
|
taskId: 'task-1',
|
|
prompt: '后端拼装后的对手形象 prompt',
|
|
};
|
|
|
|
expect(JSON.parse(JSON.stringify(request))).toMatchObject({
|
|
slot: 'opponent-character',
|
|
config: {
|
|
opponentImageDescription: '蓝头带哈士奇',
|
|
onomatopoeia: ['轰汪!', '炸场!', '冲啊!'],
|
|
},
|
|
});
|
|
expect(JSON.parse(JSON.stringify(response))).toMatchObject({
|
|
imageSrc: '/generated-bark-battle-assets/draft/opponent/image.webp',
|
|
prompt: '后端拼装后的对手形象 prompt',
|
|
});
|
|
});
|
|
|
|
test('finish accepted player_win fixture exposes backend adjudication result', () => {
|
|
const response: BarkBattleFinishResponse = {
|
|
status: 'accepted',
|
|
runId: 'run-bark-1',
|
|
workId: 'work-bark-1',
|
|
configVersion: 3,
|
|
rulesetVersion: 'bark-battle-ruleset-v1',
|
|
difficultyPreset: 'hard',
|
|
serverResult: 'player_win',
|
|
scoreSummary: {
|
|
finalEnergy: 87,
|
|
triggerCount: 42,
|
|
maxVolume: 0.96,
|
|
averageVolume: 0.61,
|
|
comboMax: 9,
|
|
durationMs: 30000,
|
|
},
|
|
leaderboardScore: 870429630,
|
|
antiCheatFlags: [],
|
|
updatedAt: '2026-05-13T03:00:30.000Z',
|
|
};
|
|
|
|
expect(response.status).toBe('accepted');
|
|
expect(response.serverResult).toBe('player_win');
|
|
expect(response.scoreSummary.finalEnergy).toBe(87);
|
|
expect(response.antiCheatFlags).toEqual([]);
|
|
});
|
|
|
|
test('work stats fixture tracks starts, finishes, result counts, flags and energy summary', () => {
|
|
const stats: BarkBattleWorkStats = {
|
|
workId: 'work-bark-1',
|
|
configVersion: 3,
|
|
rulesetVersion: 'bark-battle-ruleset-v1',
|
|
difficultyPreset: 'normal',
|
|
playStartCount: 18,
|
|
finishCount: 15,
|
|
winCount: 8,
|
|
drawCount: 2,
|
|
lossCount: 5,
|
|
flaggedCount: 1,
|
|
leaderboardEntryCount: 7,
|
|
bestLeaderboardScore: 930389410,
|
|
bestFinalEnergy: 93,
|
|
averageFinalEnergy: 41.25,
|
|
updatedAt: '2026-05-13T04:00:00.000Z',
|
|
};
|
|
|
|
expect(stats.playStartCount).toBe(18);
|
|
expect(stats.finishCount).toBe(15);
|
|
expect(stats.winCount + stats.drawCount + stats.lossCount).toBe(15);
|
|
expect(stats.flaggedCount).toBe(1);
|
|
expect(stats.bestFinalEnergy).toBeGreaterThan(stats.averageFinalEnergy);
|
|
});
|
|
|
|
test('optional score fields may be omitted instead of serialized as null', () => {
|
|
const finishWithoutLeaderboard: BarkBattleFinishResponse = {
|
|
status: 'accepted',
|
|
runId: 'run-bark-no-rank',
|
|
workId: 'work-bark-1',
|
|
configVersion: 3,
|
|
rulesetVersion: 'bark-battle-ruleset-v1',
|
|
difficultyPreset: 'normal',
|
|
serverResult: 'draw',
|
|
scoreSummary: {
|
|
finalEnergy: 50,
|
|
triggerCount: 12,
|
|
maxVolume: 0.7,
|
|
averageVolume: 0.5,
|
|
comboMax: 3,
|
|
durationMs: 30000,
|
|
},
|
|
antiCheatFlags: [],
|
|
updatedAt: '2026-05-13T03:00:30.000Z',
|
|
};
|
|
const personalBestWithoutWin: BarkBattlePersonalBestSummary = {
|
|
workId: 'work-bark-1',
|
|
rulesetVersion: 'bark-battle-ruleset-v1',
|
|
difficultyPreset: 'normal',
|
|
winCount: 0,
|
|
drawCount: 1,
|
|
lossCount: 2,
|
|
finishCount: 3,
|
|
updatedAt: '2026-05-13T04:00:00.000Z',
|
|
};
|
|
|
|
expect('leaderboardScore' in finishWithoutLeaderboard).toBe(false);
|
|
expect('bestLeaderboardScore' in personalBestWithoutWin).toBe(false);
|
|
expect('bestFinalEnergy' in personalBestWithoutWin).toBe(false);
|
|
});
|
|
|
|
});
|