Files
Genarrative/src/services/match3dGeneratedModelCache.test.ts
2026-05-14 01:11:58 +08:00

267 lines
8.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { afterEach, describe, expect, test, vi } from 'vitest';
import { setStoredAccessToken, clearStoredAccessToken } from './apiClient';
import {
clearMatch3DGeneratedModelBytesCache,
getMatch3DGeneratedImageViewSources,
getMatch3DGeneratedImageAssetSources,
getMatch3DGeneratedModelAssetSources,
hasMatch3DGeneratedImageAsset,
preloadMatch3DGeneratedImageAssets,
preloadMatch3DGeneratedModelAssets,
readMatch3DGeneratedModelBytes,
} from './match3dGeneratedModelCache';
describe('match3dGeneratedModelCache', () => {
afterEach(() => {
vi.restoreAllMocks();
clearMatch3DGeneratedModelBytesCache();
clearStoredAccessToken({ emit: false });
});
test('预加载生成模型字节并复用本地缓存', async () => {
setStoredAccessToken('test-access-token', { emit: false });
vi.spyOn(globalThis, 'fetch').mockResolvedValue(
new Response(new Uint8Array([103, 108, 84, 70]), {
status: 200,
headers: {
'Content-Type': 'model/gltf-binary',
},
}),
);
await preloadMatch3DGeneratedModelAssets(
[
{
itemId: 'match3d-item-1',
itemName: '草莓',
imageSrc: null,
imageObjectKey: null,
modelSrc:
'/generated-match3d-assets/session/profile/items/match3d-item-1/model.glb',
modelObjectKey: null,
modelFileName: 'strawberry.glb',
taskUuid: null,
subscriptionKey: null,
status: 'model_ready',
error: null,
},
],
{ expireSeconds: 300 },
);
const bytes = await readMatch3DGeneratedModelBytes(
'/generated-match3d-assets/session/profile/items/match3d-item-1/model.glb',
{ expireSeconds: 300 },
);
expect(Array.from(new Uint8Array(bytes))).toEqual([103, 108, 84, 70]);
expect(globalThis.fetch).toHaveBeenCalledTimes(1);
});
test('模型源列表会去重并兼容 modelObjectKey', () => {
const sources = getMatch3DGeneratedModelAssetSources([
{
itemId: 'match3d-item-1',
itemName: '草莓',
imageSrc: null,
imageObjectKey: null,
modelSrc: null,
modelObjectKey:
'generated-match3d-assets/session/profile/items/match3d-item-1/model.glb',
modelFileName: 'strawberry.glb',
taskUuid: null,
subscriptionKey: null,
status: 'model_ready',
error: null,
},
{
itemId: 'match3d-item-1-duplicate',
itemName: '草莓副本',
imageSrc: null,
imageObjectKey: null,
modelSrc:
'generated-match3d-assets/session/profile/items/match3d-item-1/model.glb',
modelObjectKey: null,
modelFileName: 'strawberry.glb',
taskUuid: null,
subscriptionKey: null,
status: 'model_ready',
error: null,
},
]);
expect(sources).toEqual([
'generated-match3d-assets/session/profile/items/match3d-item-1/model.glb',
]);
});
test('同时存在外部 modelSrc 和平台 modelObjectKey 时优先预加载平台对象', () => {
const sources = getMatch3DGeneratedModelAssetSources([
{
itemId: 'match3d-item-legacy',
itemName: '苹果',
imageSrc: null,
imageObjectKey: null,
modelSrc: 'https://rodin.example.com/expired/model.glb',
modelObjectKey:
'generated-match3d-assets/session/profile/items/match3d-item-legacy/model.glb',
modelFileName: 'apple.glb',
taskUuid: null,
subscriptionKey: null,
status: 'model_ready',
error: null,
},
]);
expect(sources).toEqual([
'generated-match3d-assets/session/profile/items/match3d-item-legacy/model.glb',
]);
});
test('多视角图片源优先使用 imageViews兼容首图只做兜底', () => {
const sources = getMatch3DGeneratedImageViewSources({
itemId: 'match3d-item-1',
itemName: '草莓',
imageSrc:
'/generated-match3d-assets/session/profile/items/item-1/legacy-primary.png',
imageObjectKey:
'generated-match3d-assets/session/profile/items/item-1/legacy-primary.png',
imageViews: [1, 2, 3, 4, 5].map((viewIndex) => ({
viewId: `view-${String(viewIndex).padStart(2, '0')}`,
viewIndex,
imageSrc: `/generated-match3d-assets/session/profile/items/item-1/views/view-${String(viewIndex).padStart(2, '0')}.png`,
imageObjectKey: null,
})),
modelSrc: null,
modelObjectKey: null,
modelFileName: null,
taskUuid: null,
subscriptionKey: null,
status: 'image_ready',
error: null,
});
expect(sources).toHaveLength(5);
expect(sources[0]).toContain('views/view-01.png');
expect(sources[4]).toContain('views/view-05.png');
expect(sources.some((source) => source.includes('legacy-primary'))).toBe(
false,
);
});
test('运行态图片素材判断只认物品图片,不把背景或音频当物品素材', () => {
expect(
hasMatch3DGeneratedImageAsset([
{
itemId: 'match3d-item-1',
itemName: '草莓',
imageSrc: null,
imageObjectKey: null,
imageViews: [],
modelSrc: null,
modelObjectKey: null,
status: 'image_ready',
backgroundMusic: {
taskId: 'music-task-1',
provider: 'vector-engine-suno',
assetObjectId: 'asset-music-1',
assetKind: 'match3d_background_music',
audioSrc:
'/generated-match3d-assets/session/profile/audio/background.mp3',
prompt: '',
title: '果园轻舞',
updatedAt: '2026-05-13T10:00:00.000Z',
},
backgroundAsset: {
prompt: '果园背景',
imageSrc:
'/generated-match3d-assets/session/profile/background/background.png',
imageObjectKey: null,
containerPrompt: '果园浅盘',
containerImageSrc:
'/generated-match3d-assets/session/profile/ui-container/container.png',
containerImageObjectKey: null,
status: 'image_ready',
error: null,
},
},
]),
).toBe(false);
expect(
hasMatch3DGeneratedImageAsset([
{
itemId: 'match3d-item-1',
itemName: '草莓',
imageSrc: null,
imageObjectKey: null,
imageViews: [
{
viewId: 'view-01',
viewIndex: 1,
imageSrc:
'/generated-match3d-assets/session/profile/items/item-1/views/view-01.png',
imageObjectKey: null,
},
],
modelSrc: null,
modelObjectKey: null,
status: 'image_ready',
},
]),
).toBe(true);
});
test('运行态预加载使用 2D 图片源而不是旧模型源', async () => {
setStoredAccessToken('test-access-token', { emit: false });
vi.spyOn(globalThis, 'fetch').mockResolvedValue(
new Response(
JSON.stringify({
read: {
signedUrl: 'https://oss.example.com/view-01.png',
expiresAt: new Date(Date.now() + 60_000).toISOString(),
},
}),
{
status: 200,
headers: { 'Content-Type': 'application/json' },
},
),
);
const assets = [
{
itemId: 'match3d-item-1',
itemName: '草莓',
imageSrc: null,
imageObjectKey: null,
imageViews: [
{
viewId: 'view-01',
viewIndex: 1,
imageSrc: null,
imageObjectKey:
'generated-match3d-assets/session/profile/items/item-1/views/view-01.png',
},
],
modelSrc:
'/generated-match3d-assets/session/profile/items/item-1/model/model.glb',
modelObjectKey: null,
status: 'image_ready',
},
];
expect(getMatch3DGeneratedImageAssetSources(assets)).toEqual([
'generated-match3d-assets/session/profile/items/item-1/views/view-01.png',
]);
await preloadMatch3DGeneratedImageAssets(assets, { expireSeconds: 300 });
expect(globalThis.fetch).toHaveBeenCalledTimes(1);
expect(String(vi.mocked(globalThis.fetch).mock.calls[0]?.[0])).toContain(
'/api/assets/read-url',
);
expect(String(vi.mocked(globalThis.fetch).mock.calls[0]?.[0])).toContain(
'views%2Fview-01.png',
);
});
});