@@ -139,7 +139,11 @@ fn get_byte(buf: &[u8], pos: &mut usize) -> Result<u8, DecodeError> {
139139
140140#[ cfg( test) ]
141141mod tests {
142+ use proptest:: prelude:: * ;
143+
142144 use super :: * ;
145+ use crate :: producer:: Outgoing ;
146+ use crate :: types:: IngestableEvent ;
143147
144148 fn round_trip ( team_id : i64 , pt : PropertyType , key : & str , value : & str , count : u64 ) {
145149 let buf = encode ( team_id, pt, key, value, count) ;
@@ -224,4 +228,90 @@ mod tests {
224228 "binary {binary} should be under half of json {json}"
225229 ) ;
226230 }
231+
232+ fn arb_property_type ( ) -> impl Strategy < Value = PropertyType > {
233+ prop_oneof ! [
234+ Just ( PropertyType :: Event ) ,
235+ Just ( PropertyType :: Person ) ,
236+ any:: <u8 >( ) . prop_map( PropertyType :: Group ) ,
237+ ]
238+ }
239+
240+ proptest ! {
241+ #[ test]
242+ fn round_trips_any_message(
243+ team_id: i64 ,
244+ property_type in arb_property_type( ) ,
245+ key in ".*" ,
246+ value in ".*" ,
247+ count: u64 ,
248+ ) {
249+ let decoded = decode( & encode( team_id, property_type, & key, & value, count) ) . unwrap( ) ;
250+ prop_assert_eq!( decoded. team_id, team_id) ;
251+ prop_assert_eq!( decoded. property_type, property_type) ;
252+ prop_assert_eq!( decoded. property_key, key) ;
253+ prop_assert_eq!( decoded. property_value, value) ;
254+ prop_assert_eq!( decoded. property_count, count) ;
255+ }
256+
257+ // Flipping the producer's wire format must not change what the
258+ // merger sees: both encodings of a message decode identically.
259+ #[ test]
260+ fn binary_and_json_decode_agree(
261+ team_id: i64 ,
262+ property_type in arb_property_type( ) ,
263+ key in ".*" ,
264+ value in ".*" ,
265+ count: u64 ,
266+ ) {
267+ let json = serde_json:: to_vec( & Outgoing {
268+ team_id,
269+ property_type,
270+ property_key: & key,
271+ property_value: & value,
272+ property_count: count,
273+ } )
274+ . unwrap( ) ;
275+ let binary = encode( team_id, property_type, & key, & value, count) ;
276+
277+ let from_json = PropertyValueMessage :: decode( & json) . unwrap( ) ;
278+ let from_binary = PropertyValueMessage :: decode( & binary) . unwrap( ) ;
279+ prop_assert_eq!( & from_json. team_id, & from_binary. team_id) ;
280+ prop_assert_eq!( & from_json. property_type, & from_binary. property_type) ;
281+ prop_assert_eq!( & from_json. property_key, & from_binary. property_key) ;
282+ prop_assert_eq!( & from_json. property_value, & from_binary. property_value) ;
283+ prop_assert_eq!( & from_json. property_count, & from_binary. property_count) ;
284+ }
285+
286+ // Random bytes after a valid magic drive the parser deep into the
287+ // varint/string paths; it must reject or accept, never panic.
288+ #[ test]
289+ fn decode_never_panics( garbage in proptest:: collection:: vec( any:: <u8 >( ) , 0 ..256 ) ) {
290+ drop( decode( & garbage) ) ;
291+ let mut with_magic = MAGIC . to_vec( ) ;
292+ with_magic. extend_from_slice( & garbage) ;
293+ drop( decode( & with_magic) ) ;
294+ }
295+
296+ #[ test]
297+ fn corrupted_encodings_never_panic_and_truncations_error(
298+ team_id: i64 ,
299+ property_type in arb_property_type( ) ,
300+ key in ".*" ,
301+ value in ".*" ,
302+ count: u64 ,
303+ flipped_byte in any:: <prop:: sample:: Index >( ) ,
304+ flipped_bit in 0u8 ..8 ,
305+ ) {
306+ let buf = encode( team_id, property_type, & key, & value, count) ;
307+
308+ let mut corrupted = buf. clone( ) ;
309+ let i = flipped_byte. index( corrupted. len( ) ) ;
310+ corrupted[ i] ^= 1 << flipped_bit;
311+ drop( decode( & corrupted) ) ;
312+
313+ let cut = flipped_byte. index( buf. len( ) ) ;
314+ prop_assert!( decode( & buf[ ..cut] ) . is_err( ) , "truncation at {} must error" , cut) ;
315+ }
316+ }
227317}
0 commit comments