use std::io::Cursor; use zagan_bet::protocol::{ FrameBuffer, FrameError, MAX_FRAME_LEN, Packet, encode_frame, event_line, io_failure_message, parse_packet, parse_request, reply_line, request_line, }; fn framed(payload: &str) -> Vec { encode_frame(payload) } #[test] fn decodes_every_complete_frame_and_keeps_the_remainder() { let mut buffer = FrameBuffer::new(); let mut bytes = framed("RES 1 PONG 1700000000000"); bytes.extend(framed("EVT 2 crash.tick session=cr1 t=1700000000100 m=1.5")); buffer.push(&bytes); assert_eq!( buffer.next_frame().unwrap().unwrap(), "RES 1 PONG 1700000000000" ); assert_eq!( buffer.next_frame().unwrap().unwrap(), "EVT 2 crash.tick session=cr1 t=1700000000100 m=1.5" ); assert!(buffer.next_frame().is_none()); assert!(buffer.is_empty()); } #[test] fn waits_for_a_partial_frame() { let mut buffer = FrameBuffer::new(); let bytes = framed("RES 7 OK"); buffer.push(&bytes[..3]); assert!(buffer.next_frame().is_none()); buffer.push(&bytes[3..7]); assert!(buffer.next_frame().is_none()); buffer.push(&bytes[7..]); assert_eq!(buffer.next_frame().unwrap().unwrap(), "RES 7 OK"); } #[test] fn an_oversized_length_is_rejected_without_waiting_for_bytes() { let mut buffer = FrameBuffer::new(); buffer.push(&((MAX_FRAME_LEN as u32 + 1).to_be_bytes())); match buffer.next_frame() { Some(Err(FrameError::TooLarge(length))) => { assert_eq!(length, MAX_FRAME_LEN + 1); } other => panic!("expected an oversize error, got {other:?}"), } assert!(buffer.is_empty(), "the buffer must not keep the bad frame"); } #[test] fn invalid_utf8_is_a_protocol_error() { let mut buffer = FrameBuffer::new(); buffer.push(&1u32.to_be_bytes()); buffer.push(&[0xff]); assert_eq!(buffer.next_frame(), Some(Err(FrameError::InvalidUtf8))); } #[test] fn reads_frames_from_a_blocking_reader() { let mut bytes = framed("RES 1 OK"); bytes.extend(framed("EVT 1 crash.tick session=cr1 t=1 m=1.0")); let mut reader = Cursor::new(bytes); let mut buffer = FrameBuffer::new(); assert_eq!( buffer.read_frame(&mut reader).unwrap().as_deref(), Some("RES 1 OK") ); assert_eq!( buffer.read_frame(&mut reader).unwrap().as_deref(), Some("EVT 1 crash.tick session=cr1 t=1 m=1.0") ); assert_eq!(buffer.read_frame(&mut reader).unwrap(), None); } #[test] fn builds_and_parses_the_envelope() { assert_eq!( request_line(42, None, "PLAY_START crash 50"), "REQ 42 PLAY_START crash 50" ); assert_eq!( request_line(42, Some("abc123"), "PLAY_START crash 50"), "REQ 42 idem=abc123 PLAY_START crash 50" ); let request = parse_request("REQ 42 idem=abc123 PLAY_START crash 50").unwrap(); assert_eq!(request.id, 42); assert_eq!(request.idem.as_deref(), Some("abc123")); assert_eq!(request.payload, "PLAY_START crash 50"); let request = parse_request("REQ 7 PING").unwrap(); assert_eq!(request.id, 7); assert_eq!(request.idem, None); assert_eq!(request.payload, "PING"); assert!(parse_request("PLAY_START crash 50").is_err()); assert!(parse_request("REQ nope PING").is_err()); assert!(parse_request("REQ 1 idem= PING").is_err()); } #[test] fn parses_replies_and_events() { assert_eq!( parse_packet(&reply_line(9, "ERROR Insufficient balance")).unwrap(), Packet::Reply { id: 9, body: "ERROR Insufficient balance".to_string() } ); assert_eq!( parse_packet(&event_line(3, "CRASH_BUSTED {}")).unwrap(), Packet::Event { seq: 3, body: "CRASH_BUSTED {}".to_string() } ); assert!(parse_packet("REQ 1 PING").is_err()); assert!(parse_packet("RES nope OK").is_err()); } #[test] fn io_failures_are_reported_as_short_messages() { let timeout = std::io::Error::new(std::io::ErrorKind::TimedOut, "slow"); assert_eq!(io_failure_message(&timeout), "ERROR Request timed out"); let closed = std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "gone"); assert_eq!(io_failure_message(&closed), "ERROR Connection lost"); }