11import { RPCType } from '@opentelemetry/core' ;
22import { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions' ;
33import {
4- flush ,
4+ flushIfServerless ,
55 getActiveSpan ,
66 getRootSpan ,
77 getTraceMetaTags ,
88 SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ,
99 SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ,
10- vercelWaitUntil ,
1110} from '@sentry/core' ;
1211import { PassThrough } from 'stream' ;
1312import { beforeEach , describe , expect , test , vi } from 'vitest' ;
@@ -17,73 +16,20 @@ vi.mock('@opentelemetry/core', () => ({
1716 RPCType : { HTTP : 'http' } ,
1817 getRPCMetadata : vi . fn ( ) ,
1918} ) ) ;
20-
2119vi . mock ( '@sentry/core' , ( ) => ( {
2220 SEMANTIC_ATTRIBUTE_SENTRY_SOURCE : 'sentry.source' ,
2321 SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN : 'sentry.origin' ,
2422 getActiveSpan : vi . fn ( ) ,
2523 getRootSpan : vi . fn ( ) ,
2624 getTraceMetaTags : vi . fn ( ) ,
27- flush : vi . fn ( ) ,
28- vercelWaitUntil : vi . fn ( ) ,
25+ flushIfServerless : vi . fn ( ) ,
2926} ) ) ;
3027
3128describe ( 'wrapSentryHandleRequest' , ( ) => {
3229 beforeEach ( ( ) => {
3330 vi . clearAllMocks ( ) ;
3431 } ) ;
3532
36- test ( 'should call flush on successful execution' , async ( ) => {
37- const originalHandler = vi . fn ( ) . mockResolvedValue ( 'success response' ) ;
38- const wrappedHandler = wrapSentryHandleRequest ( originalHandler ) ;
39-
40- const request = new Request ( 'https://example.com' ) ;
41- const responseStatusCode = 200 ;
42- const responseHeaders = new Headers ( ) ;
43- const routerContext = { staticHandlerContext : { matches : [ ] } } as any ;
44- const loadContext = { } as any ;
45-
46- await wrappedHandler ( request , responseStatusCode , responseHeaders , routerContext , loadContext ) ;
47-
48- expect ( vercelWaitUntil ) . toHaveBeenCalledWith ( flush ( ) ) ;
49- expect ( flush ) . toHaveBeenCalled ( ) ;
50- } ) ;
51-
52- test ( 'should call flush even when original handler throws an error' , async ( ) => {
53- const mockError = new Error ( 'Handler failed' ) ;
54- const originalHandler = vi . fn ( ) . mockRejectedValue ( mockError ) ;
55- const wrappedHandler = wrapSentryHandleRequest ( originalHandler ) ;
56-
57- const request = new Request ( 'https://example.com' ) ;
58- const responseStatusCode = 200 ;
59- const responseHeaders = new Headers ( ) ;
60- const routerContext = { staticHandlerContext : { matches : [ ] } } as any ;
61- const loadContext = { } as any ;
62-
63- await expect (
64- wrappedHandler ( request , responseStatusCode , responseHeaders , routerContext , loadContext ) ,
65- ) . rejects . toThrow ( 'Handler failed' ) ;
66-
67- expect ( vercelWaitUntil ) . toHaveBeenCalledWith ( flush ( ) ) ;
68- expect ( flush ) . toHaveBeenCalled ( ) ;
69- } ) ;
70-
71- test ( 'should propagate errors from original handler' , async ( ) => {
72- const mockError = new Error ( 'Test error' ) ;
73- const originalHandler = vi . fn ( ) . mockRejectedValue ( mockError ) ;
74- const wrappedHandler = wrapSentryHandleRequest ( originalHandler ) ;
75-
76- const request = new Request ( 'https://example.com' ) ;
77- const responseStatusCode = 500 ;
78- const responseHeaders = new Headers ( ) ;
79- const routerContext = { staticHandlerContext : { matches : [ ] } } as any ;
80- const loadContext = { } as any ;
81-
82- await expect ( wrappedHandler ( request , responseStatusCode , responseHeaders , routerContext , loadContext ) ) . rejects . toBe (
83- mockError ,
84- ) ;
85- } ) ;
86-
8733 test ( 'should call original handler with same parameters' , async ( ) => {
8834 const originalHandler = vi . fn ( ) . mockResolvedValue ( 'original response' ) ;
8935 const wrappedHandler = wrapSentryHandleRequest ( originalHandler ) ;
@@ -179,6 +125,151 @@ describe('wrapSentryHandleRequest', () => {
179125
180126 expect ( getRPCMetadata ) . not . toHaveBeenCalled ( ) ;
181127 } ) ;
128+
129+ test ( 'should call flushIfServerless on successful execution' , async ( ) => {
130+ const originalHandler = vi . fn ( ) . mockResolvedValue ( 'success response' ) ;
131+ const wrappedHandler = wrapSentryHandleRequest ( originalHandler ) ;
132+
133+ const request = new Request ( 'https://example.com' ) ;
134+ const responseStatusCode = 200 ;
135+ const responseHeaders = new Headers ( ) ;
136+ const routerContext = { staticHandlerContext : { matches : [ ] } } as any ;
137+ const loadContext = { } as any ;
138+
139+ await wrappedHandler ( request , responseStatusCode , responseHeaders , routerContext , loadContext ) ;
140+
141+ expect ( flushIfServerless ) . toHaveBeenCalled ( ) ;
142+ } ) ;
143+
144+ test ( 'should call flushIfServerless even when original handler throws an error' , async ( ) => {
145+ const mockError = new Error ( 'Handler failed' ) ;
146+ const originalHandler = vi . fn ( ) . mockRejectedValue ( mockError ) ;
147+ const wrappedHandler = wrapSentryHandleRequest ( originalHandler ) ;
148+
149+ const request = new Request ( 'https://example.com' ) ;
150+ const responseStatusCode = 200 ;
151+ const responseHeaders = new Headers ( ) ;
152+ const routerContext = { staticHandlerContext : { matches : [ ] } } as any ;
153+ const loadContext = { } as any ;
154+
155+ await expect (
156+ wrappedHandler ( request , responseStatusCode , responseHeaders , routerContext , loadContext ) ,
157+ ) . rejects . toThrow ( 'Handler failed' ) ;
158+
159+ expect ( flushIfServerless ) . toHaveBeenCalled ( ) ;
160+ } ) ;
161+
162+ test ( 'should propagate errors from original handler' , async ( ) => {
163+ const mockError = new Error ( 'Test error' ) ;
164+ const originalHandler = vi . fn ( ) . mockRejectedValue ( mockError ) ;
165+ const wrappedHandler = wrapSentryHandleRequest ( originalHandler ) ;
166+
167+ const request = new Request ( 'https://example.com' ) ;
168+ const responseStatusCode = 500 ;
169+ const responseHeaders = new Headers ( ) ;
170+ const routerContext = { staticHandlerContext : { matches : [ ] } } as any ;
171+ const loadContext = { } as any ;
172+
173+ await expect ( wrappedHandler ( request , responseStatusCode , responseHeaders , routerContext , loadContext ) ) . rejects . toBe (
174+ mockError ,
175+ ) ;
176+ } ) ;
177+ } ) ;
178+
179+ test ( 'should call original handler with same parameters' , async ( ) => {
180+ const originalHandler = vi . fn ( ) . mockResolvedValue ( 'original response' ) ;
181+ const wrappedHandler = wrapSentryHandleRequest ( originalHandler ) ;
182+
183+ const request = new Request ( 'https://taco.burrito' ) ;
184+ const responseStatusCode = 200 ;
185+ const responseHeaders = new Headers ( ) ;
186+ const routerContext = { staticHandlerContext : { matches : [ ] } } as any ;
187+ const loadContext = { } as any ;
188+
189+ const result = await wrappedHandler ( request , responseStatusCode , responseHeaders , routerContext , loadContext ) ;
190+
191+ expect ( originalHandler ) . toHaveBeenCalledWith (
192+ request ,
193+ responseStatusCode ,
194+ responseHeaders ,
195+ routerContext ,
196+ loadContext ,
197+ ) ;
198+ expect ( result ) . toBe ( 'original response' ) ;
199+ } ) ;
200+
201+ test ( 'should set span attributes when parameterized path exists and active span exists' , async ( ) => {
202+ const originalHandler = vi . fn ( ) . mockResolvedValue ( 'test' ) ;
203+ const wrappedHandler = wrapSentryHandleRequest ( originalHandler ) ;
204+
205+ const mockActiveSpan = { } ;
206+ const mockRootSpan = { setAttributes : vi . fn ( ) } ;
207+ const mockRpcMetadata = { type : RPCType . HTTP , route : '/some-path' } ;
208+
209+ ( getActiveSpan as unknown as ReturnType < typeof vi . fn > ) . mockReturnValue ( mockActiveSpan ) ;
210+ ( getRootSpan as unknown as ReturnType < typeof vi . fn > ) . mockReturnValue ( mockRootSpan ) ;
211+ const getRPCMetadata = vi . fn ( ) . mockReturnValue ( mockRpcMetadata ) ;
212+ ( vi . importActual ( '@opentelemetry/core' ) as unknown as { getRPCMetadata : typeof getRPCMetadata } ) . getRPCMetadata =
213+ getRPCMetadata ;
214+
215+ const routerContext = {
216+ staticHandlerContext : {
217+ matches : [ { route : { path : 'some-path' } } ] ,
218+ } ,
219+ } as any ;
220+
221+ await wrappedHandler ( new Request ( 'https://nacho.queso' ) , 200 , new Headers ( ) , routerContext , { } as any ) ;
222+
223+ expect ( mockRootSpan . setAttributes ) . toHaveBeenCalledWith ( {
224+ [ ATTR_HTTP_ROUTE ] : '/some-path' ,
225+ [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] : 'route' ,
226+ [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : 'auto.http.react-router.request-handler' ,
227+ } ) ;
228+ expect ( mockRpcMetadata . route ) . toBe ( '/some-path' ) ;
229+ } ) ;
230+
231+ test ( 'should not set span attributes when parameterized path does not exist' , async ( ) => {
232+ const mockActiveSpan = { } ;
233+ const mockRootSpan = { setAttributes : vi . fn ( ) } ;
234+
235+ ( getActiveSpan as unknown as ReturnType < typeof vi . fn > ) . mockReturnValue ( mockActiveSpan ) ;
236+ ( getRootSpan as unknown as ReturnType < typeof vi . fn > ) . mockReturnValue ( mockRootSpan ) ;
237+
238+ const originalHandler = vi . fn ( ) . mockResolvedValue ( 'test' ) ;
239+ const wrappedHandler = wrapSentryHandleRequest ( originalHandler ) ;
240+
241+ const routerContext = {
242+ staticHandlerContext : {
243+ matches : [ ] ,
244+ } ,
245+ } as any ;
246+
247+ await wrappedHandler ( new Request ( 'https://guapo.chulo' ) , 200 , new Headers ( ) , routerContext , { } as any ) ;
248+
249+ expect ( mockRootSpan . setAttributes ) . not . toHaveBeenCalled ( ) ;
250+ } ) ;
251+
252+ test ( 'should not set span attributes when active span does not exist' , async ( ) => {
253+ const originalHandler = vi . fn ( ) . mockResolvedValue ( 'test' ) ;
254+ const wrappedHandler = wrapSentryHandleRequest ( originalHandler ) ;
255+
256+ const mockRpcMetadata = { type : RPCType . HTTP , route : '/some-path' } ;
257+
258+ ( getActiveSpan as unknown as ReturnType < typeof vi . fn > ) . mockReturnValue ( null ) ;
259+
260+ const getRPCMetadata = vi . fn ( ) . mockReturnValue ( mockRpcMetadata ) ;
261+ ( vi . importActual ( '@opentelemetry/core' ) as unknown as { getRPCMetadata : typeof getRPCMetadata } ) . getRPCMetadata =
262+ getRPCMetadata ;
263+
264+ const routerContext = {
265+ staticHandlerContext : {
266+ matches : [ { route : { path : 'some-path' } } ] ,
267+ } ,
268+ } as any ;
269+
270+ await wrappedHandler ( new Request ( 'https://tio.pepe' ) , 200 , new Headers ( ) , routerContext , { } as any ) ;
271+
272+ expect ( getRPCMetadata ) . not . toHaveBeenCalled ( ) ;
182273} ) ;
183274
184275describe ( 'getMetaTagTransformer' , ( ) => {
0 commit comments