Files
accounted/app/api/reports/audit-trail/__tests__/route.test.ts
T
Jakob WennbergandClaude Opus 4.6 091d043c85 feat: UI polish, lint fixes, onboarding redesign, help page expansion, and test improvements
Broad update across dashboard pages, components, extensions, and lib code. Includes ESLint config additions, onboarding flow redesign, settings page refactor, help page content expansion, dead code removal, and test mock fixes. Adds dev docs and public assets.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 23:05:49 +01:00

139 lines
4.2 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest'
import { createMockRequest } from '@/tests/helpers'
vi.mock('@/lib/supabase/server', () => ({
createClient: vi.fn(),
}))
vi.mock('@/lib/core/audit/audit-service', () => ({
getAuditLog: vi.fn(),
}))
import { createClient } from '@/lib/supabase/server'
import { getAuditLog } from '@/lib/core/audit/audit-service'
import { GET } from '../route'
const mockCreateClient = vi.mocked(createClient)
const mockGetAuditLog = vi.mocked(getAuditLog)
function mockAuth(userId: string | null) {
mockCreateClient.mockResolvedValue({
auth: {
getUser: vi.fn().mockResolvedValue({
data: { user: userId ? { id: userId } : null },
}),
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any)
}
const sampleEntries = [
{
id: '1',
user_id: 'user-1',
action: 'INSERT' as const,
table_name: 'journal_entries',
record_id: 'rec-1',
actor_id: null,
old_state: null,
new_state: { description: 'Test entry' },
description: 'Created journal entry',
created_at: '2024-06-15T10:00:00Z',
},
{
id: '2',
user_id: 'user-1',
action: 'COMMIT' as const,
table_name: 'journal_entries',
record_id: 'rec-1',
actor_id: null,
old_state: { status: 'draft' },
new_state: { status: 'posted' },
description: 'Committed journal entry',
created_at: '2024-06-15T10:01:00Z',
},
]
beforeEach(() => {
vi.clearAllMocks()
})
describe('GET /api/reports/audit-trail', () => {
it('returns 401 when not authenticated', async () => {
mockAuth(null)
const req = createMockRequest('/api/reports/audit-trail')
const res = await GET(req)
expect(res.status).toBe(401)
})
it('returns CSV format with correct headers', async () => {
mockAuth('user-1')
// eslint-disable-next-line @typescript-eslint/no-explicit-any
mockGetAuditLog.mockResolvedValue({ data: sampleEntries as any, count: 2 })
const req = createMockRequest('/api/reports/audit-trail', {
searchParams: { format: 'csv' },
})
const res = await GET(req)
expect(res.status).toBe(200)
expect(res.headers.get('Content-Type')).toBe('text/csv; charset=utf-8')
expect(res.headers.get('Content-Disposition')).toBe('attachment; filename="audit-trail.csv"')
const text = await res.text()
const lines = text.split('\n')
expect(lines[0]).toBe('timestamp,action,table_name,record_id,description,old_state,new_state')
expect(lines).toHaveLength(3) // header + 2 entries
expect(lines[1]).toContain('INSERT')
expect(lines[1]).toContain('journal_entries')
})
it('returns JSON format as downloadable file', async () => {
mockAuth('user-1')
// eslint-disable-next-line @typescript-eslint/no-explicit-any
mockGetAuditLog.mockResolvedValue({ data: sampleEntries as any, count: 2 })
const req = createMockRequest('/api/reports/audit-trail', {
searchParams: { format: 'json' },
})
const res = await GET(req)
expect(res.status).toBe(200)
expect(res.headers.get('Content-Type')).toBe('application/json')
expect(res.headers.get('Content-Disposition')).toBe('attachment; filename="audit-trail.json"')
const body = await res.json()
expect(body.data).toHaveLength(2)
expect(body.count).toBe(2)
})
it('paginates through all entries', async () => {
mockAuth('user-1')
// First call returns 500 entries (full page), second returns 100 (last page)
const bigPage = Array.from({ length: 500 }, (_, i) => ({
...sampleEntries[0],
id: `entry-${i}`,
}))
const lastPage = Array.from({ length: 100 }, (_, i) => ({
...sampleEntries[0],
id: `entry-${500 + i}`,
}))
mockGetAuditLog
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.mockResolvedValueOnce({ data: bigPage as any, count: 600 })
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.mockResolvedValueOnce({ data: lastPage as any, count: 600 })
const req = createMockRequest('/api/reports/audit-trail', {
searchParams: { format: 'json' },
})
const res = await GET(req)
const body = await res.json()
expect(body.data).toHaveLength(600)
expect(mockGetAuditLog).toHaveBeenCalledTimes(2)
})
})