diff options
Diffstat (limited to '')
| -rw-r--r-- | tests/protocol.rs | 150 |
1 files changed, 101 insertions, 49 deletions
diff --git a/tests/protocol.rs b/tests/protocol.rs index bf8a4fd..d829ba9 100644 --- a/tests/protocol.rs +++ b/tests/protocol.rs | |||
| @@ -1,84 +1,136 @@ | |||
| 1 | use std::io::{Cursor, Error, ErrorKind}; | 1 | use std::io::Cursor; |
| 2 | use zagan_bet::protocol::{ | ||
| 3 | FrameBuffer, FrameError, MAX_FRAME_LEN, Packet, encode_frame, event_line, io_failure_message, | ||
| 4 | parse_packet, parse_request, reply_line, request_line, | ||
| 5 | }; | ||
| 2 | 6 | ||
| 3 | use zagan_bet::protocol::{io_failure_message, LineBuffer}; | 7 | fn framed(payload: &str) -> Vec<u8> { |
| 8 | encode_frame(payload) | ||
| 9 | } | ||
| 4 | 10 | ||
| 5 | #[test] | 11 | #[test] |
| 6 | fn keeps_data_after_the_first_newline() { | 12 | fn decodes_every_complete_frame_and_keeps_the_remainder() { |
| 7 | let mut buffer = LineBuffer::new(); | 13 | let mut buffer = FrameBuffer::new(); |
| 8 | buffer.push(b"PONG\nGAME_RESULT session 60 10 990 OSS two\n"); | 14 | let mut bytes = framed("RES 1 PONG 1700000000000"); |
| 15 | bytes.extend(framed("EVT 2 crash.tick session=cr1 t=1700000000100 m=1.5")); | ||
| 16 | buffer.push(&bytes); | ||
| 9 | 17 | ||
| 10 | assert_eq!(buffer.next_line().as_deref(), Some("PONG")); | ||
| 11 | assert_eq!( | 18 | assert_eq!( |
| 12 | buffer.next_line().as_deref(), | 19 | buffer.next_frame().unwrap().unwrap(), |
| 13 | Some("GAME_RESULT session 60 10 990 OSS two") | 20 | "RES 1 PONG 1700000000000" |
| 21 | ); | ||
| 22 | assert_eq!( | ||
| 23 | buffer.next_frame().unwrap().unwrap(), | ||
| 24 | "EVT 2 crash.tick session=cr1 t=1700000000100 m=1.5" | ||
| 14 | ); | 25 | ); |
| 15 | assert_eq!(buffer.next_line(), None); | 26 | assert!(buffer.next_frame().is_none()); |
| 27 | assert!(buffer.is_empty()); | ||
| 16 | } | 28 | } |
| 17 | 29 | ||
| 18 | #[test] | 30 | #[test] |
| 19 | fn handles_crlf_and_empty_lines() { | 31 | fn waits_for_a_partial_frame() { |
| 20 | let mut buffer = LineBuffer::new(); | 32 | let mut buffer = FrameBuffer::new(); |
| 21 | buffer.push(b"PONG\r\n\r\n"); | 33 | let bytes = framed("RES 7 OK"); |
| 34 | buffer.push(&bytes[..3]); | ||
| 35 | assert!(buffer.next_frame().is_none()); | ||
| 22 | 36 | ||
| 23 | assert_eq!(buffer.next_line().as_deref(), Some("PONG")); | 37 | buffer.push(&bytes[3..7]); |
| 24 | assert_eq!(buffer.next_line().as_deref(), Some("")); | 38 | assert!(buffer.next_frame().is_none()); |
| 25 | assert_eq!(buffer.next_line(), None); | 39 | |
| 40 | buffer.push(&bytes[7..]); | ||
| 41 | assert_eq!(buffer.next_frame().unwrap().unwrap(), "RES 7 OK"); | ||
| 26 | } | 42 | } |
| 27 | 43 | ||
| 28 | #[test] | 44 | #[test] |
| 29 | fn waits_for_a_split_line() { | 45 | fn an_oversized_length_is_rejected_without_waiting_for_bytes() { |
| 30 | let mut buffer = LineBuffer::new(); | 46 | let mut buffer = FrameBuffer::new(); |
| 31 | buffer.push(b"PO"); | 47 | buffer.push(&((MAX_FRAME_LEN as u32 + 1).to_be_bytes())); |
| 32 | assert_eq!(buffer.next_line(), None); | 48 | match buffer.next_frame() { |
| 49 | Some(Err(FrameError::TooLarge(length))) => { | ||
| 50 | assert_eq!(length, MAX_FRAME_LEN + 1); | ||
| 51 | } | ||
| 52 | other => panic!("expected an oversize error, got {other:?}"), | ||
| 53 | } | ||
| 54 | assert!(buffer.is_empty(), "the buffer must not keep the bad frame"); | ||
| 55 | } | ||
| 33 | 56 | ||
| 34 | buffer.push(b"NG\n"); | 57 | #[test] |
| 35 | assert_eq!(buffer.next_line().as_deref(), Some("PONG")); | 58 | fn invalid_utf8_is_a_protocol_error() { |
| 59 | let mut buffer = FrameBuffer::new(); | ||
| 60 | buffer.push(&1u32.to_be_bytes()); | ||
| 61 | buffer.push(&[0xff]); | ||
| 62 | assert_eq!(buffer.next_frame(), Some(Err(FrameError::InvalidUtf8))); | ||
| 36 | } | 63 | } |
| 37 | 64 | ||
| 38 | #[test] | 65 | #[test] |
| 39 | fn reads_multiple_lines_from_a_reader() { | 66 | fn reads_frames_from_a_blocking_reader() { |
| 40 | let mut buffer = LineBuffer::new(); | 67 | let mut bytes = framed("RES 1 OK"); |
| 41 | let mut reader = Cursor::new(b"one\ntwo\n".to_vec()); | 68 | bytes.extend(framed("EVT 1 crash.tick session=cr1 t=1 m=1.0")); |
| 69 | let mut reader = Cursor::new(bytes); | ||
| 70 | let mut buffer = FrameBuffer::new(); | ||
| 42 | 71 | ||
| 43 | assert_eq!( | 72 | assert_eq!( |
| 44 | buffer.read_line(&mut reader).unwrap().as_deref(), | 73 | buffer.read_frame(&mut reader).unwrap().as_deref(), |
| 45 | Some("one") | 74 | Some("RES 1 OK") |
| 46 | ); | 75 | ); |
| 47 | assert_eq!( | 76 | assert_eq!( |
| 48 | buffer.read_line(&mut reader).unwrap().as_deref(), | 77 | buffer.read_frame(&mut reader).unwrap().as_deref(), |
| 49 | Some("two") | 78 | Some("EVT 1 crash.tick session=cr1 t=1 m=1.0") |
| 50 | ); | 79 | ); |
| 51 | assert_eq!(buffer.read_line(&mut reader).unwrap(), None); | 80 | assert_eq!(buffer.read_frame(&mut reader).unwrap(), None); |
| 52 | } | 81 | } |
| 53 | 82 | ||
| 54 | #[test] | 83 | #[test] |
| 55 | fn returns_a_partial_line_at_eof() { | 84 | fn builds_and_parses_the_envelope() { |
| 56 | let mut buffer = LineBuffer::new(); | ||
| 57 | let mut reader = Cursor::new(b"partial".to_vec()); | ||
| 58 | |||
| 59 | assert_eq!( | 85 | assert_eq!( |
| 60 | buffer.read_line(&mut reader).unwrap().as_deref(), | 86 | request_line(42, None, "PLAY_START crash 50"), |
| 61 | Some("partial") | 87 | "REQ 42 PLAY_START crash 50" |
| 62 | ); | 88 | ); |
| 63 | assert_eq!(buffer.read_line(&mut reader).unwrap(), None); | 89 | assert_eq!( |
| 90 | request_line(42, Some("abc123"), "PLAY_START crash 50"), | ||
| 91 | "REQ 42 idem=abc123 PLAY_START crash 50" | ||
| 92 | ); | ||
| 93 | |||
| 94 | let request = parse_request("REQ 42 idem=abc123 PLAY_START crash 50").unwrap(); | ||
| 95 | assert_eq!(request.id, 42); | ||
| 96 | assert_eq!(request.idem.as_deref(), Some("abc123")); | ||
| 97 | assert_eq!(request.payload, "PLAY_START crash 50"); | ||
| 98 | |||
| 99 | let request = parse_request("REQ 7 PING").unwrap(); | ||
| 100 | assert_eq!(request.id, 7); | ||
| 101 | assert_eq!(request.idem, None); | ||
| 102 | assert_eq!(request.payload, "PING"); | ||
| 103 | |||
| 104 | assert!(parse_request("PLAY_START crash 50").is_err()); | ||
| 105 | assert!(parse_request("REQ nope PING").is_err()); | ||
| 106 | assert!(parse_request("REQ 1 idem= PING").is_err()); | ||
| 64 | } | 107 | } |
| 65 | 108 | ||
| 66 | #[test] | 109 | #[test] |
| 67 | fn timeout_errors_get_a_timeout_message() { | 110 | fn parses_replies_and_events() { |
| 68 | assert_eq!( | ||
| 69 | io_failure_message(&Error::from(ErrorKind::TimedOut)), | ||
| 70 | "ERROR Request timed out" | ||
| 71 | ); | ||
| 72 | assert_eq!( | ||
| 73 | io_failure_message(&Error::from(ErrorKind::WouldBlock)), | ||
| 74 | "ERROR Request timed out" | ||
| 75 | ); | ||
| 76 | assert_eq!( | 111 | assert_eq!( |
| 77 | io_failure_message(&Error::from(ErrorKind::UnexpectedEof)), | 112 | parse_packet(&reply_line(9, "ERROR Insufficient balance")).unwrap(), |
| 78 | "ERROR Connection lost" | 113 | Packet::Reply { |
| 114 | id: 9, | ||
| 115 | body: "ERROR Insufficient balance".to_string() | ||
| 116 | } | ||
| 79 | ); | 117 | ); |
| 80 | assert_eq!( | 118 | assert_eq!( |
| 81 | io_failure_message(&Error::from(ErrorKind::ConnectionReset)), | 119 | parse_packet(&event_line(3, "CRASH_BUSTED {}")).unwrap(), |
| 82 | "ERROR Connection lost" | 120 | Packet::Event { |
| 121 | seq: 3, | ||
| 122 | body: "CRASH_BUSTED {}".to_string() | ||
| 123 | } | ||
| 83 | ); | 124 | ); |
| 125 | assert!(parse_packet("REQ 1 PING").is_err()); | ||
| 126 | assert!(parse_packet("RES nope OK").is_err()); | ||
| 127 | } | ||
| 128 | |||
| 129 | #[test] | ||
| 130 | fn io_failures_are_reported_as_short_messages() { | ||
| 131 | let timeout = std::io::Error::new(std::io::ErrorKind::TimedOut, "slow"); | ||
| 132 | assert_eq!(io_failure_message(&timeout), "ERROR Request timed out"); | ||
| 133 | |||
| 134 | let closed = std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "gone"); | ||
| 135 | assert_eq!(io_failure_message(&closed), "ERROR Connection lost"); | ||
| 84 | } | 136 | } |
