File size: 4,381 Bytes
497bb49
fbf73ff
497bb49
fbf73ff
497bb49
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fbf73ff
 
 
 
 
 
497bb49
fbf73ff
497bb49
 
 
 
 
 
 
 
 
 
fbf73ff
497bb49
 
 
 
 
 
 
fbf73ff
497bb49
 
fbf73ff
497bb49
 
 
 
 
 
 
 
 
 
 
 
 
fbf73ff
497bb49
 
 
 
 
 
 
fbf73ff
497bb49
fbf73ff
497bb49
fbf73ff
497bb49
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fbf73ff
497bb49
 
fbf73ff
497bb49
 
 
 
 
fbf73ff
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import { test as base, expect, Page } from '@playwright/test'

// API response mocks matching the async job queue pattern
const MOCK_CASES = ['sub-stroke0001', 'sub-stroke0002', 'sub-stroke0003']

// Track jobs for the async pattern
interface MockJob {
  id: string
  caseId: string
  status: 'pending' | 'running' | 'completed' | 'failed'
  progress: number
  progressMessage: string
  createdAt: number
}

// Job store per test (reset for each test)
const createJobStore = () => {
  const jobs = new Map<string, MockJob>()
  let jobCounter = 0

  return {
    createJob(caseId: string): MockJob {
      const jobId = `e2e-job-${++jobCounter}`
      const job: MockJob = {
        id: jobId,
        caseId,
        status: 'pending',
        progress: 0,
        progressMessage: 'Job queued',
        createdAt: Date.now(),
      }
      jobs.set(jobId, job)
      return job
    },
    getJob(jobId: string): MockJob | undefined {
      return jobs.get(jobId)
    },
    updateJobProgress(job: MockJob): MockJob {
      // Simulate job progression over 1 second
      const elapsed = Date.now() - job.createdAt
      if (elapsed < 200) {
        return { ...job, status: 'running', progress: 25, progressMessage: 'Loading case data...' }
      } else if (elapsed < 500) {
        return { ...job, status: 'running', progress: 50, progressMessage: 'Running inference...' }
      } else if (elapsed < 800) {
        return { ...job, status: 'running', progress: 75, progressMessage: 'Processing results...' }
      } else {
        return { ...job, status: 'completed', progress: 100, progressMessage: 'Segmentation complete' }
      }
    },
  }
}

// Mock completed job result
const createMockResult = (caseId: string) => ({
  caseId,
  diceScore: 0.847,
  volumeMl: 15.32,
  elapsedSeconds: 12.5,
  // Use real public NIfTI for visual testing (NiiVue demo image)
  dwiUrl: 'https://niivue.github.io/niivue-demo-images/mni152.nii.gz',
  predictionUrl: 'https://niivue.github.io/niivue-demo-images/mni152.nii.gz',
})

// Setup API mocking for async job queue pattern
async function setupApiMocks(page: Page) {
  const jobStore = createJobStore()

  // Mock GET /api/cases
  await page.route('**/api/cases', (route) => {
    route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({ cases: MOCK_CASES }),
    })
  })

  // Mock POST /api/segment - returns 202 with job ID (async pattern)
  await page.route('**/api/segment', async (route) => {
    const request = route.request()
    const body = JSON.parse(request.postData() || '{}') as { case_id?: string }
    const caseId = body.case_id || 'sub-stroke0001'

    // Create a new job
    const job = jobStore.createJob(caseId)

    // Small delay to simulate network
    await new Promise((r) => setTimeout(r, 50))

    route.fulfill({
      status: 202,
      contentType: 'application/json',
      body: JSON.stringify({
        jobId: job.id,
        status: 'pending',
        message: `Segmentation job queued for ${caseId}`,
      }),
    })
  })

  // Mock GET /api/jobs/:jobId - returns job status (for polling)
  await page.route('**/api/jobs/*', async (route) => {
    const url = route.request().url()
    const jobId = url.split('/api/jobs/')[1]

    const job = jobStore.getJob(jobId)
    if (!job) {
      route.fulfill({
        status: 404,
        contentType: 'application/json',
        body: JSON.stringify({ detail: `Job not found: ${jobId}` }),
      })
      return
    }

    // Update job progress based on elapsed time
    const updatedJob = jobStore.updateJobProgress(job)

    const response: Record<string, unknown> = {
      jobId: updatedJob.id,
      status: updatedJob.status,
      progress: updatedJob.progress,
      progressMessage: updatedJob.progressMessage,
      elapsedSeconds: (Date.now() - updatedJob.createdAt) / 1000,
    }

    // Include result when completed
    if (updatedJob.status === 'completed') {
      response.result = createMockResult(updatedJob.caseId)
    }

    route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify(response),
    })
  })
}

// Extend base test to include API mocking
export const test = base.extend({
  // Auto-mock API routes for every test
  page: async ({ page }, use) => {
    await setupApiMocks(page)
    await use(page)
  },
})

export { expect }