|
|
import { http, HttpResponse, delay } from "msw"; |
|
|
import type { JobStatus } from "../types"; |
|
|
|
|
|
const API_BASE = import.meta.env.VITE_API_URL || "http://localhost:7860"; |
|
|
|
|
|
|
|
|
interface MockJob { |
|
|
id: string; |
|
|
caseId: string; |
|
|
status: JobStatus; |
|
|
progress: number; |
|
|
progressMessage: string; |
|
|
elapsedSeconds: number; |
|
|
fastMode: boolean; |
|
|
createdAt: number; |
|
|
} |
|
|
|
|
|
const mockJobs = new Map<string, MockJob>(); |
|
|
let jobCounter = 0; |
|
|
|
|
|
|
|
|
|
|
|
let mockJobDurationMs = 500; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function setMockJobDuration(durationMs: number): void { |
|
|
mockJobDurationMs = durationMs; |
|
|
} |
|
|
|
|
|
|
|
|
function getJobProgress(job: MockJob): MockJob { |
|
|
const elapsed = (Date.now() - job.createdAt) / 1000; |
|
|
const duration = mockJobDurationMs / 1000; |
|
|
|
|
|
if (job.status === "completed" || job.status === "failed") { |
|
|
return job; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
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 { |
|
|
|
|
|
return { |
|
|
...job, |
|
|
status: "completed", |
|
|
progress: 100, |
|
|
progressMessage: "Segmentation complete", |
|
|
elapsedSeconds: elapsed, |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
export const handlers = [ |
|
|
|
|
|
http.get(`${API_BASE}/api/cases`, async () => { |
|
|
await delay(100); |
|
|
return HttpResponse.json({ |
|
|
cases: ["sub-stroke0001", "sub-stroke0002", "sub-stroke0003"], |
|
|
}); |
|
|
}), |
|
|
|
|
|
|
|
|
http.post(`${API_BASE}/api/segment`, async ({ request }) => { |
|
|
const body = (await request.json()) as { |
|
|
case_id: string; |
|
|
fast_mode?: boolean; |
|
|
}; |
|
|
await delay(50); |
|
|
|
|
|
|
|
|
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 HttpResponse.json( |
|
|
{ |
|
|
jobId: jobId, |
|
|
status: "pending", |
|
|
message: `Segmentation job queued for ${body.case_id}`, |
|
|
}, |
|
|
{ status: 202 }, |
|
|
); |
|
|
}), |
|
|
|
|
|
|
|
|
http.get(`${API_BASE}/api/jobs/:jobId`, async ({ params }) => { |
|
|
const jobId = params.jobId as string; |
|
|
await delay(50); |
|
|
|
|
|
const job = mockJobs.get(jobId); |
|
|
if (!job) { |
|
|
return HttpResponse.json( |
|
|
{ detail: `Job not found: ${jobId}. Jobs expire after 1 hour.` }, |
|
|
{ status: 404 }, |
|
|
); |
|
|
} |
|
|
|
|
|
|
|
|
const updatedJob = getJobProgress(job); |
|
|
mockJobs.set(jobId, updatedJob); |
|
|
|
|
|
|
|
|
const response: Record<string, unknown> = { |
|
|
jobId: updatedJob.id, |
|
|
status: updatedJob.status, |
|
|
progress: updatedJob.progress, |
|
|
progressMessage: updatedJob.progressMessage, |
|
|
elapsedSeconds: Math.round(updatedJob.elapsedSeconds * 100) / 100, |
|
|
}; |
|
|
|
|
|
|
|
|
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}/${updatedJob.caseId}_dwi.nii.gz`, |
|
|
predictionUrl: `${API_BASE}/files/${jobId}/${updatedJob.caseId}/lesion_msk.nii.gz`, |
|
|
}; |
|
|
} |
|
|
|
|
|
return HttpResponse.json(response); |
|
|
}), |
|
|
]; |
|
|
|
|
|
|
|
|
let casesAttempts = 0; |
|
|
|
|
|
|
|
|
export function resetCasesAttempts(): void { |
|
|
casesAttempts = 0; |
|
|
} |
|
|
|
|
|
|
|
|
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(); |
|
|
}), |
|
|
|
|
|
|
|
|
casesColdStart: http.get(`${API_BASE}/api/cases`, async () => { |
|
|
casesAttempts++; |
|
|
if (casesAttempts === 1) { |
|
|
return HttpResponse.json( |
|
|
{ detail: "Service Unavailable" }, |
|
|
{ status: 503 }, |
|
|
); |
|
|
} |
|
|
|
|
|
return HttpResponse.json({ |
|
|
cases: ["sub-stroke0001", "sub-stroke0002", "sub-stroke0003"], |
|
|
}); |
|
|
}), |
|
|
|
|
|
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 }, |
|
|
); |
|
|
}), |
|
|
|
|
|
|
|
|
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", |
|
|
}); |
|
|
}), |
|
|
], |
|
|
}; |
|
|
|