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:
parent
7a0548ac94
commit
e65ec37c9e
1 changed files with 117 additions and 132 deletions
|
|
@ -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,80 +77,35 @@ 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);
|
success: true,
|
||||||
|
staging_path: '/music/Staging',
|
||||||
if (url.includes('/api/import/staging/files')) {
|
files: stagingFilesPayload,
|
||||||
return createResponse({
|
});
|
||||||
success: true,
|
}),
|
||||||
staging_path: '/music/Staging',
|
http.get('/api/import/staging/groups', () => {
|
||||||
files: stagingFilesPayload,
|
return HttpResponse.json({
|
||||||
});
|
success: true,
|
||||||
}
|
groups: [
|
||||||
|
{
|
||||||
if (url.includes('/api/import/staging/groups')) {
|
album: 'Album A',
|
||||||
return createResponse({
|
artist: 'Artist A',
|
||||||
success: true,
|
file_count: 2,
|
||||||
groups: [
|
file_paths: ['/music/Staging/Album/01-track.flac'],
|
||||||
{
|
},
|
||||||
album: 'Album A',
|
],
|
||||||
artist: 'Artist A',
|
});
|
||||||
file_count: 2,
|
}),
|
||||||
file_paths: ['/music/Staging/Album/01-track.flac'],
|
http.get('/api/import/staging/suggestions', () => {
|
||||||
},
|
return HttpResponse.json({
|
||||||
],
|
success: true,
|
||||||
});
|
ready: true,
|
||||||
}
|
suggestions: [
|
||||||
|
{
|
||||||
if (url.includes('/api/import/staging/suggestions')) {
|
|
||||||
return createResponse({
|
|
||||||
success: true,
|
|
||||||
ready: true,
|
|
||||||
suggestions: [
|
|
||||||
{
|
|
||||||
id: 'album-1',
|
|
||||||
name: 'Album A',
|
|
||||||
artist: 'Artist A',
|
|
||||||
source: 'deezer',
|
|
||||||
total_tracks: 1,
|
|
||||||
release_date: '2026-01-01',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (url.includes('/api/import/search/albums')) {
|
|
||||||
return createResponse({
|
|
||||||
success: true,
|
|
||||||
albums: [
|
|
||||||
{
|
|
||||||
id: 'album-1',
|
|
||||||
name: 'Album A',
|
|
||||||
artist: 'Artist A',
|
|
||||||
source: 'deezer',
|
|
||||||
total_tracks: 1,
|
|
||||||
release_date: '2026-01-01',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (url.includes('/api/import/album/match')) {
|
|
||||||
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);
|
|
||||||
return createResponse({
|
|
||||||
success: true,
|
|
||||||
received: body,
|
|
||||||
album: {
|
|
||||||
id: 'album-1',
|
id: 'album-1',
|
||||||
name: 'Album A',
|
name: 'Album A',
|
||||||
artist: 'Artist A',
|
artist: 'Artist A',
|
||||||
|
|
@ -164,56 +113,94 @@ describe('import route', () => {
|
||||||
total_tracks: 1,
|
total_tracks: 1,
|
||||||
release_date: '2026-01-01',
|
release_date: '2026-01-01',
|
||||||
},
|
},
|
||||||
matches: [
|
],
|
||||||
{
|
});
|
||||||
track: { name: 'Track One', track_number: 1 },
|
}),
|
||||||
staging_file: {
|
http.get('/api/import/search/albums', () => {
|
||||||
filename: '01-track.flac',
|
return HttpResponse.json({
|
||||||
full_path: '/music/Staging/Album/01-track.flac',
|
success: true,
|
||||||
},
|
albums: [
|
||||||
confidence: 0.95,
|
{
|
||||||
|
id: 'album-1',
|
||||||
|
name: 'Album A',
|
||||||
|
artist: 'Artist A',
|
||||||
|
source: 'deezer',
|
||||||
|
total_tracks: 1,
|
||||||
|
release_date: '2026-01-01',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
http.post('/api/import/album/match', async ({ request }) => {
|
||||||
|
const body = (await request.json()) as Record<string, unknown>;
|
||||||
|
albumMatchBodies.push(body);
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: true,
|
||||||
|
received: body,
|
||||||
|
album: {
|
||||||
|
id: 'album-1',
|
||||||
|
name: 'Album A',
|
||||||
|
artist: 'Artist A',
|
||||||
|
source: 'deezer',
|
||||||
|
total_tracks: 1,
|
||||||
|
release_date: '2026-01-01',
|
||||||
|
},
|
||||||
|
matches: [
|
||||||
|
{
|
||||||
|
track: { name: 'Track One', track_number: 1 },
|
||||||
|
staging_file: {
|
||||||
|
filename: '01-track.flac',
|
||||||
|
full_path: '/music/Staging/Album/01-track.flac',
|
||||||
},
|
},
|
||||||
],
|
confidence: 0.95,
|
||||||
});
|
},
|
||||||
}
|
],
|
||||||
|
});
|
||||||
if (url.includes('/api/auto-import/status')) {
|
}),
|
||||||
return createResponse({
|
http.get('/api/auto-import/status', () => {
|
||||||
success: true,
|
return HttpResponse.json({
|
||||||
running: true,
|
success: true,
|
||||||
current_status: 'idle',
|
running: true,
|
||||||
active_imports: [],
|
current_status: 'idle',
|
||||||
});
|
active_imports: [],
|
||||||
}
|
});
|
||||||
|
}),
|
||||||
if (url.includes('/api/auto-import/settings')) {
|
http.get('/api/auto-import/settings', () => {
|
||||||
return createResponse({
|
return HttpResponse.json({
|
||||||
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: [
|
{
|
||||||
{
|
id: 4,
|
||||||
id: 4,
|
status: 'pending_review',
|
||||||
status: 'pending_review',
|
folder_hash: 'hash-1',
|
||||||
folder_hash: 'hash-1',
|
folder_name: 'Album A',
|
||||||
folder_name: 'Album A',
|
album_name: 'Album A',
|
||||||
album_name: 'Album A',
|
artist_name: 'Artist A',
|
||||||
artist_name: 'Artist A',
|
confidence: 0.82,
|
||||||
confidence: 0.82,
|
total_files: 2,
|
||||||
total_files: 2,
|
},
|
||||||
},
|
],
|
||||||
],
|
});
|
||||||
});
|
}),
|
||||||
}
|
http.get('/api/issues/counts', () => {
|
||||||
|
return HttpResponse.json({
|
||||||
return createResponse({ success: true });
|
success: true,
|
||||||
}) as unknown as typeof fetch,
|
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');
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue