test: add hook tests for useDisclosures, useAuditLog, useUsers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
CCS Admin 2026-02-26 21:21:27 +00:00
parent 6ede0d93ed
commit e67fe73da7
3 changed files with 430 additions and 0 deletions

View 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()
})
})

View 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))
})
})

View 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))
})
})