@@ -234,6 +234,8 @@ mod test_support {
234
234
use std:: task:: Waker ;
235
235
236
236
use proptest:: prelude:: * ;
237
+ use serde_multipart:: Message ;
238
+ use serde_multipart:: Part ;
237
239
238
240
use super :: * ;
239
241
@@ -608,6 +610,45 @@ mod test_support {
608
610
// length: 1..=8, step: 1..=8 (no zeros)
609
611
prop:: collection:: vec ( 1 ..=8usize , 1 ..=8 )
610
612
}
613
+
614
+ /// Generate a single multipart `Part` with up to `max_len` bytes.
615
+ /// Includes empty parts to exercise edge cases and boundary
616
+ /// slicing.
617
+ pub fn multipart_part ( max_len : usize ) -> impl Strategy < Value = Part > {
618
+ proptest:: collection:: vec ( any :: < u8 > ( ) , 0 ..=max_len) . prop_map ( Part :: from)
619
+ }
620
+
621
+ /// Generate a `Message` that is either unipart or true multipart
622
+ /// (body + 0..=3 additional parts). Kept small for fast tests.
623
+ pub fn multipart_message ( ) -> impl Strategy < Value = Message > {
624
+ let max_parts = 4usize ; // total parts = 1..=4
625
+ let max_len = 64usize ; // bytes per part
626
+
627
+ prop_oneof ! [
628
+ // Unipart (compat path): body only
629
+ multipart_part( max_len) . prop_map( |body| Message :: from_body_and_parts( body, vec![ ] ) ) ,
630
+ // Multipart: body + 1..=3 extra parts
631
+ (
632
+ multipart_part( max_len) ,
633
+ proptest:: collection:: vec( multipart_part( max_len) , 1 ..=max_parts - 1 )
634
+ )
635
+ . prop_map( |( body, parts) | Message :: from_body_and_parts( body, parts) ) ,
636
+ ]
637
+ }
638
+
639
+ /// Generate a `Message` that is *strictly* multipart (body +
640
+ /// 1..=3 additional parts). Use this when you want to force the
641
+ /// vectored path every time.
642
+ pub fn multipart_message_only ( ) -> impl Strategy < Value = Message > {
643
+ let max_parts = 4usize ; // total parts = 2..=4
644
+ let max_len = 64usize ;
645
+
646
+ (
647
+ multipart_part ( max_len) ,
648
+ proptest:: collection:: vec ( multipart_part ( max_len) , 1 ..=max_parts - 1 ) ,
649
+ )
650
+ . prop_map ( |( body, parts) | Message :: from_body_and_parts ( body, parts) )
651
+ }
611
652
}
612
653
613
654
#[ cfg( test) ]
@@ -849,6 +890,47 @@ mod property_tests {
849
890
}
850
891
}
851
892
893
+ proptest ! {
894
+ // Sanity: multipart_message() yields either unipart or 1..=3
895
+ // extra parts, frames to the advertised length, and
896
+ // round-trips via from_framed().
897
+ #[ test]
898
+ fn test_multipart_message_shape( msg in test_support:: multipart_message( ) ) {
899
+ // Parts count bounds (unipart allowed)
900
+ prop_assert!( msg. num_parts( ) <= 3 ) ;
901
+
902
+ // `frame_len` matches actual framed length
903
+ let mut framed = msg. clone( ) . framed( ) ;
904
+ let framed_bytes = framed. copy_to_bytes( framed. remaining( ) ) ;
905
+ prop_assert_eq!( framed_bytes. len( ) , msg. frame_len( ) ) ;
906
+
907
+ // round-trip `framed` → `Message` → `framed` produces
908
+ // identical bytes
909
+ let rt = serde_multipart:: Message :: from_framed( framed_bytes. clone( ) ) . unwrap( ) ;
910
+ let mut rt_framed = rt. framed( ) ;
911
+ let rt_bytes = rt_framed. copy_to_bytes( rt_framed. remaining( ) ) ;
912
+ prop_assert_eq!( rt_bytes, framed_bytes) ;
913
+ }
914
+
915
+ // Sanity: multipart_message_only() always yields *strictly*
916
+ // multipart (≥1 extra part), and also round-trips and
917
+ // length-checks as above.
918
+ #[ test]
919
+ fn test_multipart_message_only_shape( msg in test_support:: multipart_message_only( ) ) {
920
+ // Strictly multipart.
921
+ prop_assert!( msg. num_parts( ) >= 1 && msg. num_parts( ) <= 3 ) ;
922
+
923
+ let mut framed = msg. clone( ) . framed( ) ;
924
+ let framed_bytes = framed. copy_to_bytes( framed. remaining( ) ) ;
925
+ prop_assert_eq!( framed_bytes. len( ) , msg. frame_len( ) ) ;
926
+
927
+ let rt = serde_multipart:: Message :: from_framed( framed_bytes. clone( ) ) . unwrap( ) ;
928
+ let mut rt_framed = rt. framed( ) ;
929
+ let rt_bytes = rt_framed. copy_to_bytes( rt_framed. remaining( ) ) ;
930
+ prop_assert_eq!( rt_bytes, framed_bytes) ;
931
+ }
932
+ }
933
+
852
934
// Theorem: `FrameWrite::send` is cancel-safe.
853
935
//
854
936
// Matches the cancel-safety contract from `test_utils::cancel_safe`:
0 commit comments