@@ -242,10 +242,49 @@ suite("cloud/api", () => {
242242 await assert . rejects ( ( ) => api . getTeams ( ) , / N o t a u t h e n t i c a t e d / )
243243 } )
244244
245- test ( "throws on non-ok response" , async ( ) => {
246- sinon . stub ( globalThis , "fetch" ) . resolves ( mockResponse ( { } , false , 403 ) )
245+ test ( "throws with detail message from API error response" , async ( ) => {
246+ sinon
247+ . stub ( globalThis , "fetch" )
248+ . resolves (
249+ mockResponse (
250+ {
251+ detail :
252+ "App limit reached (3). Upgrade your plan to create more apps." ,
253+ } ,
254+ false ,
255+ 403 ,
256+ ) ,
257+ )
258+
259+ await assert . rejects (
260+ ( ) => api . createApp ( "team-id" , "New App" ) ,
261+ / A p p l i m i t r e a c h e d \( 3 \) . U p g r a d e y o u r p l a n t o c r e a t e m o r e a p p s \. / ,
262+ )
263+ } )
264+
265+ test ( "falls back to generic message when no detail in error response" , async ( ) => {
266+ sinon . stub ( globalThis , "fetch" ) . resolves ( mockResponse ( { } , false , 500 ) )
247267
248- await assert . rejects ( ( ) => api . getTeams ( ) , / A P I r e q u e s t f a i l e d / )
268+ await assert . rejects (
269+ ( ) => api . createApp ( "team-id" , "New App" ) ,
270+ / A P I r e q u e s t f a i l e d : P O S T \/ a p p s \/ r e t u r n e d 5 0 0 / ,
271+ )
272+ } )
273+
274+ test ( "falls back to generic message when response body is not JSON" , async ( ) => {
275+ sinon . stub ( globalThis , "fetch" ) . resolves ( {
276+ ok : false ,
277+ status : 502 ,
278+ statusText : "Bad Gateway" ,
279+ json : async ( ) => {
280+ throw new SyntaxError ( "Unexpected token" )
281+ } ,
282+ } as unknown as Response )
283+
284+ await assert . rejects (
285+ ( ) => api . createApp ( "team-id" , "New App" ) ,
286+ / A P I r e q u e s t f a i l e d : P O S T \/ a p p s \/ r e t u r n e d 5 0 2 / ,
287+ )
249288 } )
250289
251290 test ( "getApps returns app data" , async ( ) => {
@@ -272,6 +311,34 @@ suite("cloud/api", () => {
272311 assert . strictEqual ( options ?. method , "POST" )
273312 } )
274313
314+ test ( "streamAppLogs throws with detail message from API error response" , async ( ) => {
315+ sinon
316+ . stub ( globalThis , "fetch" )
317+ . resolves ( mockResponse ( { detail : "App not found" } , false , 404 ) )
318+
319+ const stream = api . streamAppLogs ( {
320+ appId : "app-id" ,
321+ tail : 100 ,
322+ since : "2024-01-01T00:00:00Z" ,
323+ follow : false ,
324+ } )
325+
326+ await assert . rejects ( ( ) => stream . next ( ) , / A p p n o t f o u n d / )
327+ } )
328+
329+ test ( "streamAppLogs falls back to generic message when no detail" , async ( ) => {
330+ sinon . stub ( globalThis , "fetch" ) . resolves ( mockResponse ( { } , false , 502 ) )
331+
332+ const stream = api . streamAppLogs ( {
333+ appId : "app-id" ,
334+ tail : 100 ,
335+ since : "2024-01-01T00:00:00Z" ,
336+ follow : false ,
337+ } )
338+
339+ await assert . rejects ( ( ) => stream . next ( ) , / F a i l e d t o s t r e a m l o g s : 5 0 2 / )
340+ } )
341+
275342 test ( "request includes auth header and user-agent" , async ( ) => {
276343 mockSession ( "my_token" )
277344
0 commit comments