@@ -3,7 +3,7 @@ import { Logger } from '@nestjs/common'
33import { mayachainAssetId } from '@shapeshiftoss/caip'
44
55import type { Swap } from '../types'
6- import { calculateFeeForSwap } from '../utils'
6+ import { calculateFeeForSwap , describeError } from '../utils'
77
88// Minimal swap shape exercising calculateFeeForSwap's fee/volume math. CACAO fee asset so a stored
99// '0' fee amount resolves to actualFeeUsd = 0 (the real 0-bps case this branch introduced).
@@ -80,3 +80,56 @@ describe('calculateFeeForSwap volume reconstruction', () => {
8080 expect ( result ?. volumeUsd ) . toBe ( 100 )
8181 } )
8282} )
83+
84+ describe ( 'describeError' , ( ) => {
85+ // Shaped like a real AxiosError: isAxiosError is the flag axios.isAxiosError checks.
86+ const axiosError = ( status : number | undefined , data ?: unknown , message = 'Request failed with status code 500' ) =>
87+ Object . assign ( new Error ( message ) , {
88+ isAxiosError : true ,
89+ response : status === undefined ? undefined : { status, data } ,
90+ } )
91+
92+ it ( 'prefers the message the server sent over the generic axios message' , ( ) => {
93+ expect ( describeError ( axiosError ( 404 , { message : 'tx not found' } ) ) ) . toBe ( 'tx not found' )
94+ } )
95+
96+ it ( 'reads an error key when the body has no message' , ( ) => {
97+ expect ( describeError ( axiosError ( 429 , { error : 'rate limited' } ) ) ) . toBe ( 'rate limited' )
98+ } )
99+
100+ it ( 'serialises a message or error that is not a string' , ( ) => {
101+ expect ( describeError ( axiosError ( 400 , { error : { message : 'nested' } } ) ) ) . toBe ( '{"message":"nested"}' )
102+ } )
103+
104+ it ( 'falls back when the body has neither key' , ( ) => {
105+ const reason = describeError (
106+ axiosError ( 400 , { errors : { amount : [ 'too small' ] } } , 'Request failed with status code 400' ) ,
107+ )
108+
109+ expect ( reason ) . toBe ( 'Request failed with status code 400' )
110+ } )
111+
112+ it ( 'ignores a string body so an error page never reaches the log' , ( ) => {
113+ const reason = describeError ( axiosError ( 403 , '<html>' . padEnd ( 5000 , 'x' ) , 'Request failed with status code 403' ) )
114+
115+ expect ( reason ) . toBe ( 'Request failed with status code 403' )
116+ } )
117+
118+ it ( 'falls back to the message for an axios error that never got a response' , ( ) => {
119+ expect ( describeError ( axiosError ( undefined , undefined , 'connect ETIMEDOUT' ) ) ) . toBe ( 'connect ETIMEDOUT' )
120+ } )
121+
122+ it ( 'returns the message for a plain error' , ( ) => {
123+ expect ( describeError ( new Error ( 'Non-JSON response: HTTP 403 Forbidden' ) ) ) . toBe (
124+ 'Non-JSON response: HTTP 403 Forbidden' ,
125+ )
126+ } )
127+
128+ it ( 'handles a thrown string' , ( ) => {
129+ expect ( describeError ( 'boom' ) ) . toBe ( 'boom' )
130+ } )
131+
132+ it ( 'does not stringify a thrown object as [object Object]' , ( ) => {
133+ expect ( describeError ( { nope : true } ) ) . toBe ( 'Unknown error' )
134+ } )
135+ } )
0 commit comments