2323import dev .openfeature .flagd .grpc .sync .Sync .GetMetadataResponse ;
2424import dev .openfeature .flagd .grpc .sync .Sync .SyncFlagsRequest ;
2525import dev .openfeature .flagd .grpc .sync .Sync .SyncFlagsResponse ;
26+ import io .grpc .StatusRuntimeException ;
2627import io .grpc .stub .StreamObserver ;
2728import java .util .concurrent .BlockingQueue ;
2829import java .util .concurrent .CountDownLatch ;
@@ -35,11 +36,12 @@ class SyncStreamQueueSourceTest {
3536 private ChannelConnector mockConnector ;
3637 private FlagSyncServiceBlockingStub blockingStub ;
3738 private FlagSyncServiceStub stub ;
39+ private FlagSyncServiceStub errorStub ;
3840 private StreamObserver <SyncFlagsResponse > observer ;
3941 private CountDownLatch latch ; // used to wait for observer to be initialized
4042
4143 @ BeforeEach
42- public void init () throws Exception {
44+ public void setup () throws Exception {
4345 blockingStub = mock (FlagSyncServiceBlockingStub .class );
4446 when (blockingStub .withDeadlineAfter (anyLong (), any ())).thenReturn (blockingStub );
4547 when (blockingStub .getMetadata (any ())).thenReturn (GetMetadataResponse .getDefaultInstance ());
@@ -57,22 +59,54 @@ public void init() throws Exception {
5759 })
5860 .when (stub )
5961 .syncFlags (any (SyncFlagsRequest .class ), any (StreamObserver .class )); // Mock the initialize
60- // method
62+
63+ errorStub = mock (FlagSyncServiceStub .class );
64+ when (errorStub .withDeadlineAfter (anyLong (), any ())).thenReturn (errorStub );
65+ doAnswer ((Answer <Void >) invocation -> {
66+ Object [] args = invocation .getArguments ();
67+ observer = (StreamObserver <SyncFlagsResponse >) args [1 ];
68+ latch .countDown ();
69+ throw new StatusRuntimeException (io .grpc .Status .NOT_FOUND );
70+ })
71+ .when (errorStub )
72+ .syncFlags (any (SyncFlagsRequest .class ), any (StreamObserver .class )); // Mock the initialize
73+ }
74+
75+ @ Test
76+ void initError_DoesNotBusyWait () throws Exception {
77+ // make sure we do not spin in a busy loop on errors
78+
79+ SyncStreamQueueSource queueSource =
80+ new SyncStreamQueueSource (FlagdOptions .builder ().build (), mockConnector , errorStub , blockingStub );
81+ latch = new CountDownLatch (1 );
82+ queueSource .init ();
83+ latch .await ();
84+
85+ BlockingQueue <QueuePayload > streamQueue = queueSource .getStreamQueue ();
86+ QueuePayload payload = streamQueue .poll (1000 , TimeUnit .MILLISECONDS );
87+ assertNotNull (payload );
88+ assertEquals (QueuePayloadType .ERROR , payload .getType ());
89+ Thread .sleep (SyncStreamQueueSource .RETRY_LOOP_DELAY_MS + 1000 ); // wait for retries
90+
91+ // should have retried the stream (2 calls); initial + 1 retry
92+ // it's very important that the retry count is low, to confirm no busy-loop
93+ verify (errorStub , times (2 )).syncFlags (any (), any ());
6194 }
6295
96+
6397 @ Test
6498 void onNextEnqueuesDataPayload () throws Exception {
65- SyncStreamQueueSource connector =
99+ SyncStreamQueueSource queueSource =
66100 new SyncStreamQueueSource (FlagdOptions .builder ().build (), mockConnector , stub , blockingStub );
67101 latch = new CountDownLatch (1 );
68- connector .init ();
102+ queueSource .init ();
69103 latch .await ();
70104
71105 // fire onNext (data) event
72106 observer .onNext (SyncFlagsResponse .newBuilder ().build ());
73107
74108 // should enqueue data payload
75- BlockingQueue <QueuePayload > streamQueue = connector .getStreamQueue ();
109+ BlockingQueue <QueuePayload > streamQueue = queueSource .getStreamQueue ();
76110 QueuePayload payload = streamQueue .poll (1000 , TimeUnit .MILLISECONDS );
77111 assertNotNull (payload );
78112 assertNotNull (payload .getSyncContext ());
@@ -81,20 +115,21 @@ void onNextEnqueuesDataPayload() throws Exception {
81115 verify (stub , times (1 )).syncFlags (any (), any ());
82116 }
83117
118+
84119 @ Test
85120 void onNextEnqueuesDataPayloadMetadataDisabled () throws Exception {
86121 // disable GetMetadata call
87- SyncStreamQueueSource connector = new SyncStreamQueueSource (
122+ SyncStreamQueueSource queueSource = new SyncStreamQueueSource (
88123 FlagdOptions .builder ().syncMetadataDisabled (true ).build (), mockConnector , stub , blockingStub );
89124 latch = new CountDownLatch (1 );
90- connector .init ();
125+ queueSource .init ();
91126 latch .await ();
92127
93128 // fire onNext (data) event
94129 observer .onNext (SyncFlagsResponse .newBuilder ().build ());
95130
96131 // should enqueue data payload
97- BlockingQueue <QueuePayload > streamQueue = connector .getStreamQueue ();
132+ BlockingQueue <QueuePayload > streamQueue = queueSource .getStreamQueue ();
98133 QueuePayload payload = streamQueue .poll (1000 , TimeUnit .MILLISECONDS );
99134 assertNotNull (payload );
100135 assertNull (payload .getSyncContext ());
@@ -108,10 +143,10 @@ void onNextEnqueuesDataPayloadMetadataDisabled() throws Exception {
108143 @ Test
109144 void onNextEnqueuesDataPayloadWithSyncContext () throws Exception {
110145 // disable GetMetadata call
111- SyncStreamQueueSource connector =
146+ SyncStreamQueueSource queueSource =
112147 new SyncStreamQueueSource (FlagdOptions .builder ().build (), mockConnector , stub , blockingStub );
113148 latch = new CountDownLatch (1 );
114- connector .init ();
149+ queueSource .init ();
115150 latch .await ();
116151
117152 // fire onNext (data) event
@@ -120,7 +155,7 @@ void onNextEnqueuesDataPayloadWithSyncContext() throws Exception {
120155 SyncFlagsResponse .newBuilder ().setSyncContext (syncContext ).build ());
121156
122157 // should enqueue data payload
123- BlockingQueue <QueuePayload > streamQueue = connector .getStreamQueue ();
158+ BlockingQueue <QueuePayload > streamQueue = queueSource .getStreamQueue ();
124159 QueuePayload payload = streamQueue .poll (1000 , TimeUnit .MILLISECONDS );
125160 assertNotNull (payload );
126161 assertEquals (syncContext , payload .getSyncContext ());
@@ -131,18 +166,18 @@ void onNextEnqueuesDataPayloadWithSyncContext() throws Exception {
131166
132167 @ Test
133168 void onErrorEnqueuesDataPayload () throws Exception {
134- SyncStreamQueueSource connector =
169+ SyncStreamQueueSource queueSource =
135170 new SyncStreamQueueSource (FlagdOptions .builder ().build (), mockConnector , stub , blockingStub );
136171 latch = new CountDownLatch (1 );
137- connector .init ();
172+ queueSource .init ();
138173 latch .await ();
139174
140175 // fire onError event and reset latch
141176 latch = new CountDownLatch (1 );
142177 observer .onError (new Exception ("fake exception" ));
143178
144179 // should enqueue error payload
145- BlockingQueue <QueuePayload > streamQueue = connector .getStreamQueue ();
180+ BlockingQueue <QueuePayload > streamQueue = queueSource .getStreamQueue ();
146181 QueuePayload payload = streamQueue .poll (1000 , TimeUnit .MILLISECONDS );
147182 assertNotNull (payload );
148183 assertEquals (QueuePayloadType .ERROR , payload .getType ());
@@ -153,18 +188,18 @@ void onErrorEnqueuesDataPayload() throws Exception {
153188
154189 @ Test
155190 void onCompletedEnqueuesDataPayload () throws Exception {
156- SyncStreamQueueSource connector =
191+ SyncStreamQueueSource queueSource =
157192 new SyncStreamQueueSource (FlagdOptions .builder ().build (), mockConnector , stub , blockingStub );
158193 latch = new CountDownLatch (1 );
159- connector .init ();
194+ queueSource .init ();
160195 latch .await ();
161196
162197 // fire onCompleted event (graceful stream end) and reset latch
163198 latch = new CountDownLatch (1 );
164199 observer .onCompleted ();
165200
166201 // should enqueue error payload
167- BlockingQueue <QueuePayload > streamQueue = connector .getStreamQueue ();
202+ BlockingQueue <QueuePayload > streamQueue = queueSource .getStreamQueue ();
168203 assertTrue (streamQueue .isEmpty ());
169204 // should have restarted the stream (2 calls)
170205 latch .await ();
0 commit comments