mirror of
https://github.com/complexcaresolutions/dak.c2s.git
synced 2026-03-18 00:13:41 +00:00
test: add hook tests for useDisclosures, useAuditLog, useUsers
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6ede0d93ed
commit
e67fe73da7
3 changed files with 430 additions and 0 deletions
116
frontend/src/hooks/__tests__/useAuditLog.test.ts
Normal file
116
frontend/src/hooks/__tests__/useAuditLog.test.ts
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
import { waitFor } from '@testing-library/react'
|
||||||
|
import { http, HttpResponse } from 'msw'
|
||||||
|
import { server } from '@/test/mocks/server'
|
||||||
|
import { renderHookWithProviders } from '@/test/utils'
|
||||||
|
import { mockAuditLogEntry } from '@/test/mocks/data'
|
||||||
|
import { useAuditLog } from '@/hooks/useAuditLog'
|
||||||
|
import type { AuditLogFilters } from '@/hooks/useAuditLog'
|
||||||
|
|
||||||
|
describe('useAuditLog', () => {
|
||||||
|
const defaultFilters: AuditLogFilters = { skip: 0, limit: 50 }
|
||||||
|
|
||||||
|
it('fetches audit log entries (success)', async () => {
|
||||||
|
const { result } = renderHookWithProviders(() => useAuditLog(defaultFilters))
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.isSuccess).toBe(true))
|
||||||
|
|
||||||
|
expect(result.current.data).toEqual([mockAuditLogEntry])
|
||||||
|
expect(result.current.data).toHaveLength(1)
|
||||||
|
expect(result.current.data![0].action).toBe('case.update')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('passes filter parameters (user_id, action, date_from, date_to, skip, limit)', async () => {
|
||||||
|
const capturedParams: Record<string, string | null> = {}
|
||||||
|
|
||||||
|
server.use(
|
||||||
|
http.get('/api/admin/audit-log', ({ request }) => {
|
||||||
|
const url = new URL(request.url)
|
||||||
|
capturedParams.skip = url.searchParams.get('skip')
|
||||||
|
capturedParams.limit = url.searchParams.get('limit')
|
||||||
|
capturedParams.user_id = url.searchParams.get('user_id')
|
||||||
|
capturedParams.action = url.searchParams.get('action')
|
||||||
|
capturedParams.date_from = url.searchParams.get('date_from')
|
||||||
|
capturedParams.date_to = url.searchParams.get('date_to')
|
||||||
|
return HttpResponse.json([mockAuditLogEntry])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const filters: AuditLogFilters = {
|
||||||
|
skip: 10,
|
||||||
|
limit: 25,
|
||||||
|
user_id: '1',
|
||||||
|
action: 'case.update',
|
||||||
|
date_from: '2026-02-01',
|
||||||
|
date_to: '2026-02-28',
|
||||||
|
}
|
||||||
|
|
||||||
|
const { result } = renderHookWithProviders(() => useAuditLog(filters))
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.isSuccess).toBe(true))
|
||||||
|
|
||||||
|
expect(capturedParams.skip).toBe('10')
|
||||||
|
expect(capturedParams.limit).toBe('25')
|
||||||
|
expect(capturedParams.user_id).toBe('1')
|
||||||
|
expect(capturedParams.action).toBe('case.update')
|
||||||
|
expect(capturedParams.date_from).toBe('2026-02-01')
|
||||||
|
expect(capturedParams.date_to).toBe('2026-02-28')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not send optional params when they are undefined', async () => {
|
||||||
|
const capturedParams: Record<string, string | null> = {}
|
||||||
|
|
||||||
|
server.use(
|
||||||
|
http.get('/api/admin/audit-log', ({ request }) => {
|
||||||
|
const url = new URL(request.url)
|
||||||
|
capturedParams.skip = url.searchParams.get('skip')
|
||||||
|
capturedParams.limit = url.searchParams.get('limit')
|
||||||
|
capturedParams.user_id = url.searchParams.get('user_id')
|
||||||
|
capturedParams.action = url.searchParams.get('action')
|
||||||
|
capturedParams.date_from = url.searchParams.get('date_from')
|
||||||
|
capturedParams.date_to = url.searchParams.get('date_to')
|
||||||
|
return HttpResponse.json([])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const { result } = renderHookWithProviders(() => useAuditLog(defaultFilters))
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.isSuccess).toBe(true))
|
||||||
|
|
||||||
|
expect(capturedParams.skip).toBe('0')
|
||||||
|
expect(capturedParams.limit).toBe('50')
|
||||||
|
expect(capturedParams.user_id).toBeNull()
|
||||||
|
expect(capturedParams.action).toBeNull()
|
||||||
|
expect(capturedParams.date_from).toBeNull()
|
||||||
|
expect(capturedParams.date_to).toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles empty response', async () => {
|
||||||
|
server.use(
|
||||||
|
http.get('/api/admin/audit-log', () => {
|
||||||
|
return HttpResponse.json([])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const { result } = renderHookWithProviders(() => useAuditLog(defaultFilters))
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.isSuccess).toBe(true))
|
||||||
|
|
||||||
|
expect(result.current.data).toEqual([])
|
||||||
|
expect(result.current.data).toHaveLength(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles server error (500)', async () => {
|
||||||
|
server.use(
|
||||||
|
http.get('/api/admin/audit-log', () => {
|
||||||
|
return new HttpResponse(null, { status: 500 })
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const { result } = renderHookWithProviders(() => useAuditLog(defaultFilters))
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.isError).toBe(true))
|
||||||
|
|
||||||
|
expect(result.current.data).toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
146
frontend/src/hooks/__tests__/useDisclosures.test.ts
Normal file
146
frontend/src/hooks/__tests__/useDisclosures.test.ts
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
import { waitFor, act } from '@testing-library/react'
|
||||||
|
import { http, HttpResponse } from 'msw'
|
||||||
|
import { server } from '@/test/mocks/server'
|
||||||
|
import { renderHookWithProviders } from '@/test/utils'
|
||||||
|
import {
|
||||||
|
mockDisclosureRequest,
|
||||||
|
mockDisclosureApproved,
|
||||||
|
} from '@/test/mocks/data'
|
||||||
|
import { useDisclosures, useReviewDisclosure } from '@/hooks/useDisclosures'
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// useDisclosures
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
describe('useDisclosures', () => {
|
||||||
|
it('fetches disclosures with status filter (success)', async () => {
|
||||||
|
let capturedStatus: string | null = null
|
||||||
|
|
||||||
|
server.use(
|
||||||
|
http.get('/api/admin/disclosure-requests', ({ request }) => {
|
||||||
|
const url = new URL(request.url)
|
||||||
|
capturedStatus = url.searchParams.get('status')
|
||||||
|
return HttpResponse.json([mockDisclosureRequest])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const { result } = renderHookWithProviders(() => useDisclosures('pending'))
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.isSuccess).toBe(true))
|
||||||
|
|
||||||
|
expect(capturedStatus).toBe('pending')
|
||||||
|
expect(result.current.data).toEqual([mockDisclosureRequest])
|
||||||
|
expect(result.current.data).toHaveLength(1)
|
||||||
|
expect(result.current.data![0].status).toBe('pending')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles empty list', async () => {
|
||||||
|
server.use(
|
||||||
|
http.get('/api/admin/disclosure-requests', () => {
|
||||||
|
return HttpResponse.json([])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const { result } = renderHookWithProviders(() => useDisclosures('pending'))
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.isSuccess).toBe(true))
|
||||||
|
|
||||||
|
expect(result.current.data).toEqual([])
|
||||||
|
expect(result.current.data).toHaveLength(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('works without status parameter (uses default "pending")', async () => {
|
||||||
|
let capturedStatus: string | null = null
|
||||||
|
|
||||||
|
server.use(
|
||||||
|
http.get('/api/admin/disclosure-requests', ({ request }) => {
|
||||||
|
const url = new URL(request.url)
|
||||||
|
capturedStatus = url.searchParams.get('status')
|
||||||
|
return HttpResponse.json([mockDisclosureRequest])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const { result } = renderHookWithProviders(() => useDisclosures())
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.isSuccess).toBe(true))
|
||||||
|
|
||||||
|
expect(capturedStatus).toBe('pending')
|
||||||
|
expect(result.current.data).toHaveLength(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// useReviewDisclosure
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
describe('useReviewDisclosure', () => {
|
||||||
|
it('approves a disclosure (mutation success)', async () => {
|
||||||
|
server.use(
|
||||||
|
http.put('/api/admin/disclosure-requests/:id', () => {
|
||||||
|
return HttpResponse.json(mockDisclosureApproved)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const { result } = renderHookWithProviders(() => useReviewDisclosure())
|
||||||
|
|
||||||
|
let response: unknown
|
||||||
|
await act(async () => {
|
||||||
|
response = await result.current.mutateAsync({ id: 1, status: 'approved' })
|
||||||
|
})
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.isSuccess).toBe(true))
|
||||||
|
|
||||||
|
expect((response as { status: string }).status).toBe('approved')
|
||||||
|
expect((response as { reviewed_by: number }).reviewed_by).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('rejects a disclosure', async () => {
|
||||||
|
const rejectedDisclosure = {
|
||||||
|
...mockDisclosureRequest,
|
||||||
|
status: 'rejected' as const,
|
||||||
|
reviewed_by: 1,
|
||||||
|
reviewed_at: '2026-02-26T09:30:00Z',
|
||||||
|
}
|
||||||
|
|
||||||
|
server.use(
|
||||||
|
http.put('/api/admin/disclosure-requests/:id', () => {
|
||||||
|
return HttpResponse.json(rejectedDisclosure)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const { result } = renderHookWithProviders(() => useReviewDisclosure())
|
||||||
|
|
||||||
|
let response: unknown
|
||||||
|
await act(async () => {
|
||||||
|
response = await result.current.mutateAsync({ id: 1, status: 'rejected' })
|
||||||
|
})
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.isSuccess).toBe(true))
|
||||||
|
|
||||||
|
expect((response as { status: string }).status).toBe('rejected')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles error', async () => {
|
||||||
|
server.use(
|
||||||
|
http.put('/api/admin/disclosure-requests/:id', () => {
|
||||||
|
return HttpResponse.json(
|
||||||
|
{ detail: 'Not found' },
|
||||||
|
{ status: 404 },
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const { result } = renderHookWithProviders(() => useReviewDisclosure())
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
try {
|
||||||
|
await result.current.mutateAsync({ id: 999, status: 'approved' })
|
||||||
|
} catch {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.isError).toBe(true))
|
||||||
|
})
|
||||||
|
})
|
||||||
168
frontend/src/hooks/__tests__/useUsers.test.ts
Normal file
168
frontend/src/hooks/__tests__/useUsers.test.ts
Normal file
|
|
@ -0,0 +1,168 @@
|
||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
import { waitFor, act } from '@testing-library/react'
|
||||||
|
import { http, HttpResponse } from 'msw'
|
||||||
|
import { server } from '@/test/mocks/server'
|
||||||
|
import { renderHookWithProviders } from '@/test/utils'
|
||||||
|
import { mockUserResponse } from '@/test/mocks/data'
|
||||||
|
import { useUsers, useCreateUser, useUpdateUser } from '@/hooks/useUsers'
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// useUsers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
describe('useUsers', () => {
|
||||||
|
it('fetches user list (success)', async () => {
|
||||||
|
const { result } = renderHookWithProviders(() => useUsers())
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.isSuccess).toBe(true))
|
||||||
|
|
||||||
|
expect(result.current.data).toEqual([mockUserResponse])
|
||||||
|
expect(result.current.data).toHaveLength(1)
|
||||||
|
expect(result.current.data![0].username).toBe('admin_user')
|
||||||
|
expect(result.current.data![0].role).toBe('admin')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles empty list', async () => {
|
||||||
|
server.use(
|
||||||
|
http.get('/api/admin/users', () => {
|
||||||
|
return HttpResponse.json([])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const { result } = renderHookWithProviders(() => useUsers())
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.isSuccess).toBe(true))
|
||||||
|
|
||||||
|
expect(result.current.data).toEqual([])
|
||||||
|
expect(result.current.data).toHaveLength(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// useCreateUser
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
describe('useCreateUser', () => {
|
||||||
|
it('creates user (success)', async () => {
|
||||||
|
const newUser = {
|
||||||
|
...mockUserResponse,
|
||||||
|
id: 3,
|
||||||
|
username: 'new_user',
|
||||||
|
email: 'new@dak.de',
|
||||||
|
role: 'dak_mitarbeiter' as const,
|
||||||
|
}
|
||||||
|
|
||||||
|
server.use(
|
||||||
|
http.post('/api/admin/users', () => {
|
||||||
|
return HttpResponse.json(newUser)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const { result } = renderHookWithProviders(() => useCreateUser())
|
||||||
|
|
||||||
|
let response: unknown
|
||||||
|
await act(async () => {
|
||||||
|
response = await result.current.mutateAsync({
|
||||||
|
username: 'new_user',
|
||||||
|
email: 'new@dak.de',
|
||||||
|
password: 'SecurePass123!',
|
||||||
|
role: 'dak_mitarbeiter',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.isSuccess).toBe(true))
|
||||||
|
|
||||||
|
expect((response as { id: number }).id).toBe(3)
|
||||||
|
expect((response as { username: string }).username).toBe('new_user')
|
||||||
|
expect((response as { role: string }).role).toBe('dak_mitarbeiter')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles validation error (422)', async () => {
|
||||||
|
server.use(
|
||||||
|
http.post('/api/admin/users', () => {
|
||||||
|
return HttpResponse.json(
|
||||||
|
{ detail: 'Username already exists' },
|
||||||
|
{ status: 422 },
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const { result } = renderHookWithProviders(() => useCreateUser())
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
try {
|
||||||
|
await result.current.mutateAsync({
|
||||||
|
username: 'admin_user',
|
||||||
|
email: 'duplicate@dak.de',
|
||||||
|
password: 'SecurePass123!',
|
||||||
|
role: 'admin',
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.isError).toBe(true))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// useUpdateUser
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
describe('useUpdateUser', () => {
|
||||||
|
it('updates user (success)', async () => {
|
||||||
|
const updatedUser = {
|
||||||
|
...mockUserResponse,
|
||||||
|
role: 'dak_mitarbeiter' as const,
|
||||||
|
is_active: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
server.use(
|
||||||
|
http.put('/api/admin/users/:id', () => {
|
||||||
|
return HttpResponse.json(updatedUser)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const { result } = renderHookWithProviders(() => useUpdateUser())
|
||||||
|
|
||||||
|
let response: unknown
|
||||||
|
await act(async () => {
|
||||||
|
response = await result.current.mutateAsync({
|
||||||
|
id: 1,
|
||||||
|
payload: { role: 'dak_mitarbeiter', is_active: false },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.isSuccess).toBe(true))
|
||||||
|
|
||||||
|
expect((response as { role: string }).role).toBe('dak_mitarbeiter')
|
||||||
|
expect((response as { is_active: boolean }).is_active).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles error (404 Not Found)', async () => {
|
||||||
|
server.use(
|
||||||
|
http.put('/api/admin/users/:id', () => {
|
||||||
|
return HttpResponse.json(
|
||||||
|
{ detail: 'User not found' },
|
||||||
|
{ status: 404 },
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const { result } = renderHookWithProviders(() => useUpdateUser())
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
try {
|
||||||
|
await result.current.mutateAsync({
|
||||||
|
id: 999,
|
||||||
|
payload: { is_active: false },
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.isError).toBe(true))
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
Reference in a new issue