11import { AsyncLocalStorage } from 'node:async_hooks' ;
22import { ProxyTracer } from '@opentelemetry/api' ;
33import type { Event , EventHint , Log } from '@sentry/core' ;
4- import { getAsyncContextStrategy , getMainCarrier , Scope , SDK_VERSION } from '@sentry/core' ;
4+ import {
5+ createTransport ,
6+ getAsyncContextStrategy ,
7+ getClient ,
8+ getMainCarrier ,
9+ resolvedSyncPromise ,
10+ Scope ,
11+ SDK_VERSION ,
12+ startSpan ,
13+ } from '@sentry/core' ;
514import type { SentryTracerProvider } from '@sentry/opentelemetry' ;
615import { setOpenTelemetryContextAsyncContextStrategy } from '@sentry/opentelemetry' ;
716import * as SentryOpentelemetry from '@sentry/opentelemetry' ;
@@ -10,7 +19,7 @@ import * as os from 'os';
1019import { afterEach , beforeEach , describe , expect , it , test , vi } from 'vitest' ;
1120import { NodeClient } from '../../src' ;
1221import { getDefaultNodeClientOptions } from '../helpers/getDefaultNodeClientOptions' ;
13- import { cleanupOtel } from '../helpers/mockSdkInit' ;
22+ import { cleanupOtel , mockSdkInit } from '../helpers/mockSdkInit' ;
1423
1524describe ( 'NodeClient' , ( ) => {
1625 beforeEach ( ( ) => {
@@ -420,20 +429,40 @@ describe('NodeClient', () => {
420429 expect ( clearIntervalSpy ) . toHaveBeenCalledWith ( intervalResult ?. value ) ;
421430 } ) ;
422431
423- it ( 'removes log and metric exit listeners when closed' , async ( ) => {
432+ it ( 'removes log, metric and span exit listeners when closed' , async ( ) => {
424433 const originalListeners = process . listeners ( 'beforeExit' ) ;
425434 const client = new NodeClient ( getDefaultNodeClientOptions ( { sendClientReports : false } ) ) ;
426435
427436 const addedListeners = process . listeners ( 'beforeExit' ) . filter ( listener => ! originalListeners . includes ( listener ) ) ;
428437
429438 await client . close ( ) ;
430439
431- expect ( addedListeners ) . toHaveLength ( 2 ) ;
440+ expect ( addedListeners ) . toHaveLength ( 3 ) ;
432441 expect ( process . listeners ( 'beforeExit' ) ) . toEqual ( originalListeners ) ;
433442 } ) ;
434443 } ) ;
435444
436445 describe ( 'flush' , ( ) => {
446+ it ( 'sends buffered streamed spans before the process exits' , async ( ) => {
447+ const send = vi . fn ( ( ) => resolvedSyncPromise ( { } ) ) ;
448+ mockSdkInit ( {
449+ tracesSampleRate : 1 ,
450+ traceLifecycle : 'stream' ,
451+ transport : ( ) => createTransport ( { recordDroppedEvent : ( ) => undefined } , send ) ,
452+ } ) ;
453+
454+ startSpan ( { name : 'cron-job' } , ( ) => { } ) ;
455+ await new Promise ( resolve => setImmediate ( resolve ) ) ;
456+ expect ( send ) . not . toHaveBeenCalled ( ) ;
457+
458+ process . emit ( 'beforeExit' , 0 ) ;
459+
460+ expect ( send ) . toHaveBeenCalledTimes ( 1 ) ;
461+ expect ( send ) . toHaveBeenCalledWith ( expect . objectContaining ( { body : expect . stringContaining ( '"type":"span"' ) } ) ) ;
462+
463+ await getClient ( ) ?. close ( ) ;
464+ } ) ;
465+
437466 it ( 'flush returns immediately when nothing is processing' , async ( ) => {
438467 const options = getDefaultNodeClientOptions ( ) ;
439468 const client = new NodeClient ( options ) ;
0 commit comments