-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Expand file tree
/
Copy pathmiddleware.test.ts
More file actions
336 lines (266 loc) · 15.1 KB
/
middleware.test.ts
File metadata and controls
336 lines (266 loc) · 15.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
import { expect, test } from '@playwright/test';
import { waitForTransaction, waitForError } from '@sentry-internal/test-utils';
test.describe('Server Middleware Instrumentation', () => {
test('should create separate spans for each server middleware', async ({ request }) => {
const serverTxnEventPromise = waitForTransaction('nuxt-3', txnEvent => {
return txnEvent.transaction?.includes('GET /api/middleware-test') ?? false;
});
// Make request to the API endpoint that will trigger all server middleware
const response = await request.get('/api/middleware-test');
expect(response.status()).toBe(200);
const responseData = await response.json();
expect(responseData.message).toBe('Server middleware test endpoint');
const serverTxnEvent = await serverTxnEventPromise;
// Verify that we have spans for each middleware
const middlewareSpans = serverTxnEvent.spans?.filter(span => span.op === 'middleware.nuxt') || [];
// 3 simple + 3 hooks (onRequest+handler+onBeforeResponse) + 5 array hooks (2 onRequest + 1 handler + 2 onBeforeResponse)
expect(middlewareSpans).toHaveLength(11);
// Check for specific middleware spans
const firstMiddlewareSpan = middlewareSpans.find(span => span.data?.['nuxt.middleware.name'] === '01.first');
const secondMiddlewareSpan = middlewareSpans.find(span => span.data?.['nuxt.middleware.name'] === '02.second');
const authMiddlewareSpan = middlewareSpans.find(span => span.data?.['nuxt.middleware.name'] === '03.auth');
const hooksOnRequestSpan = middlewareSpans.find(span => span.data?.['nuxt.middleware.name'] === '04.hooks');
const arrayHooksHandlerSpan = middlewareSpans.find(
span => span.data?.['nuxt.middleware.name'] === '05.array-hooks',
);
expect(firstMiddlewareSpan).toBeDefined();
expect(secondMiddlewareSpan).toBeDefined();
expect(authMiddlewareSpan).toBeDefined();
expect(hooksOnRequestSpan).toBeDefined();
expect(arrayHooksHandlerSpan).toBeDefined();
// Verify each span has the correct attributes
[firstMiddlewareSpan, secondMiddlewareSpan, authMiddlewareSpan].forEach(span => {
expect(span).toEqual(
expect.objectContaining({
op: 'middleware.nuxt',
data: expect.objectContaining({
'sentry.op': 'middleware.nuxt',
'sentry.origin': 'auto.middleware.nuxt',
'sentry.source': 'custom',
'http.request.method': 'GET',
'http.route': '/api/middleware-test',
}),
parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
span_id: expect.stringMatching(/[a-f0-9]{16}/),
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
}),
);
});
// Verify spans have different span IDs (each middleware gets its own span)
const spanIds = middlewareSpans.map(span => span.span_id);
const uniqueSpanIds = new Set(spanIds);
// 3 simple + 3 hooks (onRequest+handler+onBeforeResponse) + 5 array hooks (2 onRequest + 1 handler + 2 onBeforeResponse)
expect(uniqueSpanIds.size).toBe(11);
// Verify spans share the same trace ID
const traceIds = middlewareSpans.map(span => span.trace_id);
const uniqueTraceIds = new Set(traceIds);
expect(uniqueTraceIds.size).toBe(1);
});
test('middleware spans should have proper parent-child relationship', async ({ request }) => {
const serverTxnEventPromise = waitForTransaction('nuxt-3', txnEvent => {
return txnEvent.transaction?.includes('GET /api/middleware-test') ?? false;
});
await request.get('/api/middleware-test');
const serverTxnEvent = await serverTxnEventPromise;
const middlewareSpans = serverTxnEvent.spans?.filter(span => span.op === 'middleware.nuxt') || [];
// All middleware spans should be children of the main transaction
middlewareSpans.forEach(span => {
expect(span.parent_span_id).toBe(serverTxnEvent.contexts?.trace?.span_id);
});
});
test('should capture errors thrown in middleware and associate them with the span', async ({ request }) => {
const serverTxnEventPromise = waitForTransaction('nuxt-3', txnEvent => {
return txnEvent.transaction?.includes('GET /api/middleware-test') ?? false;
});
const errorEventPromise = waitForError('nuxt-3', errorEvent => {
return errorEvent?.exception?.values?.[0]?.value === 'Auth middleware error';
});
// Make request with query param to trigger error in auth middleware
const response = await request.get('/api/middleware-test?throwError=true');
// The request should fail due to the middleware error
expect(response.status()).toBe(500);
const [serverTxnEvent, errorEvent] = await Promise.all([serverTxnEventPromise, errorEventPromise]);
// Find the auth middleware span
const authMiddlewareSpan = serverTxnEvent.spans?.find(
span => span.op === 'middleware.nuxt' && span.data?.['nuxt.middleware.name'] === '03.auth',
);
expect(authMiddlewareSpan).toBeDefined();
// Verify the span has error status
expect(authMiddlewareSpan?.status).toBe('internal_error');
// Verify the error event is associated with the correct transaction
expect(errorEvent.transaction).toContain('GET /api/middleware-test');
// Verify the error has the correct mechanism
expect(errorEvent.exception?.values?.[0]).toEqual(
expect.objectContaining({
value: 'Auth middleware error',
type: 'Error',
mechanism: expect.objectContaining({
handled: false,
// Type changes depending on whether it is being wrapped by Nitro or not
// This is a timing problem, sometimes Nitro can capture the error first, and sometimes it can't
// If nitro captures the error first, the type will be 'chained'
// If Sentry captures the error first, the type will be 'auto.middleware.nuxt'
type: expect.stringMatching(/^(auto\.middleware\.nuxt|chained)$/),
}),
}),
);
});
test('should create spans for onRequest and onBeforeResponse hooks', async ({ request }) => {
const serverTxnEventPromise = waitForTransaction('nuxt-3', txnEvent => {
return txnEvent.transaction?.includes('GET /api/middleware-test') ?? false;
});
// Make request to trigger middleware with hooks
const response = await request.get('/api/middleware-test');
expect(response.status()).toBe(200);
const serverTxnEvent = await serverTxnEventPromise;
const middlewareSpans = serverTxnEvent.spans?.filter(span => span.op === 'middleware.nuxt') || [];
// Find spans for the hooks middleware
const hooksSpans = middlewareSpans.filter(span => span.data?.['nuxt.middleware.name'] === '04.hooks');
// Should have spans for onRequest, handler, and onBeforeResponse
expect(hooksSpans).toHaveLength(3);
// Find specific hook spans
const onRequestSpan = hooksSpans.find(span => span.data?.['nuxt.middleware.hook.name'] === 'onRequest');
const handlerSpan = hooksSpans.find(span => span.data?.['nuxt.middleware.hook.name'] === 'handler');
const onBeforeResponseSpan = hooksSpans.find(
span => span.data?.['nuxt.middleware.hook.name'] === 'onBeforeResponse',
);
expect(onRequestSpan).toBeDefined();
expect(handlerSpan).toBeDefined();
expect(onBeforeResponseSpan).toBeDefined();
// Verify span names include hook types
expect(onRequestSpan?.description).toBe('04.hooks.onRequest');
expect(handlerSpan?.description).toBe('04.hooks');
expect(onBeforeResponseSpan?.description).toBe('04.hooks.onBeforeResponse');
// Verify all spans have correct middleware name (without hook suffix)
[onRequestSpan, handlerSpan, onBeforeResponseSpan].forEach(span => {
expect(span?.data?.['nuxt.middleware.name']).toBe('04.hooks');
});
// Verify hook-specific attributes
expect(onRequestSpan?.data?.['nuxt.middleware.hook.name']).toBe('onRequest');
expect(handlerSpan?.data?.['nuxt.middleware.hook.name']).toBe('handler');
expect(onBeforeResponseSpan?.data?.['nuxt.middleware.hook.name']).toBe('onBeforeResponse');
// Verify no index attributes for single hooks
expect(onRequestSpan?.data).not.toHaveProperty('nuxt.middleware.hook.index');
expect(handlerSpan?.data).not.toHaveProperty('nuxt.middleware.hook.index');
expect(onBeforeResponseSpan?.data).not.toHaveProperty('nuxt.middleware.hook.index');
});
test('should create spans with index attributes for array hooks', async ({ request }) => {
const serverTxnEventPromise = waitForTransaction('nuxt-3', txnEvent => {
return txnEvent.transaction?.includes('GET /api/middleware-test') ?? false;
});
// Make request to trigger middleware with array hooks
const response = await request.get('/api/middleware-test');
expect(response.status()).toBe(200);
const serverTxnEvent = await serverTxnEventPromise;
const middlewareSpans = serverTxnEvent.spans?.filter(span => span.op === 'middleware.nuxt') || [];
// Find spans for the array hooks middleware
const arrayHooksSpans = middlewareSpans.filter(span => span.data?.['nuxt.middleware.name'] === '05.array-hooks');
// Should have spans for 2 onRequest + 1 handler + 2 onBeforeResponse = 5 spans
expect(arrayHooksSpans).toHaveLength(5);
// Find onRequest array spans
const onRequestSpans = arrayHooksSpans.filter(span => span.data?.['nuxt.middleware.hook.name'] === 'onRequest');
expect(onRequestSpans).toHaveLength(2);
// Find onBeforeResponse array spans
const onBeforeResponseSpans = arrayHooksSpans.filter(
span => span.data?.['nuxt.middleware.hook.name'] === 'onBeforeResponse',
);
expect(onBeforeResponseSpans).toHaveLength(2);
// Find handler span
const handlerSpan = arrayHooksSpans.find(span => span.data?.['nuxt.middleware.hook.name'] === 'handler');
expect(handlerSpan).toBeDefined();
// Verify index attributes for onRequest array
const onRequest0Span = onRequestSpans.find(span => span.data?.['nuxt.middleware.hook.index'] === 0);
const onRequest1Span = onRequestSpans.find(span => span.data?.['nuxt.middleware.hook.index'] === 1);
expect(onRequest0Span).toBeDefined();
expect(onRequest1Span).toBeDefined();
// Verify index attributes for onBeforeResponse array
const onBeforeResponse0Span = onBeforeResponseSpans.find(span => span.data?.['nuxt.middleware.hook.index'] === 0);
const onBeforeResponse1Span = onBeforeResponseSpans.find(span => span.data?.['nuxt.middleware.hook.index'] === 1);
expect(onBeforeResponse0Span).toBeDefined();
expect(onBeforeResponse1Span).toBeDefined();
// Verify span names for array handlers
expect(onRequest0Span?.description).toBe('05.array-hooks.onRequest');
expect(onRequest1Span?.description).toBe('05.array-hooks.onRequest');
expect(onBeforeResponse0Span?.description).toBe('05.array-hooks.onBeforeResponse');
expect(onBeforeResponse1Span?.description).toBe('05.array-hooks.onBeforeResponse');
// Verify handler has no index
expect(handlerSpan?.data).not.toHaveProperty('nuxt.middleware.hook.index');
});
test('should handle errors in onRequest hooks', async ({ request }) => {
const serverTxnEventPromise = waitForTransaction('nuxt-3', txnEvent => {
return txnEvent.transaction?.includes('GET /api/middleware-test') ?? false;
});
const errorEventPromise = waitForError('nuxt-3', errorEvent => {
return errorEvent?.exception?.values?.[0]?.value === 'OnRequest hook error';
});
// Make request with query param to trigger error in onRequest
const response = await request.get('/api/middleware-test?throwOnRequestError=true');
expect(response.status()).toBe(500);
const [serverTxnEvent, errorEvent] = await Promise.all([serverTxnEventPromise, errorEventPromise]);
// Find the onRequest span that should have error status
const onRequestSpan = serverTxnEvent.spans?.find(
span =>
span.op === 'middleware.nuxt' &&
span.data?.['nuxt.middleware.name'] === '04.hooks' &&
span.data?.['nuxt.middleware.hook.name'] === 'onRequest',
);
expect(onRequestSpan).toBeDefined();
expect(onRequestSpan?.status).toBe('internal_error');
expect(errorEvent.exception?.values?.[0]?.value).toBe('OnRequest hook error');
});
test('should handle errors in onBeforeResponse hooks', async ({ request }) => {
const serverTxnEventPromise = waitForTransaction('nuxt-3', txnEvent => {
return txnEvent.transaction?.includes('GET /api/middleware-test') ?? false;
});
const errorEventPromise = waitForError('nuxt-3', errorEvent => {
return errorEvent?.exception?.values?.[0]?.value === 'OnBeforeResponse hook error';
});
// Make request with query param to trigger error in onBeforeResponse
const response = await request.get('/api/middleware-test?throwOnBeforeResponseError=true');
expect(response.status()).toBe(500);
const [serverTxnEvent, errorEvent] = await Promise.all([serverTxnEventPromise, errorEventPromise]);
// Find the onBeforeResponse span that should have error status
const onBeforeResponseSpan = serverTxnEvent.spans?.find(
span =>
span.op === 'middleware.nuxt' &&
span.data?.['nuxt.middleware.name'] === '04.hooks' &&
span.data?.['nuxt.middleware.hook.name'] === 'onBeforeResponse',
);
expect(onBeforeResponseSpan).toBeDefined();
expect(onBeforeResponseSpan?.status).toBe('internal_error');
expect(errorEvent.exception?.values?.[0]?.value).toBe('OnBeforeResponse hook error');
});
test('should handle errors in array hooks with proper index attribution', async ({ request }) => {
const serverTxnEventPromise = waitForTransaction('nuxt-3', txnEvent => {
return txnEvent.transaction?.includes('GET /api/middleware-test') ?? false;
});
const errorEventPromise = waitForError('nuxt-3', errorEvent => {
return errorEvent?.exception?.values?.[0]?.value === 'OnRequest[1] hook error';
});
// Make request with query param to trigger error in second onRequest handler
const response = await request.get('/api/middleware-test?throwOnRequest1Error=true');
expect(response.status()).toBe(500);
const [serverTxnEvent, errorEvent] = await Promise.all([serverTxnEventPromise, errorEventPromise]);
// Find the second onRequest span that should have error status
const onRequest1Span = serverTxnEvent.spans?.find(
span =>
span.op === 'middleware.nuxt' &&
span.data?.['nuxt.middleware.name'] === '05.array-hooks' &&
span.data?.['nuxt.middleware.hook.name'] === 'onRequest' &&
span.data?.['nuxt.middleware.hook.index'] === 1,
);
expect(onRequest1Span).toBeDefined();
expect(onRequest1Span?.status).toBe('internal_error');
expect(errorEvent.exception?.values?.[0]?.value).toBe('OnRequest[1] hook error');
// Verify the first onRequest handler still executed successfully
const onRequest0Span = serverTxnEvent.spans?.find(
span =>
span.op === 'middleware.nuxt' &&
span.data?.['nuxt.middleware.name'] === '05.array-hooks' &&
span.data?.['nuxt.middleware.hook.name'] === 'onRequest' &&
span.data?.['nuxt.middleware.hook.index'] === 0,
);
expect(onRequest0Span).toBeDefined();
expect(onRequest0Span?.status).not.toBe('internal_error');
});
});