soth-mitm 0.3.3

Rust intercepting proxy crate with deterministic handler/event contracts for SOTH.
Documentation
use proptest::prelude::*;
use soth_mitm::test_protocol::SseParser;

fn line_strategy() -> impl Strategy<Value = String> {
    proptest::string::string_regex("[a-z0-9]{0,12}").expect("line regex")
}

proptest! {
    #[test]
    fn multiline_data_round_trips_under_arbitrary_split(
        lines in proptest::collection::vec(line_strategy(), 1..8),
        split in 0_usize..512,
    ) {
        let mut payload = String::new();
        for line in &lines {
            payload.push_str("data: ");
            payload.push_str(line);
            payload.push('\n');
        }
        payload.push('\n');

        let bytes = payload.as_bytes();
        let split_at = split.min(bytes.len());
        let mut parser = SseParser::new();

        let mut emitted = Vec::new();
        emitted.extend(parser.push_bytes(&bytes[..split_at]));
        emitted.extend(parser.push_bytes(&bytes[split_at..]));

        prop_assert_eq!(emitted.len(), 1);
        let event = &emitted[0];
        prop_assert_eq!(event.data_line_count, lines.len());
        prop_assert_eq!(event.data.as_str(), lines.join("\n"));
    }

    #[test]
    fn comment_only_input_emits_no_events(
        comments in proptest::collection::vec(line_strategy(), 1..8),
        split in 0_usize..512,
    ) {
        let mut payload = String::new();
        for comment in &comments {
            payload.push(':');
            payload.push_str(comment);
            payload.push('\n');
        }
        payload.push('\n');

        let bytes = payload.as_bytes();
        let split_at = split.min(bytes.len());
        let mut parser = SseParser::new();

        let mut emitted = Vec::new();
        emitted.extend(parser.push_bytes(&bytes[..split_at]));
        emitted.extend(parser.push_bytes(&bytes[split_at..]));
        let tail = parser.finish();

        prop_assert!(emitted.is_empty());
        prop_assert!(tail.is_none());
    }

    #[test]
    fn invalid_retry_field_does_not_set_retry_ms(
        bad_retry in proptest::string::string_regex("[a-zA-Z_-]{1,16}").expect("retry regex"),
        data_line in line_strategy(),
    ) {
        let payload = format!("retry: {bad_retry}\ndata: {data_line}\n\n");
        let mut parser = SseParser::new();
        let emitted = parser.push_bytes(payload.as_bytes());

        prop_assert_eq!(emitted.len(), 1);
        let event = &emitted[0];
        prop_assert_eq!(event.retry_ms, None);
        prop_assert_eq!(event.data.as_str(), data_line.as_str());
    }
}