import { describe, expect, it } from 'vitest'; import { AppError } from '@/lib/error-model'; import { classifyGameAssetReviewLoadFailure, isGameAssetReviewResponse, } from '@/pages/Chat/game-asset-review-recovery'; const validReview = { invocationId: 'review-1', candidateIds: ['hero'], decisions: {}, status: 'pending', pendingAssetIds: ['hero'], approvedAssetIds: [], discardedAssetIds: [], }; const validAsset = { id: 'hero', name: '主角', category: 'visual', status: 'candidate', purpose: '玩家角色', source: 'Kenney', license: 'CC0', mediaKind: 'image', manifest: [], }; describe('game asset review recovery', () => { it.each([400, 403, 404, 409, 410, 422])('classifies HTTP %s as stale', (status) => { const error = new AppError('UNKNOWN', 'hidden technical error', undefined, { status }); expect(classifyGameAssetReviewLoadFailure(error)).toBe('stale'); }); it.each([500, 502, 503])('classifies HTTP %s as retryable', (status) => { const error = new AppError('UNKNOWN', 'hidden technical error', undefined, { status }); expect(classifyGameAssetReviewLoadFailure(error)).toBe('retryable'); }); it('classifies network and timeout errors as retryable', () => { expect(classifyGameAssetReviewLoadFailure(new AppError('NETWORK', 'fetch failed'))).toBe('retryable'); expect(classifyGameAssetReviewLoadFailure(new AppError('TIMEOUT', 'timed out'))).toBe('retryable'); }); it('accepts the current response contract', () => { expect(isGameAssetReviewResponse({ review: validReview, assets: [validAsset] })).toBe(true); }); it.each([ { review: validReview }, { review: { ...validReview, pendingAssetIds: undefined }, assets: [validAsset] }, { review: validReview, assets: [{ ...validAsset, manifest: undefined }] }, { review: validReview, assets: [{ ...validAsset, status: { unsafe: true } }] }, { review: validReview, assets: [{ ...validAsset, previewDataUrl: 42 }] }, { review: { ...validReview, decisions: { hero: 'corrupt' } }, assets: [validAsset] }, ])('rejects malformed response %#', (response) => { expect(isGameAssetReviewResponse(response)).toBe(false); }); });