1
1
import { RPCType } from '@opentelemetry/core' ;
2
2
import { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions' ;
3
3
import {
4
- flush ,
4
+ flushIfServerless ,
5
5
getActiveSpan ,
6
6
getRootSpan ,
7
7
getTraceMetaTags ,
8
8
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ,
9
9
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ,
10
- vercelWaitUntil ,
11
10
} from '@sentry/core' ;
12
11
import { PassThrough } from 'stream' ;
13
12
import { beforeEach , describe , expect , test , vi } from 'vitest' ;
@@ -17,73 +16,20 @@ vi.mock('@opentelemetry/core', () => ({
17
16
RPCType : { HTTP : 'http' } ,
18
17
getRPCMetadata : vi . fn ( ) ,
19
18
} ) ) ;
20
-
21
19
vi . mock ( '@sentry/core' , ( ) => ( {
22
20
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE : 'sentry.source' ,
23
21
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN : 'sentry.origin' ,
24
22
getActiveSpan : vi . fn ( ) ,
25
23
getRootSpan : vi . fn ( ) ,
26
24
getTraceMetaTags : vi . fn ( ) ,
27
- flush : vi . fn ( ) ,
28
- vercelWaitUntil : vi . fn ( ) ,
25
+ flushIfServerless : vi . fn ( ) ,
29
26
} ) ) ;
30
27
31
28
describe ( 'wrapSentryHandleRequest' , ( ) => {
32
29
beforeEach ( ( ) => {
33
30
vi . clearAllMocks ( ) ;
34
31
} ) ;
35
32
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
-
87
33
test ( 'should call original handler with same parameters' , async ( ) => {
88
34
const originalHandler = vi . fn ( ) . mockResolvedValue ( 'original response' ) ;
89
35
const wrappedHandler = wrapSentryHandleRequest ( originalHandler ) ;
@@ -179,6 +125,151 @@ describe('wrapSentryHandleRequest', () => {
179
125
180
126
expect ( getRPCMetadata ) . not . toHaveBeenCalled ( ) ;
181
127
} ) ;
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 ( ) ;
182
273
} ) ;
183
274
184
275
describe ( 'getMetaTagTransformer' , ( ) => {
0 commit comments