@@ -42,7 +42,32 @@ struct HTTPRequestConcludingAsyncReaderTests {
4242
4343 _ = try await requestReader. consumeAndConclude { bodyReader in
4444 var bodyReader = bodyReader
45- try await bodyReader. read ( maximumCount: nil ) { element in ( ) }
45+ try await bodyReader. read ( maximumCount: nil ) { _ in }
46+ }
47+ }
48+ }
49+
50+ @Test ( " Stream cannot be finished before writing request end part " )
51+ @available ( macOS 26 . 0 , iOS 26 . 0 , watchOS 26 . 0 , tvOS 26 . 0 , visionOS 26 . 0 , * )
52+ func testNotWritingRequestEndPartFatalError( ) async throws {
53+ await #expect( processExitsWith: . failure) {
54+ let ( stream, source) = NIOAsyncChannelInboundStream< HTTPRequestPart> . makeTestingStream( )
55+
56+ // Only write a request body part; do not write an end part.
57+ source. yield ( . body( . init( ) ) )
58+ source. finish ( )
59+
60+ let requestReader = HTTPRequestConcludingAsyncReader (
61+ iterator: stream. makeAsyncIterator ( ) ,
62+ readerState: . init( )
63+ )
64+
65+ _ = try await requestReader. consumeAndConclude { bodyReader in
66+ var bodyReader = bodyReader
67+
68+ try await bodyReader. read ( maximumCount: nil ) { _ in }
69+ // The stream has finished without an end part. Calling `read` now should result in a fatal error.
70+ try await bodyReader. read ( maximumCount: nil ) { _ in }
4671 }
4772 }
4873 }
@@ -172,26 +197,125 @@ struct HTTPRequestConcludingAsyncReaderTests {
172197 @available ( macOS 26 . 0 , iOS 26 . 0 , watchOS 26 . 0 , tvOS 26 . 0 , visionOS 26 . 0 , * )
173198 @Test ( " More bytes available than consumption limit " )
174199 func testCollectMoreBytesThanAvailable( ) async throws {
175- await #expect( processExitsWith: . failure) {
176- let ( stream, source) = NIOAsyncChannelInboundStream< HTTPRequestPart> . makeTestingStream( )
200+ let ( stream, source) = NIOAsyncChannelInboundStream< HTTPRequestPart> . makeTestingStream( )
177201
178- // Write 10 bytes
179- source. yield ( . body( . init( repeating: 5 , count: 10 ) ) )
180- source. finish ( )
202+ // Write 10 bytes
203+ source. yield ( . body( . init( repeating: 5 , count: 10 ) ) )
204+ source. finish ( )
181205
182- let requestReader = HTTPRequestConcludingAsyncReader (
183- iterator: stream. makeAsyncIterator ( ) ,
184- readerState: . init( )
185- )
206+ let requestReader = HTTPRequestConcludingAsyncReader (
207+ iterator: stream. makeAsyncIterator ( ) ,
208+ readerState: . init( )
209+ )
210+
211+ _ = try await requestReader. consumeAndConclude { requestBodyReader in
212+ var requestBodyReader = requestBodyReader
213+
214+ // There are more bytes available than our limit.
215+ let collected = try await requestBodyReader. collect ( upTo: 9 ) { element in
216+ var buffer = ByteBuffer ( )
217+ buffer. writeBytes ( element. bytes)
218+ return buffer
219+ }
220+
221+ // We should only collect up to the limit (the first 9 bytes).
222+ #expect( collected == . init( repeating: 5 , count: 9 ) )
223+ }
224+ }
225+
226+ @available ( macOS 26 . 0 , iOS 26 . 0 , watchOS 26 . 0 , tvOS 26 . 0 , visionOS 26 . 0 , * )
227+ @Test ( " Multiple body chunks; multiple reads with limits " )
228+ func testReadWithLimits( ) async throws {
229+ let ( stream, source) = NIOAsyncChannelInboundStream< HTTPRequestPart> . makeTestingStream( )
230+
231+ // First write 10 bytes;
232+ source. yield ( . body( . init( repeating: 1 , count: 10 ) ) )
233+ // Then write another 5 bytes.
234+ source. yield ( . body( . init( repeating: 2 , count: 5 ) ) )
235+ source. yield ( . end( nil ) )
236+ source. finish ( )
237+
238+ let streamIterator = stream. makeAsyncIterator ( )
239+
240+ let requestReader = HTTPRequestConcludingAsyncReader ( iterator: streamIterator, readerState: . init( ) )
241+ _ = try await requestReader. consumeAndConclude { requestBodyReader in
242+ var requestBodyReader = requestBodyReader
243+
244+ // Collect 8 bytes (partial of first write).
245+ let collectedPartOne = try await requestBodyReader. collect ( upTo: 8 ) { element in
246+ var buffer = ByteBuffer ( )
247+ buffer. writeBytes ( element. bytes)
248+ return buffer
249+ }
250+
251+ // Then collect 4 more bytes (overlap of first and second write).
252+ let collectedPartTwo = try await requestBodyReader. collect ( upTo: 4 ) { element in
253+ var buffer = ByteBuffer ( )
254+ buffer. writeBytes ( element. bytes)
255+ return buffer
256+ }
257+
258+ // Then collect 3 more bytes (partial of second write).
259+ let collectedPartThree = try await requestBodyReader. collect ( upTo: 3 ) { element in
260+ var buffer = ByteBuffer ( )
261+ buffer. writeBytes ( element. bytes)
262+ return buffer
263+ }
186264
187- _ = try await requestReader. consumeAndConclude { requestBodyReader in
188- var requestBodyReader = requestBodyReader
265+ #expect( collectedPartOne == . init( repeating: 1 , count: 8 ) )
266+ #expect( collectedPartTwo == . init( [ 1 , 1 , 2 , 2 ] ) )
267+ #expect( collectedPartThree == . init( repeating: 2 , count: 3 ) )
268+ }
269+ }
270+
271+ @available ( macOS 26 . 0 , iOS 26 . 0 , watchOS 26 . 0 , tvOS 26 . 0 , visionOS 26 . 0 , * )
272+ @Test ( " Multiple random-length chunks; multiple reads with random limits " )
273+ func testMultipleReadsWithRandomLimits( ) async throws {
274+ let ( stream, source) = NIOAsyncChannelInboundStream< HTTPRequestPart> . makeTestingStream( )
275+
276+ // Generate random ByteBuffers of varying length and write them to the stream.
277+ var randomBuffer = ByteBuffer ( )
278+ for _ in 0 ..< 100 {
279+ let randomNumber = UInt8 . random ( in: 1 ... 50 )
280+ let randomCount = Int . random ( in: 1 ... 50 )
281+
282+ let randomData = ByteBuffer ( repeating: randomNumber, count: randomCount)
283+ // Store the data so we can track what we have wrote
284+ randomBuffer. writeImmutableBuffer ( randomData)
285+
286+ source. yield ( . body( randomData) )
287+ }
288+ source. yield ( . end( nil ) )
289+ source. finish ( )
290+
291+ let streamIterator = stream. makeAsyncIterator ( )
292+
293+ let requestReader = HTTPRequestConcludingAsyncReader ( iterator: streamIterator, readerState: . init( ) )
294+ _ = try await requestReader. consumeAndConclude { requestBodyReader in
295+ var requestBodyReader = requestBodyReader
189296
190- // Since there are more bytes than requested, this should fail.
191- try await requestBodyReader. collect ( upTo: 9 ) { element in
192- ( )
297+ var collectedBuffer = ByteBuffer ( )
298+ while true {
299+ let randomMaxCount = Int . random ( in: 1 ... 100 )
300+
301+ let collected = try await requestBodyReader. collect ( upTo: randomMaxCount) { element in
302+ var localBuffer = ByteBuffer ( )
303+ localBuffer. writeBytes ( element. bytes)
304+ return localBuffer
193305 }
306+
307+ if collected. readableBytes == 0 {
308+ break
309+ }
310+
311+ // The collected buffer should never exceed the specified max count.
312+ try #require( collected. readableBytes <= randomMaxCount)
313+
314+ collectedBuffer. writeImmutableBuffer ( collected)
194315 }
316+
317+ // Check if the collected buffer exactly matches what was written to the stream.
318+ try #require( randomBuffer == collectedBuffer)
195319 }
196320 }
197321}
0 commit comments