22
22
import org .hiero .block .api .SubscribeStreamRequest ;
23
23
import org .hiero .block .api .SubscribeStreamResponse ;
24
24
import org .hiero .block .internal .SubscribeStreamResponseUnparsed ;
25
+ import org .hiero .block .internal .SubscribeStreamResponseUnparsed .ResponseOneOfType ;
25
26
import org .hiero .block .node .app .fixtures .plugintest .SimpleInMemoryHistoricalBlockFacility ;
26
27
import org .hiero .block .node .app .fixtures .plugintest .TestBlockMessagingFacility ;
27
28
import org .hiero .block .node .spi .BlockNodeContext ;
@@ -78,7 +79,6 @@ void setUp() {
78
79
@ Nested
79
80
@ DisplayName ("Streaming Functionality Tests" )
80
81
class StreamingTests {
81
-
82
82
/**
83
83
* Tests the complete flow of streaming both historical and live blocks.
84
84
* Verifies that the session can handle:
@@ -107,7 +107,6 @@ void shouldStreamHistoricalAndLiveBlocksSuccessfully()
107
107
sessionReadyLatch .await ();
108
108
// Phase 1: Historical Block Streaming
109
109
final int expectedResponseCount = END_BLOCK - START_BLOCK ;
110
-
111
110
// Phase 2: Live Block Streaming
112
111
BlockItems [] liveBatches = createNumberOfSimpleBlockBatches (MAX_AVAILABLE_BLOCK , END_BLOCK + 1 );
113
112
for (BlockItems next : liveBatches ) {
@@ -116,12 +115,8 @@ void shouldStreamHistoricalAndLiveBlocksSuccessfully()
116
115
// Wait for everything to complete.
117
116
// Note, don't try to wait before this; there are no execution guarantees until
118
117
// `get` is called; before that the thread may not run or may be parked indefinitely.
119
- sessionFuture .get (
120
- 1L ,
121
- TimeUnit .SECONDS ); // The timeout doesn't work, for some reason, but it's here because we don't
122
- // have a better alternative.
118
+ sessionFuture .get ();
123
119
}
124
-
125
120
// Verify final pipeline state
126
121
assertThat (responsePipeline .getReceivedResponses ()).hasSize (21 );
127
122
assertThat (responsePipeline .getCompletionCount ()).isEqualTo (1 );
@@ -148,14 +143,90 @@ void shouldStreamHistoricalBlocksSuccessfully() {
148
143
setupHistoricalBlockProvider (MIN_AVAILABLE_BLOCK , MAX_AVAILABLE_BLOCK );
149
144
// Execute session
150
145
session .call ();
151
-
152
146
// Verify pipeline interactions
153
147
assertThat (responsePipeline .getReceivedResponses ()).hasSize (11 );
154
148
assertThat (responsePipeline .getCompletionCount ()).isEqualTo (1 );
155
149
assertThat (responsePipeline .getPipelineErrors ()).isEmpty ();
156
150
}
157
151
}
158
152
153
+ /**
154
+ * Tests related to streams ending for the BlockStreamSubscriberSession.
155
+ */
156
+ @ Nested
157
+ @ DisplayName ("Stream End Tests" )
158
+ class StreamEndTests {
159
+ /**
160
+ * Tests the complete flow of streaming both historical and live blocks.
161
+ * Verifies that the session can handle:
162
+ * 1. Historical block streaming
163
+ * 2. Transition to live block streaming
164
+ * 3. Proper pipeline interactions
165
+ */
166
+ @ Test
167
+ @ DisplayName ("Should complete a combined stream when the client exits" )
168
+ void shouldCompleteWhenStreamClosed () throws InterruptedException , ExecutionException , TimeoutException {
169
+ // Setup test parameters
170
+ final int MIN_AVAILABLE_BLOCK = 0 ;
171
+ final int MAX_AVAILABLE_BLOCK = 10 ;
172
+ final int START_BLOCK = 0 ;
173
+ final int END_BLOCK = 19 ;
174
+ final ResponsePipeline disconnectedPipeline = new ResponsePipeline (true );
175
+
176
+ // Initialize session
177
+ final SubscribeStreamRequest subscribeStreamRequest = createRequest (START_BLOCK , END_BLOCK );
178
+ setupHistoricalBlockProvider (MIN_AVAILABLE_BLOCK , MAX_AVAILABLE_BLOCK + 1 );
179
+ session = new BlockStreamSubscriberSession (
180
+ CLIENT_ID , subscribeStreamRequest , disconnectedPipeline , context , sessionReadyLatch );
181
+ try (final ExecutorService sessionExecutor = Executors .newVirtualThreadPerTaskExecutor ()) {
182
+ Future <BlockStreamSubscriberSession > sessionFuture = sessionExecutor .submit (session );
183
+ sessionReadyLatch .await ();
184
+ // Phase 1: Historical Block Streaming
185
+ final int expectedResponseCount = END_BLOCK - START_BLOCK ;
186
+ // Phase 2: Live Block Streaming
187
+ BlockItems [] liveBatches = createNumberOfSimpleBlockBatches (MAX_AVAILABLE_BLOCK , END_BLOCK + 1 );
188
+ for (BlockItems next : liveBatches ) {
189
+ blockMessagingFacility .sendBlockItems (next );
190
+ }
191
+ // Wait for everything to complete.
192
+ // Note, don't try to wait before this; there are no execution guarantees until
193
+ // `get` is called; before that the thread may not run or may be parked indefinitely.
194
+ sessionFuture .get ();
195
+ }
196
+ // Verify final pipeline state
197
+ assertThat (disconnectedPipeline .getReceivedResponses ()).hasSize (0 );
198
+ assertThat (disconnectedPipeline .getCompletionCount ()).isEqualTo (1 );
199
+ assertThat (disconnectedPipeline .getPipelineErrors ()).isEmpty ();
200
+ }
201
+
202
+ /**
203
+ * Tests the historical block streaming functionality in isolation.
204
+ * Verifies that the session can properly stream blocks from the historical provider.
205
+ */
206
+ @ Test
207
+ @ DisplayName ("Should complete a stream of historical blocks when the client exits" )
208
+ void shouldCompleteWhenStreamFails () {
209
+ // Setup test parameters
210
+ final int MIN_AVAILABLE_BLOCK = 0 ;
211
+ final int MAX_AVAILABLE_BLOCK = 20 ;
212
+ final long START_BLOCK = 1L ;
213
+ final long END_BLOCK = 10L ;
214
+ final ResponsePipeline disconnectedPipeline = new ResponsePipeline (true );
215
+
216
+ // Initialize session
217
+ final SubscribeStreamRequest subscribeStreamRequest = createRequest (START_BLOCK , END_BLOCK );
218
+ session = new BlockStreamSubscriberSession (
219
+ CLIENT_ID , subscribeStreamRequest , disconnectedPipeline , context , sessionReadyLatch );
220
+ setupHistoricalBlockProvider (MIN_AVAILABLE_BLOCK , MAX_AVAILABLE_BLOCK );
221
+ // Execute session
222
+ session .call ();
223
+ // Verify pipeline interactions
224
+ assertThat (disconnectedPipeline .getReceivedResponses ()).hasSize (0 );
225
+ assertThat (disconnectedPipeline .getCompletionCount ()).isEqualTo (1 );
226
+ assertThat (disconnectedPipeline .getPipelineErrors ()).isEmpty ();
227
+ }
228
+ }
229
+
159
230
/**
160
231
* Sets up the historical block provider with the specified block range.
161
232
*
@@ -195,8 +266,17 @@ private static class ResponsePipeline implements Pipeline<SubscribeStreamRespons
195
266
private final List <SubscribeStreamResponse > receivedResponses = new ArrayList <>();
196
267
197
268
private final List <Throwable > pipelineErrors = new ArrayList <>();
269
+ private final boolean throwOnNext ;
198
270
private int completionCount = 0 ;
199
271
272
+ public ResponsePipeline () {
273
+ this .throwOnNext = false ;
274
+ }
275
+
276
+ public ResponsePipeline (final boolean throwOnNext ) {
277
+ this .throwOnNext = throwOnNext ;
278
+ }
279
+
200
280
public List <SubscribeStreamResponse > getReceivedResponses () {
201
281
return receivedResponses ;
202
282
}
@@ -217,6 +297,9 @@ public void onSubscribe(Flow.Subscription subscription) {}
217
297
218
298
@ Override
219
299
public void onNext (SubscribeStreamResponseUnparsed item ) {
300
+ if (throwOnNext && item .response ().kind () != ResponseOneOfType .STATUS ) {
301
+ throw new RuntimeException ("Simulated \" closed\" stream." );
302
+ }
220
303
try {
221
304
var binary = SubscribeStreamResponseUnparsed .PROTOBUF .toBytes (item );
222
305
var response = SubscribeStreamResponse .PROTOBUF .parse (binary );
0 commit comments