Batch from a real migration walkthrough (Fortnox -> Accounted, 2026-08-20): - Årsredovisning: the "Låst version" select was empty with no explanation because the only version was a draft and "Lås version för underskrift" is disabled while the four Lagstadgade upplysningar checkboxes and the content confirmation count as blockers. The select is now disabled with a hint that names the blocker count and links to Fullständighetskontroll, the four AR-NOTE-*-UNCONFIRMED issues carry remediation text, the lock button explains why it is grey, and "Markera som signerad" says what it still needs (locked version, bevisreferens, date). - Moderföretagets org.nr accepts a foreign registration identifier (CHE-123.456.789, HRB 12345, 923 609 016); personnummer shapes stay out. - Fortnox underlag discovery: log status, body and Fortnox's message on failure, show the message in the UI, treat a 400 with behörighet/scope text as scopes-required, and fall back to an unfiltered voucherfileconnections list when the financialyear filter answers 400. - Kontomapping: the Momskod column had min-w only; table-fixed collapsed it and its selects overflowed into Konfidens. Real w-72 now. - SIE import warnings pluralise correctly for one skipped voucher; the Verifikationsserie option says the source series is preserved. Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
52 lines
2.0 KiB
TypeScript
52 lines
2.0 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { FortnoxApiError, fortnoxErrorMessage, isFortnoxPermissionError } from '../client';
|
|
|
|
describe('fortnoxErrorMessage', () => {
|
|
it('reads ErrorInformation.message from a Fortnox error body', () => {
|
|
const error = new FortnoxApiError(
|
|
'Fortnox API error: 400 Bad Request',
|
|
400,
|
|
'{"ErrorInformation":{"error":1,"message":"Kan inte hitta kontot.","code":2000423}}',
|
|
);
|
|
expect(fortnoxErrorMessage(error)).toBe('Kan inte hitta kontot.');
|
|
});
|
|
|
|
it('falls back to the raw body when it is not JSON and to null when empty', () => {
|
|
expect(fortnoxErrorMessage(new FortnoxApiError('x', 500, 'Gateway timeout'))).toBe(
|
|
'Gateway timeout',
|
|
);
|
|
expect(fortnoxErrorMessage(new FortnoxApiError('x', 500, ''))).toBeNull();
|
|
expect(fortnoxErrorMessage(new Error('not fortnox'))).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('isFortnoxPermissionError', () => {
|
|
it('treats 403 as a permission failure regardless of body', () => {
|
|
expect(isFortnoxPermissionError(new FortnoxApiError('forbidden', 403))).toBe(true);
|
|
});
|
|
|
|
it('treats a 400 with a behörighet/scope/licens message as a permission failure', () => {
|
|
const error = new FortnoxApiError(
|
|
'Fortnox API error: 400 Bad Request',
|
|
400,
|
|
'{"ErrorInformation":{"error":1,"message":"Du saknar behörighet till denna resurs.","code":2000663}}',
|
|
);
|
|
expect(isFortnoxPermissionError(error)).toBe(true);
|
|
expect(
|
|
isFortnoxPermissionError(
|
|
new FortnoxApiError('x', 400, '{"ErrorInformation":{"message":"Invalid scope"}}'),
|
|
),
|
|
).toBe(true);
|
|
});
|
|
|
|
it('does not classify an ordinary 400 or a non-Fortnox error as permission', () => {
|
|
expect(
|
|
isFortnoxPermissionError(
|
|
new FortnoxApiError('x', 400, '{"ErrorInformation":{"message":"Kan inte hitta kontot."}}'),
|
|
),
|
|
).toBe(false);
|
|
expect(isFortnoxPermissionError(new FortnoxApiError('x', 400, ''))).toBe(false);
|
|
expect(isFortnoxPermissionError(new Error('boom'))).toBe(false);
|
|
});
|
|
});
|