11import { expect , test } from '@playwright/test' ;
2- import { waitForError , waitForTransaction } from '@sentry-internal/test-utils' ;
2+ import { getSpanOp , waitForError , waitForStreamedSpan } from '@sentry-internal/test-utils' ;
3+
4+ const APP_NAME = 'nestjs-bullmq' ;
5+
6+ function waitForProcessSpan ( ) : Promise < unknown > {
7+ return waitForStreamedSpan ( APP_NAME , span => span . is_segment && getSpanOp ( span ) === 'queue.process' ) ;
8+ }
9+
10+ /**
11+ * The `/check-isolation` route reports the leaked breadcrumbs it can see as a span attribute,
12+ * because streamed spans carry no breadcrumbs of their own.
13+ */
14+ async function getLeakedBreadcrumbs ( baseURL : string ) : Promise < string [ ] > {
15+ const segmentSpanPromise = waitForStreamedSpan ( APP_NAME , span => {
16+ return span . is_segment && span . name === 'GET /check-isolation' ;
17+ } ) ;
18+
19+ await fetch ( `${ baseURL } /check-isolation` ) ;
20+
21+ const attribute = ( await segmentSpanPromise ) . attributes [ 'isolation_scope.leaked_breadcrumbs' ] ;
22+
23+ // A route that stopped reporting would make every `not.toContain` below pass for the wrong
24+ // reason, so require the attribute rather than defaulting it away.
25+ expect ( attribute ) . toEqual ( { type : 'array' , value : expect . any ( Array ) } ) ;
26+
27+ return attribute ! . value as string [ ] ;
28+ }
329
430test ( 'Sends exception to Sentry on error in @Processor process method' , async ( { baseURL } ) => {
5- const errorEventPromise = waitForError ( 'nestjs-bullmq' , event => {
31+ const errorEventPromise = waitForError ( APP_NAME , event => {
632 return (
733 ! event . type &&
834 event . exception ?. values ?. [ 0 ] ?. value === 'Test error from BullMQ processor' &&
@@ -22,43 +48,31 @@ test('Sends exception to Sentry on error in @Processor process method', async ({
2248 } ) ;
2349} ) ;
2450
25- test ( 'Creates a transaction for successful job processing' , async ( { baseURL } ) => {
26- const transactionPromise = waitForTransaction ( 'nestjs-bullmq' , transactionEvent => {
27- return transactionEvent . contexts ?. trace ?. op === 'queue.process' ;
51+ test ( 'Creates a segment span for successful job processing' , async ( { baseURL } ) => {
52+ const spanPromise = waitForStreamedSpan ( APP_NAME , span => {
53+ return span . is_segment && getSpanOp ( span ) === 'queue.process' ;
2854 } ) ;
2955
3056 // Enqueue a job that will succeed
3157 await fetch ( `${ baseURL } /enqueue/success` ) ;
3258
33- const transaction = await transactionPromise ;
59+ const span = await spanPromise ;
3460
35- expect ( transaction . transaction ) . toBe ( 'test-queue process' ) ;
36- expect ( transaction . contexts ?. trace ?. op ) . toBe ( 'queue.process' ) ;
37- expect ( transaction . contexts ?. trace ?. origin ) . toBe ( 'auto.queue.nestjs.bullmq' ) ;
61+ // Streamed messaging spans are named `<operation> <destination>`, the other way around from the
62+ // transaction name.
63+ expect ( span . name ) . toBe ( 'process test-queue' ) ;
64+ expect ( span . attributes [ 'sentry.origin' ] ) . toEqual ( { value : 'auto.queue.nestjs.bullmq' , type : 'string' } ) ;
3865} ) ;
3966
4067test ( 'BullMQ processor breadcrumbs do not leak into subsequent HTTP requests' , async ( { baseURL } ) => {
41- const processTransactionPromise = waitForTransaction ( 'nestjs-bullmq' , transactionEvent => {
42- return transactionEvent . contexts ?. trace ?. op === 'queue.process' ;
43- } ) ;
68+ const processSpanPromise = waitForProcessSpan ( ) ;
4469
4570 // Enqueue a job that adds a breadcrumb during processing
4671 await fetch ( `${ baseURL } /enqueue/breadcrumb-test` ) ;
4772
48- await processTransactionPromise ;
73+ await processSpanPromise ;
4974
50- const transactionPromise = waitForTransaction ( 'nestjs-bullmq' , transactionEvent => {
51- return transactionEvent . transaction === 'GET /check-isolation' ;
52- } ) ;
53-
54- await fetch ( `${ baseURL } /check-isolation` ) ;
55-
56- const transaction = await transactionPromise ;
57-
58- const leakedBreadcrumb = ( transaction . breadcrumbs || [ ] ) . find (
59- ( b : any ) => b . message === 'leaked-breadcrumb-from-bullmq-processor' ,
60- ) ;
61- expect ( leakedBreadcrumb ) . toBeUndefined ( ) ;
75+ expect ( await getLeakedBreadcrumbs ( baseURL ! ) ) . not . toContain ( 'leaked-breadcrumb-from-bullmq-processor' ) ;
6276} ) ;
6377
6478// TODO: @OnWorkerEvent ('completed') handlers run outside the isolation scope created by process().
@@ -67,84 +81,45 @@ test('BullMQ processor breadcrumbs do not leak into subsequent HTTP requests', a
6781test ( 'BullMQ @OnWorkerEvent completed lifecycle breadcrumbs currently leak into subsequent HTTP requests' , async ( {
6882 baseURL,
6983} ) => {
70- const processTransactionPromise = waitForTransaction ( 'nestjs-bullmq' , transactionEvent => {
71- return transactionEvent . contexts ?. trace ?. op === 'queue.process' ;
72- } ) ;
84+ const processSpanPromise = waitForProcessSpan ( ) ;
7385
7486 // Enqueue a job (the completed event fires right after the job is processed)
7587 await fetch ( `${ baseURL } /enqueue/lifecycle-breadcrumb-test` ) ;
7688
77- await processTransactionPromise ;
78-
79- const transactionPromise = waitForTransaction ( 'nestjs-bullmq' , transactionEvent => {
80- return transactionEvent . transaction === 'GET /check-isolation' ;
81- } ) ;
82-
83- await fetch ( `${ baseURL } /check-isolation` ) ;
89+ await processSpanPromise ;
8490
85- const transaction = await transactionPromise ;
86-
87- const leakedBreadcrumb = ( transaction . breadcrumbs || [ ] ) . find (
88- ( b : any ) => b . message === 'leaked-breadcrumb-from-lifecycle-event' ,
89- ) ;
90- // This SHOULD be toBeUndefined() once lifecycle event isolation is implemented.
91- expect ( leakedBreadcrumb ) . toBeDefined ( ) ;
91+ // This SHOULD be not.toContain() once lifecycle event isolation is implemented.
92+ expect ( await getLeakedBreadcrumbs ( baseURL ! ) ) . toContain ( 'leaked-breadcrumb-from-lifecycle-event' ) ;
9293} ) ;
9394
9495// TODO: @OnWorkerEvent ('active') handlers run outside the isolation scope created by process().
9596// Breadcrumbs set there leak into the default isolation scope and appear on subsequent HTTP requests.
9697test ( 'BullMQ @OnWorkerEvent active lifecycle breadcrumbs currently leak into subsequent HTTP requests' , async ( {
9798 baseURL,
9899} ) => {
99- const processTransactionPromise = waitForTransaction ( 'nestjs-bullmq' , transactionEvent => {
100- return transactionEvent . contexts ?. trace ?. op === 'queue.process' ;
101- } ) ;
100+ const processSpanPromise = waitForProcessSpan ( ) ;
102101
103102 await fetch ( `${ baseURL } /enqueue/lifecycle-active-breadcrumb-test` ) ;
104103
105- await processTransactionPromise ;
106-
107- const transactionPromise = waitForTransaction ( 'nestjs-bullmq' , transactionEvent => {
108- return transactionEvent . transaction === 'GET /check-isolation' ;
109- } ) ;
110-
111- await fetch ( `${ baseURL } /check-isolation` ) ;
112-
113- const transaction = await transactionPromise ;
104+ await processSpanPromise ;
114105
115- const leakedBreadcrumb = ( transaction . breadcrumbs || [ ] ) . find (
116- ( b : any ) => b . message === 'leaked-breadcrumb-from-active-event' ,
117- ) ;
118- // This SHOULD be toBeUndefined() once lifecycle event isolation is implemented.
119- expect ( leakedBreadcrumb ) . toBeDefined ( ) ;
106+ // This SHOULD be not.toContain() once lifecycle event isolation is implemented.
107+ expect ( await getLeakedBreadcrumbs ( baseURL ! ) ) . toContain ( 'leaked-breadcrumb-from-active-event' ) ;
120108} ) ;
121109
122110// TODO: @OnWorkerEvent ('failed') handlers run outside the isolation scope created by process().
123111// Breadcrumbs set there leak into the default isolation scope and appear on subsequent HTTP requests.
124112test ( 'BullMQ @OnWorkerEvent failed lifecycle breadcrumbs currently leak into subsequent HTTP requests' , async ( {
125113 baseURL,
126114} ) => {
127- const processTransactionPromise = waitForTransaction ( 'nestjs-bullmq' , transactionEvent => {
128- return transactionEvent . contexts ?. trace ?. op === 'queue.process' ;
129- } ) ;
115+ const processSpanPromise = waitForProcessSpan ( ) ;
130116
131117 await fetch ( `${ baseURL } /enqueue/lifecycle-failed-breadcrumb-test` ) ;
132118
133- await processTransactionPromise ;
134-
135- const transactionPromise = waitForTransaction ( 'nestjs-bullmq' , transactionEvent => {
136- return transactionEvent . transaction === 'GET /check-isolation' ;
137- } ) ;
119+ await processSpanPromise ;
138120
139- await fetch ( `${ baseURL } /check-isolation` ) ;
140-
141- const transaction = await transactionPromise ;
142-
143- const leakedBreadcrumb = ( transaction . breadcrumbs || [ ] ) . find (
144- ( b : any ) => b . message === 'leaked-breadcrumb-from-failed-event' ,
145- ) ;
146- // This SHOULD be toBeUndefined() once lifecycle event isolation is implemented.
147- expect ( leakedBreadcrumb ) . toBeDefined ( ) ;
121+ // This SHOULD be not.toContain() once lifecycle event isolation is implemented.
122+ expect ( await getLeakedBreadcrumbs ( baseURL ! ) ) . toContain ( 'leaked-breadcrumb-from-failed-event' ) ;
148123} ) ;
149124
150125// The 'progress' event does NOT leak breadcrumbs — unlike 'active', 'completed', and 'failed',
@@ -153,24 +128,11 @@ test('BullMQ @OnWorkerEvent failed lifecycle breadcrumbs currently leak into sub
153128test ( 'BullMQ @OnWorkerEvent progress lifecycle breadcrumbs do not leak into subsequent HTTP requests' , async ( {
154129 baseURL,
155130} ) => {
156- const processTransactionPromise = waitForTransaction ( 'nestjs-bullmq' , transactionEvent => {
157- return transactionEvent . contexts ?. trace ?. op === 'queue.process' ;
158- } ) ;
131+ const processSpanPromise = waitForProcessSpan ( ) ;
159132
160133 await fetch ( `${ baseURL } /enqueue/lifecycle-progress-breadcrumb-test` ) ;
161134
162- await processTransactionPromise ;
163-
164- const transactionPromise = waitForTransaction ( 'nestjs-bullmq' , transactionEvent => {
165- return transactionEvent . transaction === 'GET /check-isolation' ;
166- } ) ;
167-
168- await fetch ( `${ baseURL } /check-isolation` ) ;
169-
170- const transaction = await transactionPromise ;
135+ await processSpanPromise ;
171136
172- const leakedBreadcrumb = ( transaction . breadcrumbs || [ ] ) . find (
173- ( b : any ) => b . message === 'leaked-breadcrumb-from-progress-event' ,
174- ) ;
175- expect ( leakedBreadcrumb ) . toBeUndefined ( ) ;
137+ expect ( await getLeakedBreadcrumbs ( baseURL ! ) ) . not . toContain ( 'leaked-breadcrumb-from-progress-event' ) ;
176138} ) ;
0 commit comments