Files
accounted/lib/errors/__tests__/get-bank-connection-error-message.test.ts
T
Jakob Wennberg 64119d30bc fix(bank): Swedish provider errors and a durable failure trail for bank connect attempts (#1841)
Issue #1716: a user stuck in Handelsbankens fullmakt step got a raw
provider token back (server_error, invalid_state) and support had nothing
to look at afterwards: the failed pending row is deleted by design, the
callback only logged to console (short retention), and event_log recorded
successes only. Diagnosis of the reported case: the failures were on the
bank's side (the corporate fullmakt requirement); both of the reporter's
companies connected successfully on 2026-08-12 with no code change on our
side in between, and the connections have been active and syncing since.

Changes:
- lib/errors/get-error-message.ts: getBankConnectionErrorMessage() maps
  PSD2 callback outcomes (access_denied, server_error,
  temporarily_unavailable, session expiry, plus the internal
  invalid_state, missing_parameters and invalid_code_format tokens) to
  Swedish user messages, appending the raw provider description so the
  underlying error is still surfaced.
- callback route: every bank_error redirect and the stored error_message
  now carry the mapped Swedish text; bank_error_code, bank_name and
  psu_type still flow so the settings page keeps its targeted guidance
  (Handelsbanken fullmakt steps included).
- New audit events bank_connection.consent_denied and
  bank_connection.finalize_failed are emitted on the two failure paths
  and persisted to event_log, so support can answer which attempt failed,
  with which provider error, on whose side, even after the row is gone.


Claude-Session: https://claude.ai/code/session_01SyDuePXxUFowaPBKpAv8SF

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-26 09:35:26 +02:00

67 lines
3.1 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import { getBankConnectionErrorMessage } from '../get-error-message'
// The PSD2 callback mapper (issue #1716): raw provider tokens used to reach
// the user verbatim ("server_error", "invalid_state"), which left them with
// nothing to act on and support with nothing to answer.
describe('getBankConnectionErrorMessage', () => {
it('maps a user cancel (access_denied) without echoing the provider text', () => {
const msg = getBankConnectionErrorMessage('access_denied', 'User cancelled')
expect(msg).toContain('Anslutningen avbröts hos banken')
expect(msg).not.toContain('User cancelled')
})
it('treats "Cancelled by user" descriptions as a cancel regardless of code', () => {
const msg = getBankConnectionErrorMessage('server_error', 'Cancelled by user')
expect(msg).toContain('Anslutningen avbröts hos banken')
})
it('maps a bare server_error to the bank-side failure explanation', () => {
const msg = getBankConnectionErrorMessage('server_error')
expect(msg).toContain('fel på bankens sida')
// The Handelsbanken corporate case: point at mandates without naming a bank
// (the settings page adds the bank-specific steps from bank_error_code).
expect(msg).toContain('fullmakt')
})
it('surfaces the provider description in parentheses on unknown codes', () => {
const msg = getBankConnectionErrorMessage('aspsp_error', 'PSU lacks corporate mandate')
expect(msg).toContain('Banken avvisade anslutningen')
expect(msg).toContain('(PSU lacks corporate mandate)')
})
it('does not duplicate the code when the description equals the code', () => {
const msg = getBankConnectionErrorMessage('server_error', 'server_error')
expect(msg).toContain('fel på bankens sida')
expect(msg).not.toContain('(server_error)')
})
it('maps session-expiry descriptions to the expired-session message', () => {
const msg = getBankConnectionErrorMessage('server_error', 'Session expired at ASPSP')
expect(msg).toContain('inloggningssession')
expect(msg).toContain('(Session expired at ASPSP)')
})
it('maps the internal invalid_state token to a retry explanation', () => {
const msg = getBankConnectionErrorMessage('invalid_state')
expect(msg).toContain('Starta bankkopplingen på nytt')
expect(msg).not.toContain('invalid_state')
})
it('maps missing_parameters and invalid_code_format to Swedish', () => {
expect(getBankConnectionErrorMessage('missing_parameters')).toContain('ofullständigt svar')
expect(getBankConnectionErrorMessage('invalid_code_format')).toContain('ogiltigt svar')
})
it('maps temporarily_unavailable to the try-later message', () => {
const msg = getBankConnectionErrorMessage('temporarily_unavailable')
expect(msg).toContain('tillfälligt otillgänglig')
})
it('falls back to a Swedish rejection message for unknown codes without description', () => {
const msg = getBankConnectionErrorMessage('weird_code')
expect(msg).toContain('Banken avvisade anslutningen')
expect(msg).not.toContain('weird_code')
})
})