UNPKG

@ledgerhq/coin-stellar

Version:
134 lines (120 loc) 4.05 kB
// SPDX-FileCopyrightText: © 2026 LEDGER SAS // SPDX-License-Identifier: Apache-2.0 import { BadResponseError } from '@stellar/stellar-sdk' import { documentationSummaryFromHorizonBody, getHorizonErrorBody, isHorizonErrorBody, } from './horizonErrorBody' describe('isHorizonErrorBody', () => { it('accepts RFC 7807 problem bodies with title and status', () => { expect( isHorizonErrorBody({ type: 'https://stellar.org/horizon-errors/transaction_malformed', title: 'Transaction Malformed', status: 400, detail: 'Horizon could not decode the transaction envelope in this request.', }) ).toBe(true) }) it('rejects non-problem payloads', () => { expect(isHorizonErrorBody({ title: 'Missing status' })).toBe(false) expect(isHorizonErrorBody(null)).toBe(false) }) }) describe('getHorizonErrorBody', () => { const body = { type: 'https://stellar.org/horizon-errors/bad_request', title: 'Bad Request', status: 400, detail: 'Invalid cursor.', extras: { reason: 'cursor is invalid' }, } it('reads BadResponseError.response', () => { const error = new BadResponseError('bad', body) expect(getHorizonErrorBody(error)).toEqual(body) }) it('reads axios-like error.response.data', () => { const error = Object.assign(new Error('Request failed with status code 400'), { response: { data: body }, }) expect(getHorizonErrorBody(error)).toEqual(body) }) }) describe('documentationSummaryFromHorizonBody', () => { const genericDetail = 'The transaction failed when submitted to the Stellar network. The `extras.result_codes` field on this response contains further details.' it('prefers operation result code over generic tx_failed', () => { expect( documentationSummaryFromHorizonBody({ title: 'Transaction Failed', status: 400, detail: genericDetail, extras: { result_codes: { transaction: 'tx_failed', operations: ['op_underfunded'], }, }, }) ).toBe( 'The source account does not have enough balance to send the payment amount while maintaining its minimum reserve.' ) }) it('skips op_inner and uses the first failing operation code', () => { expect( documentationSummaryFromHorizonBody({ title: 'Transaction Failed', status: 400, detail: genericDetail, extras: { result_codes: { transaction: 'tx_failed', operations: ['op_inner', 'op_underfunded'], }, }, }) ).toBe( 'The source account does not have enough balance to send the payment amount while maintaining its minimum reserve.' ) }) it('uses transaction code when it is specific', () => { expect( documentationSummaryFromHorizonBody({ title: 'Transaction Failed', status: 400, detail: genericDetail, extras: { result_codes: { transaction: 'tx_bad_seq' }, }, }) ).toBe('Sequence number does not match source account.') }) it('uses Horizon detail when no result codes are present', () => { expect( documentationSummaryFromHorizonBody({ title: 'Transaction Malformed', status: 400, detail: 'Horizon could not decode the transaction envelope in this request.', extras: { envelope_xdr: 'AAAA' }, }) ).toBe('Horizon could not decode the transaction envelope in this request.') }) it('uses extras.reason for validation errors', () => { expect( documentationSummaryFromHorizonBody({ title: 'Bad Request', status: 400, extras: { invalid_field: 'cursor', reason: 'cursor is invalid' }, }) ).toBe('cursor is invalid (field: cursor)') }) it('falls back to title when nothing else is available', () => { expect( documentationSummaryFromHorizonBody({ title: 'Rate Limited', status: 429, }) ).toBe('Rate Limited') }) })