Files
accounted/extensions/general/invoice-inbox/lib/__tests__/invoice-analyzer.test.ts
T
Jakob WennbergandClaude Opus 4.6 3f255ea201 refactor: consolidate AI analyzers into shared lib/ai module and add journal entry reversal columns
Extract shared vision/document analysis logic into lib/ai (vision-client, document-analyzer,
image preprocessing, validation helpers), simplifying invoice-analyzer, receipt-analyzer, and
document classifier. Add migration 046 for journal entry reversal/correction link columns
(reversed_by_id, reverses_id, correction_of_id) required by storno service. Update extension
components and shared UI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 17:14:44 +01:00

114 lines
3.4 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest'
// Mock server-only
vi.mock('server-only', () => ({}))
// Mock the core document-analyzer module
const { mockExtractInvoice } = vi.hoisted(() => ({
mockExtractInvoice: vi.fn(),
}))
vi.mock('@/lib/ai/document-analyzer', () => ({
extractInvoice: mockExtractInvoice,
}))
import { analyzeInvoice } from '../invoice-analyzer'
describe('Invoice Analyzer', () => {
beforeEach(() => {
vi.clearAllMocks()
})
const validExtraction = {
supplier: {
name: 'Kontorsbolaget AB',
orgNumber: '556123-4567',
vatNumber: 'SE5561234567',
address: 'Storgatan 1, 111 22 Stockholm',
bankgiro: '123-4567',
plusgiro: null,
},
invoice: {
invoiceNumber: 'F-2024-001',
invoiceDate: '2024-06-15',
dueDate: '2024-07-15',
paymentReference: '1234567890',
currency: 'SEK',
},
lineItems: [
{
description: 'Kontorsmaterial',
quantity: 10,
unitPrice: 50,
lineTotal: 500,
vatRate: 25,
accountSuggestion: '6100',
},
],
totals: {
subtotal: 500,
vatAmount: 125,
total: 625,
},
vatBreakdown: [
{ rate: 25, base: 500, amount: 125 },
],
confidence: 0.92,
}
it('delegates to extractInvoice from core', async () => {
mockExtractInvoice.mockResolvedValueOnce(validExtraction)
const result = await analyzeInvoice('base64data', 'application/pdf')
expect(mockExtractInvoice).toHaveBeenCalledWith('base64data', 'application/pdf')
expect(result.supplier.name).toBe('Kontorsbolaget AB')
expect(result.supplier.orgNumber).toBe('556123-4567')
expect(result.invoice.invoiceNumber).toBe('F-2024-001')
expect(result.lineItems).toHaveLength(1)
expect(result.lineItems[0].lineTotal).toBe(500)
expect(result.totals.total).toBe(625)
expect(result.confidence).toBe(0.92)
})
it('works with image mime types', async () => {
mockExtractInvoice.mockResolvedValueOnce(validExtraction)
const result = await analyzeInvoice('base64data', 'image/jpeg')
expect(mockExtractInvoice).toHaveBeenCalledWith('base64data', 'image/jpeg')
expect(result.supplier.name).toBe('Kontorsbolaget AB')
})
it('propagates errors from core', async () => {
mockExtractInvoice.mockRejectedValueOnce(new Error('Vision API call failed after 3 attempts'))
await expect(analyzeInvoice('base64data', 'application/pdf')).rejects.toThrow(
'Vision API call failed after 3 attempts'
)
})
it('propagates unsupported file type errors', async () => {
mockExtractInvoice.mockRejectedValueOnce(new Error('Unsupported file type: text/plain'))
await expect(analyzeInvoice('base64data', 'text/plain')).rejects.toThrow(
'Unsupported file type'
)
})
it('returns validated account suggestions', async () => {
const extractionWithAccounts = {
...validExtraction,
lineItems: [
{ description: 'Item', quantity: 1, unitPrice: 100, lineTotal: 100, vatRate: 25, accountSuggestion: '6100' },
{ description: 'Bad', quantity: 1, unitPrice: 50, lineTotal: 50, vatRate: 25, accountSuggestion: null },
],
}
mockExtractInvoice.mockResolvedValueOnce(extractionWithAccounts)
const result = await analyzeInvoice('base64data', 'application/pdf')
expect(result.lineItems[0].accountSuggestion).toBe('6100')
expect(result.lineItems[1].accountSuggestion).toBeNull()
})
})