feat: add INK2 declaration, full archive export, AI consent gate, fix VAT declaration rutor
- Fix VAT declaration ruta mappings to match SKV 4700 form correctly (ruta 05 = total taxable sales, ruta 10/11/12 = output VAT per rate) - Add INK2 declaration report for aktiebolag with SRU export - Add full archive ZIP export for 7-year retention compliance - Add AI consent gate requiring user approval before AI extension API calls - Add DPA and privacy policy public pages - Add audit trail API routes - Update VAT registration threshold from 80k to 120k kr in onboarding - Update CLAUDE.md documentation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
50bf7b3b0f
commit
29240738fa
@@ -0,0 +1,106 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { createMockRequest, parseJsonResponse } 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 },
|
||||
}),
|
||||
},
|
||||
} as any)
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('GET /api/audit-trail', () => {
|
||||
it('returns 401 when not authenticated', async () => {
|
||||
mockAuth(null)
|
||||
const req = createMockRequest('/api/audit-trail')
|
||||
const { status, body } = await parseJsonResponse(await GET(req))
|
||||
expect(status).toBe(401)
|
||||
expect(body).toEqual({ error: 'Unauthorized' })
|
||||
})
|
||||
|
||||
it('returns audit log with data and count', async () => {
|
||||
mockAuth('user-1')
|
||||
const entries = [
|
||||
{ id: '1', action: 'INSERT', table_name: 'journal_entries', created_at: '2024-01-01T00:00:00Z' },
|
||||
{ id: '2', action: 'COMMIT', table_name: 'journal_entries', created_at: '2024-01-02T00:00:00Z' },
|
||||
]
|
||||
mockGetAuditLog.mockResolvedValue({ data: entries as any, count: 2 })
|
||||
|
||||
const req = createMockRequest('/api/audit-trail')
|
||||
const { status, body } = await parseJsonResponse<{ data: any[]; count: number }>(await GET(req))
|
||||
|
||||
expect(status).toBe(200)
|
||||
expect(body.data).toHaveLength(2)
|
||||
expect(body.count).toBe(2)
|
||||
expect(mockGetAuditLog).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
'user-1',
|
||||
expect.objectContaining({})
|
||||
)
|
||||
})
|
||||
|
||||
it('passes query param filters to getAuditLog', async () => {
|
||||
mockAuth('user-1')
|
||||
mockGetAuditLog.mockResolvedValue({ data: [], count: 0 })
|
||||
|
||||
const req = createMockRequest('/api/audit-trail', {
|
||||
searchParams: {
|
||||
action: 'INSERT',
|
||||
table_name: 'journal_entries',
|
||||
record_id: 'rec-1',
|
||||
from_date: '2024-01-01',
|
||||
to_date: '2024-12-31',
|
||||
page: '2',
|
||||
page_size: '25',
|
||||
},
|
||||
})
|
||||
|
||||
await GET(req)
|
||||
|
||||
expect(mockGetAuditLog).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
'user-1',
|
||||
{
|
||||
action: 'INSERT',
|
||||
table_name: 'journal_entries',
|
||||
record_id: 'rec-1',
|
||||
from_date: '2024-01-01',
|
||||
to_date: '2024-12-31',
|
||||
page: 2,
|
||||
pageSize: 25,
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('returns 500 on service error', async () => {
|
||||
mockAuth('user-1')
|
||||
mockGetAuditLog.mockRejectedValue(new Error('DB error'))
|
||||
|
||||
const req = createMockRequest('/api/audit-trail')
|
||||
const { status, body } = await parseJsonResponse(await GET(req))
|
||||
|
||||
expect(status).toBe(500)
|
||||
expect(body).toEqual({ error: 'DB error' })
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,35 @@
|
||||
import { createClient } from '@/lib/supabase/server'
|
||||
import { NextResponse } from 'next/server'
|
||||
import { getAuditLog } from '@/lib/core/audit/audit-service'
|
||||
import type { AuditAction } from '@/types'
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const supabase = await createClient()
|
||||
const { data: { user } } = await supabase.auth.getUser()
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(request.url)
|
||||
|
||||
const filters = {
|
||||
action: (searchParams.get('action') as AuditAction) || undefined,
|
||||
table_name: searchParams.get('table_name') || undefined,
|
||||
record_id: searchParams.get('record_id') || undefined,
|
||||
from_date: searchParams.get('from_date') || undefined,
|
||||
to_date: searchParams.get('to_date') || undefined,
|
||||
page: searchParams.has('page') ? Number(searchParams.get('page')) : undefined,
|
||||
pageSize: searchParams.has('page_size') ? Number(searchParams.get('page_size')) : undefined,
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await getAuditLog(supabase, user.id, filters)
|
||||
return NextResponse.json({ data: result.data, count: result.count })
|
||||
} catch (err) {
|
||||
return NextResponse.json(
|
||||
{ error: err instanceof Error ? err.message : 'Failed to fetch audit log' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user