11import { BehaviorSubject , EmptyError , Subject , firstValueFrom , lastValueFrom , tap } from 'rxjs' ;
22import { InvalidStringError } from '@cardano-sdk/util' ;
3+ import { Logger } from 'ts-log' ;
34import { RetryBackoffConfig , retryBackoff } from 'backoff-rxjs' ;
45import { coldObservableProvider } from '../src' ;
56
@@ -8,11 +9,24 @@ jest.mock('backoff-rxjs', () => ({
89 retryBackoff : jest . fn ( ) . mockImplementation ( ( ...args ) => jest . requireActual ( 'backoff-rxjs' ) . retryBackoff ( ...args ) )
910} ) ) ;
1011
12+ const createMockLogger = ( ) : Logger => ( {
13+ debug : jest . fn ( ) ,
14+ error : jest . fn ( ) ,
15+ info : jest . fn ( ) ,
16+ trace : jest . fn ( ) ,
17+ warn : jest . fn ( )
18+ } ) ;
19+
1120describe ( 'coldObservableProvider' , ( ) => {
1221 it ( 'returns an observable that calls underlying provider on each subscription and uses retryBackoff' , async ( ) => {
1322 const underlyingProvider = jest . fn ( ) . mockResolvedValue ( true ) ;
1423 const backoffConfig : RetryBackoffConfig = { initialInterval : 1 } ;
15- const provider$ = coldObservableProvider ( { provider : underlyingProvider , retryBackoffConfig : backoffConfig } ) ;
24+ const logger = createMockLogger ( ) ;
25+ const provider$ = coldObservableProvider ( {
26+ logger,
27+ provider : underlyingProvider ,
28+ retryBackoffConfig : backoffConfig
29+ } ) ;
1630 expect ( await firstValueFrom ( provider$ ) ) . toBe ( true ) ;
1731 expect ( await firstValueFrom ( provider$ ) ) . toBe ( true ) ;
1832 expect ( underlyingProvider ) . toBeCalledTimes ( 2 ) ;
@@ -24,8 +38,10 @@ describe('coldObservableProvider', () => {
2438 const underlyingProvider = ( ) => firstValueFrom ( fakeProviderSubject ) ;
2539 const backoffConfig : RetryBackoffConfig = { initialInterval : 1 } ;
2640 const cancel$ = new BehaviorSubject < boolean > ( true ) ;
41+ const logger = createMockLogger ( ) ;
2742 const provider$ = coldObservableProvider ( {
2843 cancel$,
44+ logger,
2945 provider : underlyingProvider ,
3046 retryBackoffConfig : backoffConfig
3147 } ) ;
@@ -39,9 +55,10 @@ describe('coldObservableProvider', () => {
3955 } ) ;
4056
4157 it ( 'retries using retryBackoff, when underlying provider rejects' , async ( ) => {
58+ const logger = createMockLogger ( ) ;
4259 const underlyingProvider = jest . fn ( ) . mockRejectedValueOnce ( false ) . mockResolvedValue ( true ) ;
4360 const retryBackoffConfig : RetryBackoffConfig = { initialInterval : 1 } ;
44- const provider$ = coldObservableProvider ( { provider : underlyingProvider , retryBackoffConfig } ) ;
61+ const provider$ = coldObservableProvider ( { logger , provider : underlyingProvider , retryBackoffConfig } ) ;
4562 const resolvedValue = await firstValueFrom ( provider$ ) ;
4663 expect ( underlyingProvider ) . toBeCalledTimes ( 2 ) ;
4764 expect ( resolvedValue ) . toBeTruthy ( ) ;
@@ -50,6 +67,7 @@ describe('coldObservableProvider', () => {
5067 it ( 'does not retry, when underlying provider rejects with InvalidStringError' , async ( ) => {
5168 const testValue = { test : 'value' } ;
5269 const testError = new InvalidStringError ( 'Test invalid string error' ) ;
70+ const logger = createMockLogger ( ) ;
5371 const underlyingProvider = jest
5472 . fn ( )
5573 . mockRejectedValueOnce ( new Error ( 'Test error' ) )
@@ -59,6 +77,7 @@ describe('coldObservableProvider', () => {
5977 const onFatalError = jest . fn ( ) ;
6078 const retryBackoffConfig : RetryBackoffConfig = { initialInterval : 1 , shouldRetry : ( ) => true } ;
6179 const provider$ = coldObservableProvider ( {
80+ logger,
6281 onFatalError,
6382 provider : underlyingProvider ,
6483 retryBackoffConfig
@@ -67,6 +86,7 @@ describe('coldObservableProvider', () => {
6786 await expect ( firstValueFrom ( provider$ ) ) . rejects . toThrow ( EmptyError ) ;
6887 expect ( underlyingProvider ) . toBeCalledTimes ( 3 ) ;
6988 expect ( onFatalError ) . toBeCalledWith ( testError ) ;
89+ expect ( logger . error ) . toBeCalledWith ( testError ) ;
7090 } ) ;
7191
7292 it ( 'polls the provider until the pollUntil condition is satisfied' , async ( ) => {
@@ -77,8 +97,10 @@ describe('coldObservableProvider', () => {
7797 . mockResolvedValueOnce ( 'c' )
7898 . mockResolvedValue ( 'Never reached' ) ;
7999 const backoffConfig : RetryBackoffConfig = { initialInterval : 1 } ;
100+ const logger = createMockLogger ( ) ;
80101
81102 const provider$ = coldObservableProvider ( {
103+ logger,
82104 pollUntil : ( v ) => v === 'c' ,
83105 provider : underlyingProvider ,
84106 retryBackoffConfig : backoffConfig
@@ -89,4 +111,27 @@ describe('coldObservableProvider', () => {
89111 expect ( providerValues ) . toEqual ( [ 'a' , 'b' , 'c' ] ) ;
90112 expect ( underlyingProvider ) . toBeCalledTimes ( 3 ) ;
91113 } ) ;
114+
115+ it ( 'stops retrying after maxRetries attempts and handles the error in catchError' , async ( ) => {
116+ const testError = new Error ( 'Test error' ) ;
117+ const underlyingProvider = jest . fn ( ) . mockRejectedValue ( testError ) ;
118+ const maxRetries = 3 ;
119+ const retryBackoffConfig : RetryBackoffConfig = { initialInterval : 1 , maxRetries } ;
120+ const onFatalError = jest . fn ( ) ;
121+ const logger = createMockLogger ( ) ;
122+
123+ const provider$ = coldObservableProvider ( {
124+ logger,
125+ onFatalError,
126+ provider : underlyingProvider ,
127+ retryBackoffConfig
128+ } ) ;
129+
130+ await expect ( firstValueFrom ( provider$ ) ) . rejects . toThrow ( testError ) ;
131+
132+ expect ( underlyingProvider ) . toBeCalledTimes ( maxRetries + 1 ) ;
133+ expect ( onFatalError ) . toBeCalledWith ( expect . any ( Error ) ) ;
134+ expect ( logger . error ) . toHaveBeenCalled ( ) ;
135+ expect ( logger . error ) . toHaveBeenCalledWith ( testError ) ;
136+ } ) ;
92137} ) ;
0 commit comments