@@ -3,10 +3,12 @@ import {
33 assertEquals ,
44 assertFalse ,
55 assertRejects ,
6+ assertStrictEquals ,
67 assertStringIncludes ,
78} from "@std/assert" ;
89import {
910 FetchClient ,
11+ FetchClientDeserializationError ,
1012 FetchClientError ,
1113 type FetchClientResponse ,
1214 ProblemDetails ,
@@ -260,3 +262,92 @@ Deno.test("problem details are populated on error responses", async () => {
260262 assert ( res . problem . errors . server ) ;
261263 assertEquals ( res . problem . errors . server [ 0 ] , "Database connection failed" ) ;
262264} ) ;
265+
266+ Deno . test ( "malformed JSON in a successful response throws a deserialization error" , async ( ) => {
267+ const provider = new FetchClientProvider ( ) ;
268+ provider . fetch = ( ) =>
269+ Promise . resolve (
270+ new Response ( '{"value":' , {
271+ status : 200 ,
272+ headers : { "Content-Type" : "application/json" } ,
273+ } ) ,
274+ ) ;
275+
276+ const client = provider . getFetchClient ( ) ;
277+ let callbackResponse : FetchClientResponse < unknown > | undefined ;
278+
279+ const error = await assertRejects (
280+ ( ) =>
281+ client . getJSON ( "https://example.com/malformed" , {
282+ errorCallback : ( response ) => {
283+ callbackResponse = response ;
284+ return false ;
285+ } ,
286+ } ) ,
287+ FetchClientDeserializationError ,
288+ ) ;
289+
290+ assert ( error instanceof FetchClientDeserializationError ) ;
291+ assert ( error . cause instanceof SyntaxError ) ;
292+ assertEquals ( error . responseText , '{"value":' ) ;
293+ assertEquals ( error . response . status , 200 ) ;
294+ assert ( error . response . ok ) ;
295+ assertEquals ( error . response . data , null ) ;
296+ assertStringIncludes (
297+ error . response . problem . title ?? "" ,
298+ "Unable to deserialize response data" ,
299+ ) ;
300+ assertStrictEquals ( callbackResponse , error . response ) ;
301+ assertEquals ( client . requestCount , 0 ) ;
302+ assertEquals ( provider . requestCount , 0 ) ;
303+ } ) ;
304+
305+ Deno . test ( "aborting a successful response body read preserves the abort reason" , async ( ) => {
306+ const provider = new FetchClientProvider ( ) ;
307+ provider . fetch = ( request ) => {
308+ const signal = request instanceof Request
309+ ? request . signal
310+ : new Request ( request ) . signal ;
311+ const body = new ReadableStream < Uint8Array > ( {
312+ start ( controller ) {
313+ controller . enqueue ( new TextEncoder ( ) . encode ( '{"value":' ) ) ;
314+ signal . addEventListener (
315+ "abort" ,
316+ ( ) => controller . error ( signal . reason ) ,
317+ { once : true } ,
318+ ) ;
319+ } ,
320+ } ) ;
321+
322+ return Promise . resolve (
323+ new Response ( body , {
324+ status : 200 ,
325+ headers : { "Content-Type" : "application/json" } ,
326+ } ) ,
327+ ) ;
328+ } ;
329+
330+ const client = provider . getFetchClient ( ) ;
331+ const abortController = new AbortController ( ) ;
332+ const abortReason = new DOMException ( "Query cancelled" , "AbortError" ) ;
333+ let middlewareSawAbort = false ;
334+ client . use ( async ( _context , next ) => {
335+ try {
336+ await next ( ) ;
337+ } catch ( error ) {
338+ middlewareSawAbort = error === abortReason ;
339+ throw error ;
340+ }
341+ } ) ;
342+
343+ const request = client . getJSON ( "https://example.com/stream" , {
344+ signal : abortController . signal ,
345+ } ) ;
346+ setTimeout ( ( ) => abortController . abort ( abortReason ) , 10 ) ;
347+
348+ const error = await assertRejects ( ( ) => request ) ;
349+ assertStrictEquals ( error , abortReason ) ;
350+ assert ( middlewareSawAbort ) ;
351+ assertEquals ( client . requestCount , 0 ) ;
352+ assertEquals ( provider . requestCount , 0 ) ;
353+ } ) ;
0 commit comments