test(import): use MSW route handlers

- replace direct fetch stubs with shared MSW handlers
- keep fetch spying only for request assertions
- cover the shell prefetch with an issues counts handler
This commit is contained in:
Antti Kettunen 2026-05-18 21:30:35 +03:00
parent 7a0548ac94
commit e65ec37c9e
No known key found for this signature in database
GPG key ID: C6B2A3D250359BD7

View file

@ -3,6 +3,7 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest'; import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { ShellBridge, ShellPageId } from '@/platform/shell/bridge'; import type { ShellBridge, ShellPageId } from '@/platform/shell/bridge';
import { HttpResponse, http, server } from '@/test/msw';
import { createAppQueryClient } from '@/app/query-client'; import { createAppQueryClient } from '@/app/query-client';
import { AppRouterProvider, createAppRouter } from '@/app/router'; import { AppRouterProvider, createAppRouter } from '@/app/router';
@ -10,13 +11,6 @@ import { AppRouterProvider, createAppRouter } from '@/app/router';
import type { ImportStagingFile } from './-import.types'; import type { ImportStagingFile } from './-import.types';
import { resetImportWorkflowStore } from './-import.store'; import { resetImportWorkflowStore } from './-import.store';
function createResponse(body: unknown, status = 200) {
return new Response(JSON.stringify(body), {
status,
headers: { 'Content-Type': 'application/json' },
});
}
function createShellBridge(overrides: Partial<ShellBridge> = {}): ShellBridge { function createShellBridge(overrides: Partial<ShellBridge> = {}): ShellBridge {
return { return {
getCurrentProfileContext: vi.fn(() => ({ profileId: 2, isAdmin: true })), getCurrentProfileContext: vi.fn(() => ({ profileId: 2, isAdmin: true })),
@ -83,22 +77,18 @@ describe('import route', () => {
window.SoulSyncWebShellBridge = createShellBridge(); window.SoulSyncWebShellBridge = createShellBridge();
window.showToast = vi.fn(); window.showToast = vi.fn();
window.showConfirmDialog = vi.fn(async () => true); window.showConfirmDialog = vi.fn(async () => true);
vi.spyOn(globalThis, 'fetch');
vi.stubGlobal( server.use(
'fetch', http.get('/api/import/staging/files', () => {
vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => { return HttpResponse.json({
const url = input instanceof Request ? input.url : String(input);
if (url.includes('/api/import/staging/files')) {
return createResponse({
success: true, success: true,
staging_path: '/music/Staging', staging_path: '/music/Staging',
files: stagingFilesPayload, files: stagingFilesPayload,
}); });
} }),
http.get('/api/import/staging/groups', () => {
if (url.includes('/api/import/staging/groups')) { return HttpResponse.json({
return createResponse({
success: true, success: true,
groups: [ groups: [
{ {
@ -109,10 +99,9 @@ describe('import route', () => {
}, },
], ],
}); });
} }),
http.get('/api/import/staging/suggestions', () => {
if (url.includes('/api/import/staging/suggestions')) { return HttpResponse.json({
return createResponse({
success: true, success: true,
ready: true, ready: true,
suggestions: [ suggestions: [
@ -126,10 +115,9 @@ describe('import route', () => {
}, },
], ],
}); });
} }),
http.get('/api/import/search/albums', () => {
if (url.includes('/api/import/search/albums')) { return HttpResponse.json({
return createResponse({
success: true, success: true,
albums: [ albums: [
{ {
@ -142,18 +130,11 @@ describe('import route', () => {
}, },
], ],
}); });
} }),
http.post('/api/import/album/match', async ({ request }) => {
if (url.includes('/api/import/album/match')) { const body = (await request.json()) as Record<string, unknown>;
const body =
input instanceof Request
? ((await input.clone().json()) as Record<string, unknown>)
: (JSON.parse(typeof init?.body === 'string' ? init.body : '{}') as Record<
string,
unknown
>);
albumMatchBodies.push(body); albumMatchBodies.push(body);
return createResponse({ return HttpResponse.json({
success: true, success: true,
received: body, received: body,
album: { album: {
@ -175,27 +156,24 @@ describe('import route', () => {
}, },
], ],
}); });
} }),
http.get('/api/auto-import/status', () => {
if (url.includes('/api/auto-import/status')) { return HttpResponse.json({
return createResponse({
success: true, success: true,
running: true, running: true,
current_status: 'idle', current_status: 'idle',
active_imports: [], active_imports: [],
}); });
} }),
http.get('/api/auto-import/settings', () => {
if (url.includes('/api/auto-import/settings')) { return HttpResponse.json({
return createResponse({
success: true, success: true,
scan_interval: 60, scan_interval: 60,
confidence_threshold: 0.9, confidence_threshold: 0.9,
}); });
} }),
http.get('/api/auto-import/results', () => {
if (url.includes('/api/auto-import/results')) { return HttpResponse.json({
return createResponse({
success: true, success: true,
results: [ results: [
{ {
@ -210,10 +188,19 @@ describe('import route', () => {
}, },
], ],
}); });
} }),
http.get('/api/issues/counts', () => {
return createResponse({ success: true }); return HttpResponse.json({
}) as unknown as typeof fetch, success: true,
counts: {
open: 0,
in_progress: 0,
resolved: 0,
dismissed: 0,
total: 0,
},
});
}),
); );
}); });
@ -227,9 +214,7 @@ describe('import route', () => {
await waitFor(() => await waitFor(() =>
expect(getFetchUrls().some((url) => url.includes('/api/import/staging/groups'))).toBe(true), expect(getFetchUrls().some((url) => url.includes('/api/import/staging/groups'))).toBe(true),
); );
expect(getFetchUrls().some((url) => url.includes('/api/import/staging/suggestions'))).toBe( expect(getFetchUrls().some((url) => url.includes('/api/import/staging/suggestions'))).toBe(true);
true,
);
expect(window.SoulSyncWebShellBridge?.showReactHost).toHaveBeenCalledWith('import'); expect(window.SoulSyncWebShellBridge?.showReactHost).toHaveBeenCalledWith('import');
expect(window.SoulSyncWebShellBridge?.setActivePageChrome).toHaveBeenCalledWith('import'); expect(window.SoulSyncWebShellBridge?.setActivePageChrome).toHaveBeenCalledWith('import');
}); });