|
8 | 8 | CompositeDataSource,
|
9 | 9 | TransitionConditions,
|
10 | 10 | } from '../../../src/datasource/CompositeDataSource';
|
| 11 | +import { DataSourceErrorKind } from '../../../src/datasource/DataSourceErrorKinds'; |
| 12 | +import { LDFlagDeliveryFallbackError } from '../../../src/datasource/errors'; |
11 | 13 |
|
12 | 14 | function makeDataSourceFactory(internal: DataSource): LDDataSourceFactory {
|
13 | 15 | return () => internal;
|
@@ -75,6 +77,7 @@ it('handles initializer getting basis, switching to synchronizer', async () => {
|
75 | 77 | const underTest = new CompositeDataSource(
|
76 | 78 | [makeDataSourceFactory(mockInitializer1)],
|
77 | 79 | [makeDataSourceFactory(mockSynchronizer1)],
|
| 80 | + [], |
78 | 81 | undefined,
|
79 | 82 | makeTestTransitionConditions(),
|
80 | 83 | makeZeroBackoff(),
|
@@ -169,6 +172,7 @@ it('handles initializer getting basis, switches to synchronizer 1, falls back to
|
169 | 172 | const underTest = new CompositeDataSource(
|
170 | 173 | [makeDataSourceFactory(mockInitializer1)],
|
171 | 174 | [makeDataSourceFactory(mockSynchronizer1), makeDataSourceFactory(mockSynchronizer2)],
|
| 175 | + [], |
172 | 176 | undefined,
|
173 | 177 | makeTestTransitionConditions(),
|
174 | 178 | makeZeroBackoff(),
|
@@ -270,6 +274,7 @@ it('removes synchronizer that reports unrecoverable error and loops on remaining
|
270 | 274 | const underTest = new CompositeDataSource(
|
271 | 275 | [makeDataSourceFactory(mockInitializer1)],
|
272 | 276 | [makeDataSourceFactory(mockSynchronizer1), makeDataSourceFactory(mockSynchronizer2)],
|
| 277 | + [], |
273 | 278 | undefined,
|
274 | 279 | makeTestTransitionConditions(),
|
275 | 280 | makeZeroBackoff(),
|
@@ -306,6 +311,117 @@ it('removes synchronizer that reports unrecoverable error and loops on remaining
|
306 | 311 | expect(statusCallback).toHaveBeenNthCalledWith(10, DataSourceState.Valid, undefined); // sync1 valid
|
307 | 312 | });
|
308 | 313 |
|
| 314 | +it('falls back to FDv1 synchronizers when FDv1 fallback error is reported', async () => { |
| 315 | + const mockInitializer1: DataSource = { |
| 316 | + start: jest |
| 317 | + .fn() |
| 318 | + .mockImplementation( |
| 319 | + ( |
| 320 | + _dataCallback: (basis: boolean, data: any) => void, |
| 321 | + _statusCallback: (status: DataSourceState, err?: any) => void, |
| 322 | + ) => { |
| 323 | + _statusCallback(DataSourceState.Initializing); |
| 324 | + _statusCallback(DataSourceState.Valid); |
| 325 | + _dataCallback(true, { key: 'init1' }); |
| 326 | + _statusCallback(DataSourceState.Closed); |
| 327 | + }, |
| 328 | + ), |
| 329 | + stop: jest.fn(), |
| 330 | + }; |
| 331 | + |
| 332 | + const mockSynchronizer1 = { |
| 333 | + start: jest |
| 334 | + .fn() |
| 335 | + .mockImplementation( |
| 336 | + ( |
| 337 | + _dataCallback: (basis: boolean, data: any) => void, |
| 338 | + _statusCallback: (status: DataSourceState, err?: any) => void, |
| 339 | + ) => { |
| 340 | + _statusCallback(DataSourceState.Initializing); |
| 341 | + _statusCallback( |
| 342 | + DataSourceState.Closed, |
| 343 | + new LDFlagDeliveryFallbackError( |
| 344 | + DataSourceErrorKind.ErrorResponse, |
| 345 | + `Response header indicates to fallback to FDv1`, |
| 346 | + 403, |
| 347 | + ), |
| 348 | + ); |
| 349 | + }, |
| 350 | + ), |
| 351 | + stop: jest.fn(), |
| 352 | + }; |
| 353 | + |
| 354 | + const mockSynchronizer2 = { |
| 355 | + start: jest |
| 356 | + .fn() |
| 357 | + .mockImplementation( |
| 358 | + ( |
| 359 | + _dataCallback: (basis: boolean, data: any) => void, |
| 360 | + _statusCallback: (status: DataSourceState, err?: any) => void, |
| 361 | + ) => { |
| 362 | + _statusCallback(DataSourceState.Initializing); |
| 363 | + _statusCallback(DataSourceState.Closed, { |
| 364 | + name: 'Error', |
| 365 | + message: 'I should NOT be called due to FDv1 Fallback', |
| 366 | + }); |
| 367 | + }, |
| 368 | + ), |
| 369 | + stop: jest.fn(), |
| 370 | + }; |
| 371 | + |
| 372 | + const mockFDv1Data = { key: 'FDv1Data' }; |
| 373 | + const mockFDv1Synchronizer = { |
| 374 | + start: jest |
| 375 | + .fn() |
| 376 | + .mockImplementation( |
| 377 | + ( |
| 378 | + _dataCallback: (basis: boolean, data: any) => void, |
| 379 | + _statusCallback: (status: DataSourceState, err?: any) => void, |
| 380 | + ) => { |
| 381 | + _statusCallback(DataSourceState.Initializing); |
| 382 | + _statusCallback(DataSourceState.Valid, null); // this should lead to recovery |
| 383 | + _dataCallback(false, mockFDv1Data); |
| 384 | + }, |
| 385 | + ), |
| 386 | + stop: jest.fn(), |
| 387 | + }; |
| 388 | + |
| 389 | + const underTest = new CompositeDataSource( |
| 390 | + [makeDataSourceFactory(mockInitializer1)], |
| 391 | + [makeDataSourceFactory(mockSynchronizer1), makeDataSourceFactory(mockSynchronizer2)], |
| 392 | + [makeDataSourceFactory(mockFDv1Synchronizer)], |
| 393 | + undefined, |
| 394 | + makeTestTransitionConditions(), |
| 395 | + makeZeroBackoff(), |
| 396 | + ); |
| 397 | + |
| 398 | + let dataCallback; |
| 399 | + const statusCallback = jest.fn(); |
| 400 | + await new Promise<void>((resolve) => { |
| 401 | + dataCallback = jest.fn((_: boolean, data: any) => { |
| 402 | + if (data === mockFDv1Data) { |
| 403 | + resolve(); |
| 404 | + } |
| 405 | + }); |
| 406 | + |
| 407 | + underTest.start(dataCallback, statusCallback); |
| 408 | + }); |
| 409 | + |
| 410 | + expect(mockInitializer1.start).toHaveBeenCalledTimes(1); |
| 411 | + expect(mockSynchronizer1.start).toHaveBeenCalledTimes(1); |
| 412 | + expect(mockSynchronizer2.start).toHaveBeenCalledTimes(0); // this synchronizer should not be called because we fall back to FDv1 synchronizers instead |
| 413 | + expect(mockFDv1Synchronizer.start).toHaveBeenCalledTimes(1); |
| 414 | + expect(dataCallback).toHaveBeenCalledTimes(2); |
| 415 | + expect(dataCallback).toHaveBeenNthCalledWith(1, true, { key: 'init1' }); |
| 416 | + expect(dataCallback).toHaveBeenNthCalledWith(2, false, { key: 'FDv1Data' }); |
| 417 | + expect(statusCallback).toHaveBeenCalledTimes(5); |
| 418 | + expect(statusCallback).toHaveBeenNthCalledWith(1, DataSourceState.Initializing, undefined); |
| 419 | + expect(statusCallback).toHaveBeenNthCalledWith(2, DataSourceState.Valid, undefined); // initializer got data |
| 420 | + expect(statusCallback).toHaveBeenNthCalledWith(3, DataSourceState.Interrupted, undefined); // initializer closed |
| 421 | + expect(statusCallback).toHaveBeenNthCalledWith(4, DataSourceState.Interrupted, expect.anything()); // sync1 fdv1 fallback error |
| 422 | + expect(statusCallback).toHaveBeenNthCalledWith(5, DataSourceState.Valid, undefined); // sync1 valid |
| 423 | +}); |
| 424 | + |
309 | 425 | it('reports error when all initializers fail', async () => {
|
310 | 426 | const mockInitializer1Error = {
|
311 | 427 | name: 'Error',
|
@@ -348,6 +464,7 @@ it('reports error when all initializers fail', async () => {
|
348 | 464 | const underTest = new CompositeDataSource(
|
349 | 465 | [makeDataSourceFactory(mockInitializer1), makeDataSourceFactory(mockInitializer2)],
|
350 | 466 | [], // no synchronizers for this test
|
| 467 | + [], |
351 | 468 | undefined,
|
352 | 469 | makeTestTransitionConditions(),
|
353 | 470 | makeZeroBackoff(),
|
@@ -445,6 +562,7 @@ it('it reports DataSourceState Closed when all synchronizers report Closed with
|
445 | 562 | const underTest = new CompositeDataSource(
|
446 | 563 | [makeDataSourceFactory(mockInitializer1)],
|
447 | 564 | [makeDataSourceFactory(mockSynchronizer1), makeDataSourceFactory(mockSynchronizer2)],
|
| 565 | + [], |
448 | 566 | undefined,
|
449 | 567 | makeTestTransitionConditions(),
|
450 | 568 | makeZeroBackoff(),
|
@@ -508,6 +626,7 @@ it('can be stopped when in thrashing synchronizer fallback loop', async () => {
|
508 | 626 | const underTest = new CompositeDataSource(
|
509 | 627 | [makeDataSourceFactory(mockInitializer1)],
|
510 | 628 | [makeDataSourceFactory(mockSynchronizer1)], // will continuously fallback onto itself
|
| 629 | + [], |
511 | 630 | undefined,
|
512 | 631 | makeTestTransitionConditions(),
|
513 | 632 | makeZeroBackoff(),
|
@@ -579,6 +698,7 @@ it('can be stopped and restarted', async () => {
|
579 | 698 | const underTest = new CompositeDataSource(
|
580 | 699 | [makeDataSourceFactory(mockInitializer1)],
|
581 | 700 | [makeDataSourceFactory(mockSynchronizer1)],
|
| 701 | + [], |
582 | 702 | undefined,
|
583 | 703 | makeTestTransitionConditions(),
|
584 | 704 | makeZeroBackoff(),
|
@@ -625,6 +745,7 @@ it('can be stopped and restarted', async () => {
|
625 | 745 |
|
626 | 746 | it('is well behaved with no initializers and no synchronizers configured', async () => {
|
627 | 747 | const underTest = new CompositeDataSource(
|
| 748 | + [], |
628 | 749 | [],
|
629 | 750 | [],
|
630 | 751 | undefined,
|
@@ -671,6 +792,7 @@ it('is well behaved with no initializer and synchronizer configured', async () =
|
671 | 792 | const underTest = new CompositeDataSource(
|
672 | 793 | [],
|
673 | 794 | [makeDataSourceFactory(mockSynchronizer1)],
|
| 795 | + [], |
674 | 796 | undefined,
|
675 | 797 | makeTestTransitionConditions(),
|
676 | 798 | makeZeroBackoff(),
|
@@ -712,6 +834,7 @@ it('is well behaved with an initializer and no synchronizers configured', async
|
712 | 834 | const underTest = new CompositeDataSource(
|
713 | 835 | [makeDataSourceFactory(mockInitializer1)],
|
714 | 836 | [],
|
| 837 | + [], |
715 | 838 | undefined,
|
716 | 839 | makeTestTransitionConditions(),
|
717 | 840 | makeZeroBackoff(),
|
@@ -774,6 +897,7 @@ it('consumes cancellation tokens correctly', async () => {
|
774 | 897 | const underTest = new CompositeDataSource(
|
775 | 898 | [makeDataSourceFactory(mockInitializer1)],
|
776 | 899 | [makeDataSourceFactory(mockSynchronizer1)],
|
| 900 | + [], |
777 | 901 | undefined,
|
778 | 902 | {
|
779 | 903 | // pass in transition condition so that it will thrash, generating cancellation tokens repeatedly
|
|
0 commit comments