267 lines
8.3 KiB
TypeScript
267 lines
8.3 KiB
TypeScript
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',
|
||
);
|
||
});
|
||
});
|