import { http, HttpResponse, delay } from 'msw' import type { JobStatus } from '../types' const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:7860' // In-memory job store for mocking interface MockJob { id: string caseId: string status: JobStatus progress: number progressMessage: string elapsedSeconds: number fastMode: boolean createdAt: number } const mockJobs = new Map() let jobCounter = 0 // Configurable job duration for tests (ms) // Default: 500ms for fast tests let mockJobDurationMs = 500 /** * Set the mock job duration for tests. * Jobs will complete after this many milliseconds. */ export function setMockJobDuration(durationMs: number): void { mockJobDurationMs = durationMs } // Simulate job progression over time function getJobProgress(job: MockJob): MockJob { const elapsed = (Date.now() - job.createdAt) / 1000 const duration = mockJobDurationMs / 1000 // Convert to seconds if (job.status === 'completed' || job.status === 'failed') { return job } // Progress through stages based on elapsed time relative to configured duration // Stages: 20% loading, 40% inference, 30% processing, 10% finalizing const progress20 = duration * 0.2 const progress60 = duration * 0.6 const progress90 = duration * 0.9 if (elapsed < progress20) { return { ...job, status: 'running', progress: 10, progressMessage: 'Loading case data...', elapsedSeconds: elapsed } } else if (elapsed < progress60) { return { ...job, status: 'running', progress: 30, progressMessage: 'Running DeepISLES inference...', elapsedSeconds: elapsed } } else if (elapsed < progress90) { return { ...job, status: 'running', progress: 70, progressMessage: 'Processing results...', elapsedSeconds: elapsed } } else if (elapsed < duration) { return { ...job, status: 'running', progress: 90, progressMessage: 'Computing metrics...', elapsedSeconds: elapsed } } else { // Job complete return { ...job, status: 'completed', progress: 100, progressMessage: 'Segmentation complete', elapsedSeconds: elapsed } } } export const handlers = [ // GET /api/cases - List available cases http.get(`${API_BASE}/api/cases`, async () => { await delay(100) return HttpResponse.json({ cases: ['sub-stroke0001', 'sub-stroke0002', 'sub-stroke0003'], }) }), // POST /api/segment - Create segmentation job (returns immediately) http.post(`${API_BASE}/api/segment`, async ({ request }) => { const body = (await request.json()) as { case_id: string; fast_mode?: boolean } await delay(50) // Small delay to simulate network // Create a new job const jobId = `mock-${++jobCounter}` const job: MockJob = { id: jobId, caseId: body.case_id, status: 'pending', progress: 0, progressMessage: 'Job queued', elapsedSeconds: 0, fastMode: body.fast_mode !== false, createdAt: Date.now(), } mockJobs.set(jobId, job) // Return 202 Accepted with job ID return HttpResponse.json( { jobId: jobId, status: 'pending', message: `Segmentation job queued for ${body.case_id}`, }, { status: 202 } ) }), // GET /api/jobs/:jobId - Get job status http.get(`${API_BASE}/api/jobs/:jobId`, async ({ params }) => { const jobId = params.jobId as string await delay(50) // Small delay to simulate network const job = mockJobs.get(jobId) if (!job) { return HttpResponse.json( { detail: `Job not found: ${jobId}. Jobs expire after 1 hour.` }, { status: 404 } ) } // Update job progress based on elapsed time const updatedJob = getJobProgress(job) mockJobs.set(jobId, updatedJob) // Build response const response: Record = { jobId: updatedJob.id, status: updatedJob.status, progress: updatedJob.progress, progressMessage: updatedJob.progressMessage, elapsedSeconds: Math.round(updatedJob.elapsedSeconds * 100) / 100, } // Include result if completed if (updatedJob.status === 'completed') { response.result = { caseId: updatedJob.caseId, diceScore: 0.847, volumeMl: 15.32, elapsedSeconds: updatedJob.fastMode ? 12.5 : 45.0, dwiUrl: `${API_BASE}/files/${jobId}/${updatedJob.caseId}/dwi.nii.gz`, predictionUrl: `${API_BASE}/files/${jobId}/${updatedJob.caseId}/prediction.nii.gz`, } } return HttpResponse.json(response) }), ] // Error handlers for testing error states export const errorHandlers = { casesServerError: http.get(`${API_BASE}/api/cases`, () => { return HttpResponse.json( { detail: 'Internal server error' }, { status: 500 } ) }), casesNetworkError: http.get(`${API_BASE}/api/cases`, () => { return HttpResponse.error() }), segmentCreateError: http.post(`${API_BASE}/api/segment`, () => { return HttpResponse.json( { detail: 'Failed to create job: case not found' }, { status: 400 } ) }), jobNotFound: http.get(`${API_BASE}/api/jobs/:jobId`, () => { return HttpResponse.json( { detail: 'Job not found or expired' }, { status: 404 } ) }), // Simulate a job that fails during processing jobFailed: [ http.post(`${API_BASE}/api/segment`, async ({ request }) => { const body = (await request.json()) as { case_id: string } const jobId = `fail-${++jobCounter}` mockJobs.set(jobId, { id: jobId, caseId: body.case_id, status: 'failed', progress: 30, progressMessage: 'Error occurred', elapsedSeconds: 5.2, fastMode: true, createdAt: Date.now(), }) return HttpResponse.json( { jobId, status: 'pending', message: 'Job queued' }, { status: 202 } ) }), http.get(`${API_BASE}/api/jobs/:jobId`, ({ params }) => { const jobId = params.jobId as string const job = mockJobs.get(jobId) if (!job) { return HttpResponse.json({ detail: 'Not found' }, { status: 404 }) } return HttpResponse.json({ jobId: job.id, status: 'failed', progress: 30, progressMessage: 'Error occurred', elapsedSeconds: 5.2, error: 'Segmentation failed: out of memory', }) }), ], }