mirror of
https://github.com/complexcaresolutions/dak.c2s.git
synced 2026-03-17 17:13:42 +00:00
feat: add MSW mock server, handlers, and test data
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8b30be0dcb
commit
0416035ce9
4 changed files with 505 additions and 0 deletions
272
frontend/src/test/mocks/data.ts
Normal file
272
frontend/src/test/mocks/data.ts
Normal 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',
|
||||
}
|
||||
216
frontend/src/test/mocks/handlers.ts
Normal file
216
frontend/src/test/mocks/handlers.ts
Normal 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)
|
||||
}),
|
||||
]
|
||||
4
frontend/src/test/mocks/server.ts
Normal file
4
frontend/src/test/mocks/server.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import { setupServer } from 'msw/node'
|
||||
import { handlers } from './handlers'
|
||||
|
||||
export const server = setupServer(...handlers)
|
||||
13
frontend/src/test/setup.ts
Normal file
13
frontend/src/test/setup.ts
Normal 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())
|
||||
Loading…
Reference in a new issue