@@ -271,4 +271,114 @@ describe('getFlattenedRoutes', () => {
271271 expect ( usersRoute ?. hooks . response [ 401 ] . $ref ) . toBe ( 'ErrorResponse' )
272272 expect ( usersRoute ?. hooks . response [ 500 ] . $ref ) . toBe ( 'ErrorResponse' )
273273 } )
274+
275+ it ( 'correctly handles union response schemas from guards' , ( ) => {
276+ // Regression test: unions don't have 'type' property, only 'anyOf'
277+ // Previous implementation would misclassify them as status code objects
278+ const app = new Elysia ( ) . guard (
279+ {
280+ response : t . Union ( [ t . String ( ) , t . Number ( ) ] )
281+ } ,
282+ ( app ) =>
283+ app . get ( '/data' , ( ) => 'test' , {
284+ response : t . Object ( {
285+ value : t . String ( )
286+ } )
287+ } )
288+ )
289+
290+ // @ts -expect-error - accessing protected method for testing
291+ const flatRoutes = app . getFlattenedRoutes ( )
292+
293+ const dataRoute = flatRoutes . find ( ( r ) => r . path === '/data' )
294+
295+ expect ( dataRoute ) . toBeDefined ( )
296+ expect ( dataRoute ?. hooks . response ) . toBeDefined ( )
297+
298+ // The route-level object schema should be preserved (takes precedence)
299+ expect ( dataRoute ?. hooks . response . type ) . toBe ( 'object' )
300+ expect ( dataRoute ?. hooks . response . properties ) . toHaveProperty ( 'value' )
301+
302+ // Should NOT have anyOf from the union polluting the response
303+ expect ( dataRoute ?. hooks . response . anyOf ) . toBeUndefined ( )
304+
305+ // Should NOT have a synthetic status code structure
306+ expect ( dataRoute ?. hooks . response [ 200 ] ) . toBeUndefined ( )
307+ } )
308+
309+ it ( 'correctly handles intersect response schemas from guards' , ( ) => {
310+ // Intersects use 'allOf' instead of 'type'
311+ const app = new Elysia ( ) . guard (
312+ {
313+ response : {
314+ 200 : t . Intersect ( [
315+ t . Object ( { id : t . String ( ) } ) ,
316+ t . Object ( { timestamp : t . Number ( ) } )
317+ ] ) ,
318+ 404 : t . Object ( { error : t . String ( ) } )
319+ }
320+ } ,
321+ ( app ) =>
322+ app . get ( '/item' , ( ) => ( { id : '1' , timestamp : 123 } ) , {
323+ response : t . Object ( {
324+ id : t . String ( ) ,
325+ timestamp : t . Number ( ) ,
326+ extra : t . String ( )
327+ } )
328+ } )
329+ )
330+
331+ // @ts -expect-error - accessing protected method for testing
332+ const flatRoutes = app . getFlattenedRoutes ( )
333+
334+ const itemRoute = flatRoutes . find ( ( r ) => r . path === '/item' )
335+
336+ expect ( itemRoute ) . toBeDefined ( )
337+ expect ( itemRoute ?. hooks . response ) . toBeDefined ( )
338+
339+ // The 200 response should be the route-level object (takes precedence)
340+ expect ( itemRoute ?. hooks . response [ 200 ] ) . toBeDefined ( )
341+ expect ( itemRoute ?. hooks . response [ 200 ] . type ) . toBe ( 'object' )
342+ expect ( itemRoute ?. hooks . response [ 200 ] . properties ) . toHaveProperty ( 'extra' )
343+
344+ // The 404 from guard should be preserved
345+ expect ( itemRoute ?. hooks . response [ 404 ] ) . toBeDefined ( )
346+ expect ( itemRoute ?. hooks . response [ 404 ] . type ) . toBe ( 'object' )
347+ expect ( itemRoute ?. hooks . response [ 404 ] . properties ) . toHaveProperty ( 'error' )
348+
349+ // Should NOT have allOf polluting the status code object
350+ expect ( itemRoute ?. hooks . response . allOf ) . toBeUndefined ( )
351+ } )
352+
353+ it ( 'correctly handles t.Any and other schemas without type property' , ( ) => {
354+ // Test other schemas that don't have 'type' property
355+ const app = new Elysia ( ) . guard (
356+ {
357+ response : {
358+ 200 : t . Any ( ) ,
359+ 500 : t . Object ( { error : t . String ( ) } )
360+ }
361+ } ,
362+ ( app ) =>
363+ app . get ( '/any' , ( ) => 'anything' , {
364+ response : t . String ( )
365+ } )
366+ )
367+
368+ // @ts -expect-error - accessing protected method for testing
369+ const flatRoutes = app . getFlattenedRoutes ( )
370+
371+ const anyRoute = flatRoutes . find ( ( r ) => r . path === '/any' )
372+
373+ expect ( anyRoute ) . toBeDefined ( )
374+ expect ( anyRoute ?. hooks . response ) . toBeDefined ( )
375+
376+ // The 200 response should be the route-level string schema
377+ expect ( anyRoute ?. hooks . response [ 200 ] ) . toBeDefined ( )
378+ expect ( anyRoute ?. hooks . response [ 200 ] . type ) . toBe ( 'string' )
379+
380+ // The 500 from guard should be preserved
381+ expect ( anyRoute ?. hooks . response [ 500 ] ) . toBeDefined ( )
382+ expect ( anyRoute ?. hooks . response [ 500 ] . type ) . toBe ( 'object' )
383+ } )
274384} )
0 commit comments