import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; import { clearSignedAssetReadUrlCache, getSignedAssetReadUrl, resolveAssetReadUrl, } from './assetReadUrlService'; describe('assetReadUrlService', () => { beforeEach(() => { clearSignedAssetReadUrlCache(); vi.restoreAllMocks(); }); afterEach(() => { vi.useRealTimers(); }); test('resolveAssetReadUrl returns passthrough for absolute url', async () => { await expect(resolveAssetReadUrl('https://example.com/demo.png')).resolves.toBe( 'https://example.com/demo.png', ); }); test('resolveAssetReadUrl returns passthrough for data url', async () => { await expect(resolveAssetReadUrl('data:image/png;base64,abc')).resolves.toBe( 'data:image/png;base64,abc', ); }); test('resolveAssetReadUrl exchanges legacy generated path for signed url', async () => { vi.spyOn(globalThis, 'fetch').mockResolvedValue( new Response( JSON.stringify({ ok: true, data: { read: { objectKey: 'generated-characters/hero/visual/asset-01/master.png', signedUrl: 'https://signed.example.com/master.png', expiresAt: '2099-01-01T00:10:00Z', }, }, error: null, meta: { apiVersion: '2026-04-08', routeVersion: '2026-04-08', latencyMs: 1, timestamp: '2099-01-01T00:00:00Z', }, }), { status: 200, headers: { 'Content-Type': 'application/json', }, }, ), ); await expect( resolveAssetReadUrl('/generated-characters/hero/visual/asset-01/master.png'), ).resolves.toBe('https://signed.example.com/master.png'); expect(globalThis.fetch).toHaveBeenCalledTimes(1); expect(String(vi.mocked(globalThis.fetch).mock.calls[0]?.[0])).toContain( '/api/assets/read-url?', ); }); test('getSignedAssetReadUrl reuses cached signed url before expiry', async () => { vi.useFakeTimers(); vi.setSystemTime(new Date('2099-01-01T00:00:00Z')); vi.spyOn(globalThis, 'fetch').mockResolvedValue( new Response( JSON.stringify({ ok: true, data: { read: { objectKey: 'generated-custom-world-scenes/profile-1/landmark-1/scene.png', signedUrl: 'https://signed.example.com/scene.png', expiresAt: '2099-01-01T00:10:00Z', }, }, error: null, meta: { apiVersion: '2026-04-08', routeVersion: '2026-04-08', latencyMs: 1, timestamp: '2099-01-01T00:00:00Z', }, }), { status: 200, headers: { 'Content-Type': 'application/json', }, }, ), ); const first = await getSignedAssetReadUrl({ legacyPublicPath: '/generated-custom-world-scenes/profile-1/landmark-1/scene.png', }); const second = await getSignedAssetReadUrl({ legacyPublicPath: '/generated-custom-world-scenes/profile-1/landmark-1/scene.png', }); expect(first).toBe('https://signed.example.com/scene.png'); expect(second).toBe(first); expect(globalThis.fetch).toHaveBeenCalledTimes(1); }); });