feat: add MSW mock server, handlers, and test data

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
CCS Admin 2026-02-26 21:08:55 +00:00
parent 8b30be0dcb
commit 0416035ce9
4 changed files with 505 additions and 0 deletions

View file

@ -0,0 +1,272 @@
import type {
User,
TokenResponse,
Case,
CaseListResponse,
DashboardKPIs,
WeeklyDataPoint,
DashboardResponse,
Notification,
NotificationList,
ReportMeta,
UserResponse,
AuditLogEntry,
DisclosureRequest,
} from '@/types'
// ---------- Users ----------
export const mockUser: User = {
id: 1,
username: 'admin_user',
email: 'admin@dak.de',
first_name: 'Max',
last_name: 'Mustermann',
display_name: 'Max Mustermann',
avatar_url: null,
role: 'admin',
mfa_enabled: false,
is_active: true,
last_login: '2026-02-25T10:30:00Z',
created_at: '2026-01-01T08:00:00Z',
}
export const mockDakUser: User = {
id: 2,
username: 'dak_user',
email: 'mitarbeiter@dak.de',
first_name: 'Anna',
last_name: 'Schmidt',
display_name: 'Anna Schmidt',
avatar_url: null,
role: 'dak_mitarbeiter',
mfa_enabled: false,
is_active: true,
last_login: '2026-02-24T14:00:00Z',
created_at: '2026-01-15T09:00:00Z',
}
// ---------- Auth ----------
export const mockTokenResponse: TokenResponse = {
access_token: 'mock-access-token-abc123',
refresh_token: 'mock-refresh-token-xyz789',
token_type: 'bearer',
user: mockUser,
}
// ---------- Cases ----------
export const mockCase: Case = {
id: 1,
fall_id: '2026-06-onko-A123456789',
crm_ticket_id: 'CRM-2026-001',
jahr: 2026,
kw: 6,
datum: '2026-02-05',
anrede: 'Herr',
vorname: 'Hans',
nachname: 'Meier',
geburtsdatum: '1965-03-15',
kvnr: 'A123456789',
versicherung: 'DAK-Gesundheit',
icd: 'C50.4',
fallgruppe: 'onko',
strasse: 'Musterstraße 12',
plz: '20095',
ort: 'Hamburg',
email: 'hans.meier@example.de',
ansprechpartner: 'Dr. Müller',
telefonnummer: '040-12345678',
mobiltelefon: '0151-12345678',
unterlagen: true,
unterlagen_verschickt: '2026-02-06',
erhalten: true,
unterlagen_erhalten: '2026-02-08',
unterlagen_an_gutachter: '2026-02-09',
gutachten: true,
gutachter: 'Prof. Dr. Weber',
gutachten_erstellt: '2026-02-15',
gutachten_versendet: '2026-02-16',
schweigepflicht: true,
ablehnung: false,
abbruch: false,
abbruch_datum: null,
gutachten_typ: 'Zweitmeinung',
therapieaenderung: 'Ja',
ta_diagnosekorrektur: false,
ta_unterversorgung: true,
ta_uebertherapie: false,
kurzbeschreibung: 'Mammakarzinom links, geplante Chemotherapie',
fragestellung: 'Ist die empfohlene Chemotherapie indiziert?',
kommentar: null,
sonstiges: null,
abgerechnet: false,
abrechnung_datum: null,
import_source: 'excel_import',
imported_at: '2026-02-05T09:00:00Z',
updated_at: '2026-02-16T14:30:00Z',
disclosure_granted: false,
disclosure_expires_at: null,
}
export const mockCaseNoIcd: Case = {
...mockCase,
id: 2,
fall_id: '2026-07-ortho-B987654321',
kvnr: 'B987654321',
icd: null,
fallgruppe: 'ortho',
kw: 7,
datum: '2026-02-12',
vorname: 'Maria',
nachname: 'Fischer',
anrede: 'Frau',
geburtsdatum: '1978-11-22',
gutachten: false,
gutachter: null,
gutachten_erstellt: null,
gutachten_versendet: null,
gutachten_typ: null,
therapieaenderung: null,
ta_diagnosekorrektur: false,
ta_unterversorgung: false,
ta_uebertherapie: false,
kurzbeschreibung: 'Knie-TEP geplant',
fragestellung: 'Ist der Eingriff zum jetzigen Zeitpunkt notwendig?',
updated_at: '2026-02-12T10:00:00Z',
}
export const mockCaseListResponse: CaseListResponse = {
items: [mockCase, mockCaseNoIcd],
total: 2,
page: 1,
per_page: 20,
}
export const mockEmptyCaseList: CaseListResponse = {
items: [],
total: 0,
page: 1,
per_page: 20,
}
// ---------- Dashboard ----------
export const mockDashboardKPIs: DashboardKPIs = {
total_cases: 142,
pending_icd: 18,
pending_coding: 7,
total_gutachten: 89,
fallgruppen: {
onko: 65,
ortho: 42,
kardio: 20,
neuro: 15,
},
}
export const mockWeeklyData: WeeklyDataPoint[] = [
{ kw: 1, erstberatungen: 12, unterlagen: 8, ablehnungen: 1, keine_rm: 2, gutachten: 5 },
{ kw: 2, erstberatungen: 15, unterlagen: 10, ablehnungen: 0, keine_rm: 3, gutachten: 7 },
{ kw: 3, erstberatungen: 10, unterlagen: 9, ablehnungen: 2, keine_rm: 1, gutachten: 6 },
{ kw: 4, erstberatungen: 18, unterlagen: 12, ablehnungen: 1, keine_rm: 4, gutachten: 8 },
{ kw: 5, erstberatungen: 14, unterlagen: 11, ablehnungen: 0, keine_rm: 2, gutachten: 9 },
{ kw: 6, erstberatungen: 16, unterlagen: 13, ablehnungen: 1, keine_rm: 1, gutachten: 10 },
]
export const mockDashboardResponse: DashboardResponse = {
kpis: mockDashboardKPIs,
weekly: mockWeeklyData,
}
// ---------- Notifications ----------
export const mockNotification: Notification = {
id: 1,
notification_type: 'disclosure_request',
title: 'Neue Freigabe-Anfrage',
message: 'Anna Schmidt hat eine Freigabe für Fall 2026-06-onko-A123456789 beantragt.',
is_read: false,
created_at: '2026-02-26T09:15:00Z',
}
export const mockNotificationRead: Notification = {
id: 2,
notification_type: 'import_complete',
title: 'Import abgeschlossen',
message: '15 Fälle wurden erfolgreich importiert.',
is_read: true,
created_at: '2026-02-25T16:00:00Z',
}
export const mockNotificationList: NotificationList = {
items: [mockNotification, mockNotificationRead],
unread_count: 1,
}
// ---------- Reports ----------
export const mockReportMeta: ReportMeta = {
id: 1,
jahr: 2026,
kw: 6,
report_date: '2026-02-09',
generated_at: '2026-02-10T08:30:00Z',
}
// ---------- Admin Users ----------
export const mockUserResponse: UserResponse = {
id: 1,
username: 'admin_user',
email: 'admin@dak.de',
first_name: 'Max',
last_name: 'Mustermann',
display_name: 'Max Mustermann',
avatar_url: null,
role: 'admin',
is_active: true,
mfa_enabled: false,
last_login: '2026-02-25T10:30:00Z',
created_at: '2026-01-01T08:00:00Z',
}
// ---------- Audit Log ----------
export const mockAuditLogEntry: AuditLogEntry = {
id: 1,
user_id: 1,
action: 'case.update',
entity_type: 'case',
entity_id: '1',
old_values: { icd: null },
new_values: { icd: 'C50.4' },
ip_address: '10.10.181.104',
created_at: '2026-02-26T11:00:00Z',
}
// ---------- Disclosure Requests ----------
export const mockDisclosureRequest: DisclosureRequest = {
id: 1,
case_id: 1,
requester_id: 2,
requester_username: 'dak_user',
fall_id: '2026-06-onko-A123456789',
reason: 'Benötige vollständige Patientendaten für Rückruf.',
status: 'pending',
reviewed_by: null,
reviewed_at: null,
expires_at: null,
created_at: '2026-02-26T09:00:00Z',
}
export const mockDisclosureApproved: DisclosureRequest = {
...mockDisclosureRequest,
id: 2,
status: 'approved',
reviewed_by: 1,
reviewed_at: '2026-02-26T09:30:00Z',
expires_at: '2026-02-27T09:30:00Z',
}

View file

@ -0,0 +1,216 @@
import { http, HttpResponse } from 'msw'
import {
mockTokenResponse,
mockUser,
mockCase,
mockCaseNoIcd,
mockCaseListResponse,
mockDashboardResponse,
mockNotificationList,
mockReportMeta,
mockUserResponse,
mockAuditLogEntry,
mockDisclosureRequest,
mockDisclosureApproved,
} from './data'
export const handlers = [
// ==================== Auth ====================
// POST /api/auth/login
http.post('/api/auth/login', () => {
return HttpResponse.json(mockTokenResponse)
}),
// POST /api/auth/register
http.post('/api/auth/register', () => {
return HttpResponse.json({ user: mockUser })
}),
// POST /api/auth/logout
http.post('/api/auth/logout', () => {
return new HttpResponse(null, { status: 204 })
}),
// GET /api/auth/me
http.get('/api/auth/me', () => {
return HttpResponse.json(mockUser)
}),
// POST /api/auth/refresh
http.post('/api/auth/refresh', () => {
return HttpResponse.json({
access_token: 'refreshed-access-token',
refresh_token: 'refreshed-refresh-token',
token_type: 'bearer',
})
}),
// PUT /api/auth/profile
http.put('/api/auth/profile', () => {
return HttpResponse.json(mockUser)
}),
// POST /api/auth/avatar
http.post('/api/auth/avatar', () => {
return HttpResponse.json({ ...mockUser, avatar_url: '/uploads/avatar-1.png' })
}),
// DELETE /api/auth/avatar
http.delete('/api/auth/avatar', () => {
return HttpResponse.json({ ...mockUser, avatar_url: null })
}),
// POST /api/auth/change-password
http.post('/api/auth/change-password', () => {
return new HttpResponse(null, { status: 200 })
}),
// POST /api/auth/mfa/setup
http.post('/api/auth/mfa/setup', () => {
return HttpResponse.json({
secret: 'JBSWY3DPEHPK3PXP',
qr_uri: 'otpauth://totp/DAK:admin@dak.de?secret=JBSWY3DPEHPK3PXP&issuer=DAK',
})
}),
// POST /api/auth/mfa/verify
http.post('/api/auth/mfa/verify', () => {
return new HttpResponse(null, { status: 200 })
}),
// DELETE /api/auth/mfa
http.delete('/api/auth/mfa', () => {
return new HttpResponse(null, { status: 200 })
}),
// ==================== Cases ====================
// GET /api/cases/
http.get('/api/cases/', () => {
return HttpResponse.json(mockCaseListResponse)
}),
// GET /api/cases/pending-icd
http.get('/api/cases/pending-icd', () => {
return HttpResponse.json({
items: [mockCaseNoIcd],
total: 1,
page: 1,
per_page: 20,
})
}),
// PUT /api/cases/:id
http.put('/api/cases/:id', ({ params }) => {
const id = Number(params.id)
return HttpResponse.json({ ...mockCase, id })
}),
// PUT /api/cases/:id/kvnr
http.put('/api/cases/:id/kvnr', ({ params }) => {
const id = Number(params.id)
return HttpResponse.json({ ...mockCase, id, kvnr: 'C111222333' })
}),
// PUT /api/cases/:id/icd
http.put('/api/cases/:id/icd', ({ params }) => {
const id = Number(params.id)
return HttpResponse.json({ ...mockCase, id, icd: 'C61' })
}),
// POST /api/cases/:caseId/disclosure-request
http.post('/api/cases/:caseId/disclosure-request', () => {
return HttpResponse.json(mockDisclosureRequest)
}),
// ==================== Dashboard ====================
// GET /api/reports/dashboard
http.get('/api/reports/dashboard', () => {
return HttpResponse.json(mockDashboardResponse)
}),
// ==================== Reports ====================
// GET /api/reports/list
http.get('/api/reports/list', () => {
return HttpResponse.json({
items: [mockReportMeta],
total: 1,
})
}),
// POST /api/reports/generate
http.post('/api/reports/generate', () => {
return HttpResponse.json(mockReportMeta)
}),
// DELETE /api/reports/delete
http.delete('/api/reports/delete', () => {
return HttpResponse.json({ deleted: 1 })
}),
// ==================== Notifications ====================
// GET /api/notifications
http.get('/api/notifications', () => {
return HttpResponse.json(mockNotificationList)
}),
// PUT /api/notifications/:id/read
http.put('/api/notifications/:id/read', ({ params }) => {
const id = Number(params.id)
return HttpResponse.json({
...mockNotificationList.items[0],
id,
is_read: true,
})
}),
// PUT /api/notifications/read-all
http.put('/api/notifications/read-all', () => {
return HttpResponse.json({ marked_read: 1 })
}),
// ==================== Admin: Users ====================
// GET /api/admin/users
http.get('/api/admin/users', () => {
return HttpResponse.json([mockUserResponse])
}),
// POST /api/admin/users
http.post('/api/admin/users', () => {
return HttpResponse.json(mockUserResponse)
}),
// PUT /api/admin/users/:id
http.put('/api/admin/users/:id', () => {
return HttpResponse.json(mockUserResponse)
}),
// ==================== Admin: Audit Log ====================
// GET /api/admin/audit-log
http.get('/api/admin/audit-log', () => {
return HttpResponse.json([mockAuditLogEntry])
}),
// ==================== Admin: Disclosure Requests ====================
// GET /api/admin/disclosure-requests
http.get('/api/admin/disclosure-requests', () => {
return HttpResponse.json([mockDisclosureRequest])
}),
// GET /api/admin/disclosure-requests/count
http.get('/api/admin/disclosure-requests/count', () => {
return HttpResponse.json({ pending_count: 1 })
}),
// PUT /api/admin/disclosure-requests/:id
http.put('/api/admin/disclosure-requests/:id', () => {
return HttpResponse.json(mockDisclosureApproved)
}),
]

View file

@ -0,0 +1,4 @@
import { setupServer } from 'msw/node'
import { handlers } from './handlers'
export const server = setupServer(...handlers)

View file

@ -0,0 +1,13 @@
import '@testing-library/jest-dom/vitest'
import { cleanup } from '@testing-library/react'
import { afterAll, afterEach, beforeAll } from 'vitest'
import { server } from './mocks/server'
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
afterEach(() => {
cleanup()
server.resetHandlers()
})
afterAll(() => server.close())