import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { setStoredAccessToken } from '../apiClient'; import { generateEditorBackgroundMusic } from './editorProjectClient'; function createLocalStorageMock() { const store = new Map(); return { getItem(key: string) { return store.has(key) ? store.get(key)! : null; }, setItem(key: string, value: string) { store.set(key, String(value)); }, removeItem(key: string) { store.delete(key); }, clear() { store.clear(); }, }; } describe('editorProjectClient background music transport', () => { const fetchMock = vi.fn(); const requestBody = { gptDescriptionPrompt: '森林冒险背景音乐', makeInstrumental: true as const, projectId: 'editor-project-1', }; beforeEach(() => { fetchMock.mockReset(); vi.stubGlobal('fetch', fetchMock); vi.stubGlobal('window', { dispatchEvent: vi.fn(), localStorage: createLocalStorageMock(), }); setStoredAccessToken('background-music-test-token', { emit: false }); }); afterEach(() => { vi.unstubAllGlobals(); }); function expectSingleBackgroundMusicPost() { expect(fetchMock).toHaveBeenCalledTimes(1); const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit]; expect(url).toBe('/api/editor/audios/background-music/generations'); expect(init.method).toBe('POST'); expect(JSON.parse(String(init.body))).toEqual(requestBody); } it.each([429, 503])( 'does not retry the formal POST after an HTTP %i response', async (status) => { fetchMock.mockResolvedValueOnce(new Response('', { status })); await expect( generateEditorBackgroundMusic(requestBody), ).rejects.toMatchObject({ status }); expectSingleBackgroundMusicPost(); }, ); it('does not retry the formal POST after a transport error', async () => { fetchMock.mockRejectedValueOnce(new TypeError('Failed to fetch')); await expect( generateEditorBackgroundMusic(requestBody), ).rejects.toBeInstanceOf(TypeError); expectSingleBackgroundMusicPost(); }); });