55//! unlimited behaviour). Both tests pin the same cap so they are
66//! order-independent under the parallel test runner.
77
8+ use std:: cell:: { Cell , RefCell } ;
9+ use std:: ops:: ControlFlow ;
10+
811use serde_json:: Value ;
912use tokio:: runtime:: Builder ;
10- use vespera_inprocess:: { dispatch_from_bytes, set_max_request_bytes} ;
13+ use vespera_inprocess:: {
14+ dispatch_from_bytes, dispatch_streaming_async, dispatch_streaming_with_header_async,
15+ set_max_request_bytes,
16+ } ;
1117
1218/// Small enough that a tiny valid header passes but a padded request
1319/// trips the cap.
@@ -19,15 +25,28 @@ fn ensure_cap() {
1925 let _ = set_max_request_bytes ( CAP ) ;
2026}
2127
28+ /// Parse the JSON header out of a `[u32 BE len | header JSON | body]`
29+ /// wire response.
30+ fn parse_header_json ( resp : & [ u8 ] ) -> Value {
31+ assert ! ( resp. len( ) >= 4 , "wire response too short" ) ;
32+ let header_len = u32:: from_be_bytes ( resp[ ..4 ] . try_into ( ) . unwrap ( ) ) as usize ;
33+ serde_json:: from_slice ( & resp[ 4 ..4 + header_len] ) . expect ( "response header JSON" )
34+ }
35+
2236fn dispatch ( wire : Vec < u8 > ) -> Value {
2337 let runtime = Builder :: new_current_thread ( )
2438 . enable_all ( )
2539 . build ( )
2640 . expect ( "build runtime" ) ;
27- let resp = dispatch_from_bytes ( wire, & runtime) ;
28- assert ! ( resp. len( ) >= 4 , "wire response too short" ) ;
29- let header_len = u32:: from_be_bytes ( resp[ ..4 ] . try_into ( ) . unwrap ( ) ) as usize ;
30- serde_json:: from_slice ( & resp[ 4 ..4 + header_len] ) . expect ( "response header JSON" )
41+ parse_header_json ( & dispatch_from_bytes ( wire, & runtime) )
42+ }
43+
44+ fn block_on < F : std:: future:: Future > ( fut : F ) -> F :: Output {
45+ Builder :: new_current_thread ( )
46+ . enable_all ( )
47+ . build ( )
48+ . expect ( "build runtime" )
49+ . block_on ( fut)
3150}
3251
3352fn wire_with_body ( body_len : usize ) -> Vec < u8 > {
@@ -66,3 +85,68 @@ fn within_limit_request_is_not_capped() {
6685 "a request within the cap must not be rejected as oversized"
6786 ) ;
6887}
88+
89+ // ── Streaming-path ingress cap (INP-01) ──────────────────────────────
90+ //
91+ // Response streaming still buffers the full *request* in memory, so it
92+ // must enforce the same cap as the buffered entry points — unlike
93+ // bidirectional streaming, which pulls the request chunk-by-chunk and
94+ // is intentionally exempt.
95+
96+ #[ test]
97+ fn oversized_streaming_request_returns_413 ( ) {
98+ ensure_cap ( ) ;
99+ let wire = wire_with_body ( 200 ) ;
100+ assert ! ( wire. len( ) > CAP ) ;
101+
102+ let chunks = Cell :: new ( 0usize ) ;
103+ let header_bytes = block_on ( dispatch_streaming_async ( wire, |_chunk : & [ u8 ] | {
104+ chunks. set ( chunks. get ( ) + 1 ) ;
105+ ControlFlow :: Continue ( ( ) )
106+ } ) ) ;
107+
108+ let header = parse_header_json ( & header_bytes) ;
109+ assert_eq ! (
110+ header[ "status" ] . as_u64( ) ,
111+ Some ( 413 ) ,
112+ "response streaming buffers the full request, so an over-cap request must be 413"
113+ ) ;
114+ assert_eq ! (
115+ chunks. get( ) ,
116+ 0 ,
117+ "a capped request must never stream body chunks"
118+ ) ;
119+ }
120+
121+ #[ test]
122+ fn oversized_streaming_with_header_request_returns_413 ( ) {
123+ ensure_cap ( ) ;
124+ let wire = wire_with_body ( 200 ) ;
125+ assert ! ( wire. len( ) > CAP ) ;
126+
127+ let header_seen: RefCell < Option < Vec < u8 > > > = RefCell :: new ( None ) ;
128+ let chunks = Cell :: new ( 0usize ) ;
129+ block_on ( dispatch_streaming_with_header_async (
130+ wire,
131+ |header : & [ u8 ] | * header_seen. borrow_mut ( ) = Some ( header. to_vec ( ) ) ,
132+ |_chunk : & [ u8 ] | {
133+ chunks. set ( chunks. get ( ) + 1 ) ;
134+ ControlFlow :: Continue ( ( ) )
135+ } ,
136+ ) ) ;
137+
138+ let header_bytes = header_seen
139+ . into_inner ( )
140+ . expect ( "the header callback must fire exactly once, even on the 413 cap path" ) ;
141+ let header = parse_header_json ( & header_bytes) ;
142+ assert_eq ! (
143+ header[ "status" ] . as_u64( ) ,
144+ Some ( 413 ) ,
145+ "the 413 must be delivered through the header callback"
146+ ) ;
147+ assert_eq ! (
148+ chunks. get( ) ,
149+ 0 ,
150+ "a capped request must never stream body chunks"
151+ ) ;
152+ }
0 commit comments