diff --git a/client/tests/integration/api/client.test.ts b/client/tests/integration/api/client.test.ts index 529fc715..0b71af53 100644 --- a/client/tests/integration/api/client.test.ts +++ b/client/tests/integration/api/client.test.ts @@ -334,12 +334,14 @@ describe('API client interceptors', () => { // ── backupApi.download ─────────────────────────────────────────────────────── it('FE-API-013: backupApi.download creates a temp anchor and clicks it', async () => { - // backupApi.download uses native fetch (not axios), so mock it directly to - // avoid jsdom/MSW interception differences across environments. + // backupApi.download uses native fetch (not axios). Mock fetch directly and + // use a plain-object Response duck-type to avoid MSW patching the Response + // constructor (which calls blob.stream() — not implemented in jsdom's Blob). const blob = new Blob(['zip-bytes'], { type: 'application/zip' }); - vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce( - new Response(blob, { status: 200 }) - ); + vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce({ + ok: true, + blob: () => Promise.resolve(blob), + } as unknown as Response); const createObjectURL = vi.spyOn(URL, 'createObjectURL').mockReturnValue('blob:mock-url'); const revokeObjectURL = vi.spyOn(URL, 'revokeObjectURL').mockImplementation(() => {}); diff --git a/client/tests/unit/api/authUrl.test.ts b/client/tests/unit/api/authUrl.test.ts index 23e5f23a..16e3aead 100644 --- a/client/tests/unit/api/authUrl.test.ts +++ b/client/tests/unit/api/authUrl.test.ts @@ -9,6 +9,12 @@ const flushPromises = () => new Promise(r => setTimeout(r, 10)); beforeEach(() => { clearImageQueue(); vi.restoreAllMocks(); // restore any vi.spyOn() wrappers from the previous test + + // jsdom's URL.createObjectURL returns '' and may be non-configurable, so + // Object.defineProperty in setup.ts can fail silently on CI. Assign directly + // here (after restoreAllMocks) so every test in this file gets a fresh mock. + URL.createObjectURL = vi.fn(() => 'blob:mock') as typeof URL.createObjectURL; + URL.revokeObjectURL = vi.fn() as typeof URL.revokeObjectURL; }); // ── getAuthUrl ───────────────────────────────────────────────────────────────── @@ -193,7 +199,6 @@ describe('clearImageQueue', () => { it('removes queued items so they never execute after active slots drain', async () => { const resolvers: Array<() => void> = []; const createObjectURLSpy = vi.spyOn(URL, 'createObjectURL'); - createObjectURLSpy.mockClear(); // vi.spyOn returns the same vi.fn() set in setup.ts; reset accumulated calls from prior tests vi.spyOn(globalThis, 'fetch').mockImplementation(async () => { await new Promise(r => resolvers.push(r));