@@ -10,6 +10,7 @@ import (
1010 "context"
1111 "sync"
1212 "testing"
13+ "time"
1314
1415 "github.com/hyperledger-labs/fabric-smart-client/pkg/utils/errors"
1516 cdriver "github.com/hyperledger-labs/fabric-smart-client/platform/common/driver"
@@ -206,6 +207,83 @@ func TestListenerEvent_Process_Busy_TxCheckSucceeds(t *testing.T) {
206207 assert .Equal (t , 1 , mockListener .OnStatusCallCount ())
207208}
208209
210+ func TestListenerEvent_Process_RetriesOnTransientError (t * testing.T ) {
211+ ctx := t .Context ()
212+ txID := "tx123"
213+ namespace := "token-namespace"
214+ tokenRequestHash := []byte ("hash123" )
215+ key := "token-request-key"
216+
217+ mockQS := & mock.QueryService {}
218+ mockKT := & mock.KeyTranslator {}
219+ mockListener := & mock.Listener {}
220+
221+ var attempts int
222+ // Fail twice, succeed on third attempt
223+ mockQS .GetStateStub = func (_ string , _ string ) (* cdriver.VaultValue , error ) {
224+ attempts ++
225+ if attempts < 3 {
226+ return nil , errors .New ("transient peer error" )
227+ }
228+
229+ return & cdriver.VaultValue {Raw : tokenRequestHash }, nil
230+ }
231+ mockKT .CreateTokenRequestKeyReturns (key , nil )
232+
233+ event := & finality.ListenerEvent {
234+ QueryService : mockQS ,
235+ KeyTranslator : mockKT ,
236+ Listener : mockListener ,
237+ TxID : txID ,
238+ Status : fdriver .Valid ,
239+ Namespace : namespace ,
240+ MaxRetries : 3 ,
241+ RetryInterval : 10 * time .Millisecond ,
242+ }
243+
244+ err := event .Process (ctx )
245+ require .NoError (t , err )
246+
247+ // Should have retried and eventually called OnStatus
248+ assert .Equal (t , 3 , attempts )
249+ assert .Equal (t , 1 , mockListener .OnStatusCallCount ())
250+ assert .Equal (t , 0 , mockListener .OnErrorCallCount ())
251+ }
252+
253+ func TestListenerEvent_Process_CallsOnErrorAfterAllRetriesExhausted (t * testing.T ) {
254+ ctx := t .Context ()
255+ txID := "tx123"
256+ key := "token-request-key"
257+
258+ mockQS := & mock.QueryService {}
259+ mockKT := & mock.KeyTranslator {}
260+ mockListener := & mock.Listener {}
261+
262+ mockKT .CreateTokenRequestKeyReturns (key , nil )
263+ mockQS .GetStateReturns (nil , errors .New ("persistent peer error" ))
264+
265+ event := & finality.ListenerEvent {
266+ QueryService : mockQS ,
267+ KeyTranslator : mockKT ,
268+ Listener : mockListener ,
269+ TxID : txID ,
270+ Status : fdriver .Valid ,
271+ Namespace : "token-namespace" ,
272+ MaxRetries : 2 ,
273+ RetryInterval : 10 * time .Millisecond ,
274+ }
275+
276+ err := event .Process (ctx )
277+ require .NoError (t , err )
278+
279+ // All retries exhausted — OnStatus must NOT be called, OnError must be called once
280+ assert .Equal (t , 0 , mockListener .OnStatusCallCount ())
281+ assert .Equal (t , 1 , mockListener .OnErrorCallCount ())
282+ _ , callTxID , callErr := mockListener .OnErrorArgsForCall (0 )
283+ assert .Equal (t , txID , callTxID )
284+ assert .Contains (t , callErr .Error (), "persistent peer error" )
285+ }
286+
209287func TestListenerEvent_Process_CreateTokenRequestKeyError (t * testing.T ) {
210288 ctx := t .Context ()
211289 txID := "tx123"
@@ -224,12 +302,18 @@ func TestListenerEvent_Process_CreateTokenRequestKeyError(t *testing.T) {
224302 Status : fdriver .Valid ,
225303 StatusMessage : "" ,
226304 Namespace : "token-namespace" ,
305+ MaxRetries : 2 ,
306+ RetryInterval : 10 * time .Millisecond ,
227307 }
228308
229309 err := event .Process (ctx )
230- require .Error (t , err )
231- assert .Contains (t , err .Error (), "can't create for token request" )
232- assert .Contains (t , err .Error (), "key creation failed" )
310+ require .NoError (t , err )
311+ // All retries exhausted — listener must be notified via OnError
312+ assert .Equal (t , 0 , mockListener .OnStatusCallCount ())
313+ assert .Equal (t , 1 , mockListener .OnErrorCallCount ())
314+ _ , callTxID , callErr := mockListener .OnErrorArgsForCall (0 )
315+ assert .Equal (t , txID , callTxID )
316+ assert .Contains (t , callErr .Error (), "key creation failed" )
233317}
234318
235319func TestListenerEvent_Process_GetStateError (t * testing.T ) {
@@ -252,12 +336,18 @@ func TestListenerEvent_Process_GetStateError(t *testing.T) {
252336 Status : fdriver .Valid ,
253337 StatusMessage : "" ,
254338 Namespace : "token-namespace" ,
339+ MaxRetries : 2 ,
340+ RetryInterval : 10 * time .Millisecond ,
255341 }
256342
257343 err := event .Process (ctx )
258- require .Error (t , err )
259- assert .Contains (t , err .Error (), "can't get state for token request" )
260- assert .Contains (t , err .Error (), "state retrieval failed" )
344+ require .NoError (t , err )
345+ // All retries exhausted — listener must be notified via OnError
346+ assert .Equal (t , 0 , mockListener .OnStatusCallCount ())
347+ assert .Equal (t , 1 , mockListener .OnErrorCallCount ())
348+ _ , callTxID , callErr := mockListener .OnErrorArgsForCall (0 )
349+ assert .Equal (t , txID , callTxID )
350+ assert .Contains (t , callErr .Error (), "state retrieval failed" )
261351}
262352
263353func TestListenerEvent_String (t * testing.T ) {
0 commit comments