@@ -2,6 +2,7 @@ import type { LDLogger } from '@launchdarkly/js-client-sdk-common';
22
33import { createClient } from '../src' ;
44import NodeDataManager from '../src/NodeDataManager' ;
5+ import { NodeClient } from '../src/NodeClient' ;
56import { makeMockPlatform , mockFetch } from './NodeClient.mocks' ;
67
78// Replace NodePlatform's constructor with one that returns the mock platform. Lets us
@@ -307,3 +308,101 @@ it('setConnectionMode streaming -> offline tears down the EventSource', async ()
307308
308309 await client . close ( ) ;
309310} ) ;
311+
312+ it ( 'keeps event-sending state consistent with the mode under concurrent setConnectionMode' , async ( ) => {
313+ const fakePlatform = makeMockPlatform ( {
314+ requests : {
315+ fetch : mockFetch ( '' , 202 ) ,
316+ createEventSource : jest . fn ( ( ) => ( {
317+ addEventListener : jest . fn ( ) ,
318+ close : jest . fn ( ) ,
319+ onclose : jest . fn ( ) ,
320+ onerror : jest . fn ( ) ,
321+ onopen : jest . fn ( ) ,
322+ onretrying : jest . fn ( ) ,
323+ } ) ) ,
324+ getEventSourceCapabilities : ( ) => ( { readTimeout : true , headers : true , customMethod : true } ) ,
325+ } ,
326+ } ) ;
327+ NodePlatformMock . mockImplementationOnce ( ( ) => fakePlatform ) ;
328+
329+ // Use the implementation directly so we can assert on the internal event-sending flag,
330+ // which governs background (timer-driven) analytics delivery.
331+ const client = new NodeClient (
332+ 'client-side-id' ,
333+ { kind : 'user' , key : 'bob' } ,
334+ {
335+ initialConnectionMode : 'streaming' ,
336+ sendEvents : true ,
337+ diagnosticOptOut : true ,
338+ logger,
339+ } ,
340+ ) ;
341+
342+ await client . start ( { bootstrap : bootstrapData } ) ;
343+
344+ // Fire two transitions without awaiting between them. Without serialization the offline
345+ // transition could settle while event-sending is left enabled.
346+ const p1 = client . setConnectionMode ( 'streaming' ) ;
347+ const p2 = client . setConnectionMode ( 'offline' ) ;
348+ await Promise . all ( [ p1 , p2 ] ) ;
349+
350+ expect ( client . getConnectionMode ( ) ) . toBe ( 'offline' ) ;
351+ expect ( client . isOffline ( ) ) . toBe ( true ) ;
352+ // When offline, background analytics delivery must be disabled.
353+ // eslint-disable-next-line no-underscore-dangle
354+ expect ( ( client as any ) . _eventSendingEnabled ) . toBe ( false ) ;
355+
356+ await client . close ( ) ;
357+ } ) ;
358+
359+ it ( 'rejects identify called after close without waiting for the identify timeout' , async ( ) => {
360+ const fakePlatform = makeMockPlatform ( ) ;
361+ NodePlatformMock . mockImplementationOnce ( ( ) => fakePlatform ) ;
362+
363+ const client = createClient (
364+ 'client-side-id' ,
365+ { kind : 'user' , key : 'bob' } ,
366+ {
367+ initialConnectionMode : 'streaming' ,
368+ sendEvents : false ,
369+ diagnosticOptOut : true ,
370+ logger,
371+ } ,
372+ ) ;
373+
374+ await client . start ( { bootstrap : bootstrapData } ) ;
375+ await client . close ( ) ;
376+
377+ const start = Date . now ( ) ;
378+ const result = await client . identify ( { kind : 'user' , key : 'alice' } ) ;
379+ const elapsed = Date . now ( ) - start ;
380+
381+ expect ( result . status ) . toBe ( 'error' ) ;
382+ // Should fail fast, not sit until the 5s identify timeout.
383+ expect ( elapsed ) . toBeLessThan ( 1000 ) ;
384+ } ) ;
385+
386+ it ( 'does not read cached flags when bootstrap is provided' , async ( ) => {
387+ const fakePlatform = makeMockPlatform ( ) ;
388+ NodePlatformMock . mockImplementationOnce ( ( ) => fakePlatform ) ;
389+
390+ const client = createClient (
391+ 'client-side-id' ,
392+ { kind : 'user' , key : 'bob' } ,
393+ {
394+ initialConnectionMode : 'streaming' ,
395+ sendEvents : false ,
396+ diagnosticOptOut : true ,
397+ logger,
398+ } ,
399+ ) ;
400+
401+ await client . start ( { bootstrap : bootstrapData } ) ;
402+
403+ // Bootstrap and cache are mutually exclusive: cached flags must not be consulted (and so
404+ // cannot overwrite) the freshly applied bootstrap data.
405+ expect ( fakePlatform . storage ! . get ) . not . toHaveBeenCalled ( ) ;
406+
407+ await client . close ( ) ;
408+ } ) ;
0 commit comments