Files
accounted/lib/providers/fortnox/__tests__/attachments.test.ts
T
Mattsson a97b0023d4 feat: import Fortnox voucher attachments (#1541)
* feat: import Fortnox voucher attachments

* fix: show Fortnox document import follow-up

* fix: harden optional Fortnox document import

* test: pin optional Fortnox import flow

* fix: use browser timer handle type

* fix: avoid serializing OAuth resume state
2026-08-13 00:33:10 +02:00

125 lines
4.2 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest';
import {
downloadFortnoxArchiveFile,
fetchFortnoxFileConnections,
fetchFortnoxFinancialYears,
} from '../attachments';
import { FortnoxApiError, type FortnoxClient } from '../client';
function clientWith(methods: Partial<FortnoxClient>): FortnoxClient {
return methods as FortnoxClient;
}
describe('Fortnox attachments', () => {
it('maps financial years and skips malformed rows', async () => {
const getPaginated = vi.fn().mockResolvedValue([
{ Id: 3, FromDate: '2021-01-01', ToDate: '2021-12-31' },
{ Id: '4', FromDate: '2022-01-01', ToDate: '2022-12-31' },
{ Id: null, FromDate: '2023-01-01', ToDate: '2023-12-31' },
]);
const client = clientWith({ getPaginated } as Partial<FortnoxClient>);
await expect(fetchFortnoxFinancialYears(client, 'token')).resolves.toEqual([
{ id: 3, fromDate: '2021-01-01', toDate: '2021-12-31' },
{ id: 4, fromDate: '2022-01-01', toDate: '2022-12-31' },
]);
expect(getPaginated).toHaveBeenCalledWith(
'token',
'/financialyears',
'FinancialYears',
{ pageSize: 500 },
);
});
it('queries each financial year, coerces fields, skips malformed rows, and deduplicates', async () => {
const duplicate = {
FileId: 'file-1',
Name: 'kvitto.pdf',
VoucherSeries: 'A',
VoucherNumber: '12',
VoucherYear: '3',
};
const getPaginated = vi
.fn()
.mockResolvedValueOnce([
duplicate,
duplicate,
{ FileId: '', VoucherSeries: 'A', VoucherNumber: '13', VoucherYear: '3' },
])
.mockResolvedValueOnce([
{
FileId: 'file-2',
Name: ' faktura.png ',
VoucherSeries: 'B',
VoucherNumber: 7,
VoucherYear: 4,
},
]);
const client = clientWith({ getPaginated } as Partial<FortnoxClient>);
await expect(fetchFortnoxFileConnections(client, 'token', [3, 4])).resolves.toEqual([
{
fileId: 'file-1',
name: 'kvitto.pdf',
series: 'A',
number: 12,
financialYearId: 3,
},
{
fileId: 'file-2',
name: 'faktura.png',
series: 'B',
number: 7,
financialYearId: 4,
},
]);
expect(getPaginated).toHaveBeenNthCalledWith(
1,
'token',
'/voucherfileconnections?financialyear=3',
'VoucherFileConnections',
{ pageSize: 500 },
);
expect(getPaginated).toHaveBeenNthCalledWith(
2,
'token',
'/voucherfileconnections?financialyear=4',
'VoucherFileConnections',
{ pageSize: 500 },
);
});
it('downloads through the archive path without fallback when it succeeds', async () => {
const response = { bytes: new ArrayBuffer(2), contentType: 'application/pdf' };
const getBinary = vi.fn().mockResolvedValue(response);
const client = clientWith({ getBinary } as Partial<FortnoxClient>);
await expect(downloadFortnoxArchiveFile(client, 'token', 'file-1')).resolves.toBe(response);
expect(getBinary).toHaveBeenCalledTimes(1);
expect(getBinary).toHaveBeenCalledWith('token', '/archive/file-1');
});
it('retries the query-form archive endpoint once after a 404', async () => {
const response = { bytes: new ArrayBuffer(2), contentType: 'image/jpeg' };
const getBinary = vi
.fn()
.mockRejectedValueOnce(new FortnoxApiError('not found', 404))
.mockResolvedValueOnce(response);
const client = clientWith({ getBinary } as Partial<FortnoxClient>);
await expect(downloadFortnoxArchiveFile(client, 'token', 'file-1')).resolves.toBe(response);
expect(getBinary).toHaveBeenNthCalledWith(1, 'token', '/archive/file-1');
expect(getBinary).toHaveBeenNthCalledWith(2, 'token', '/archive/?fileid=file-1');
});
it('propagates non-404 archive failures without trying the fallback', async () => {
const error = new FortnoxApiError('server error', 500);
const getBinary = vi.fn().mockRejectedValue(error);
const client = clientWith({ getBinary } as Partial<FortnoxClient>);
await expect(downloadFortnoxArchiveFile(client, 'token', 'file-1')).rejects.toBe(error);
expect(getBinary).toHaveBeenCalledTimes(1);
});
});