diff --git a/crates/core/src/bson/mod.rs b/crates/core/src/bson/mod.rs index 6cfaa3c..7d27620 100644 --- a/crates/core/src/bson/mod.rs +++ b/crates/core/src/bson/mod.rs @@ -15,6 +15,7 @@ pub fn from_bytes<'de, T: Deserialize<'de>>(bytes: &'de [u8]) -> Result { + empty: &'a str, + } + + let doc: TestDoc = from_bytes(bson).expect("should deserialize"); + assert_eq!(doc.empty, ""); + } + + #[test] + fn test_unicode_string() { + // {"unicode": "🦀💖"} + let bson = b"\x1b\x00\x00\x00\x02unicode\x00\x09\x00\x00\x00\xf0\x9f\xa6\x80\xf0\x9f\x92\x96\x00\x00"; + + #[derive(Deserialize)] + struct TestDoc<'a> { + unicode: &'a str, + } + + let doc: TestDoc = from_bytes(bson).expect("should deserialize"); + assert_eq!(doc.unicode, "🦀💖"); + } + + #[test] + fn test_boolean_values() { + // {"true_val": true, "false_val": false} + let bson = b"\x1c\x00\x00\x00\x08true_val\x00\x01\x08false_val\x00\x00\x00"; + + #[derive(Deserialize)] + struct TestDoc { + true_val: bool, + false_val: bool, + } + + let doc: TestDoc = from_bytes(bson).expect("should deserialize"); + assert_eq!(doc.true_val, true); + assert_eq!(doc.false_val, false); + } + + #[test] + fn test_null_value() { + // {"null_val": null} + let bson = b"\x0f\x00\x00\x00\x0anull_val\x00\x00"; + + #[derive(Deserialize)] + struct TestDoc { + null_val: Option, + } + + let doc: TestDoc = from_bytes(bson).expect("should deserialize"); + assert_eq!(doc.null_val, None); + } + + #[test] + fn test_empty_document() { + // {} + let bson = b"\x05\x00\x00\x00\x00"; + + #[derive(Deserialize)] + struct TestDoc {} + + let _doc: TestDoc = from_bytes(bson).expect("should deserialize"); + } + + #[test] + fn test_nested_document() { + // {"nested": {"inner": 42}} + let bson = + b"\x1d\x00\x00\x00\x03nested\x00\x10\x00\x00\x00\x10inner\x00*\x00\x00\x00\x00\x00"; + + #[derive(Deserialize)] + struct Inner { + inner: i32, + } + + #[derive(Deserialize)] + struct TestDoc { + nested: Inner, + } + + let doc: TestDoc = from_bytes(bson).expect("should deserialize"); + assert_eq!(doc.nested.inner, 42); + } + + #[test] + fn test_array_with_integers() { + // {"array": [1, 2]} - simplified array test + // Array format: {"0": 1, "1": 2} + let bson = b"\x1f\x00\x00\x00\x04array\x00\x13\x00\x00\x00\x100\x00\x01\x00\x00\x00\x101\x00\x02\x00\x00\x00\x00\x00"; + + #[derive(Deserialize)] + struct TestDoc { + array: Vec, + } + + let doc: TestDoc = from_bytes(bson).expect("should deserialize"); + assert_eq!(doc.array, vec![1, 2]); + } + + #[test] + fn test_binary_data() { + // {"binary": } + let bson = b"\x16\x00\x00\x00\x05binary\x00\x04\x00\x00\x00\x00\x01\x02\x03\x04\x00"; + + #[derive(Deserialize)] + struct TestDoc<'a> { + binary: &'a [u8], + } + + let doc: TestDoc = from_bytes(bson).expect("should deserialize"); + assert_eq!(doc.binary, &[1, 2, 3, 4]); + } + + // Error case tests + + #[test] + fn test_invalid_element_type() { + // Document with invalid element type (99) + let bson = b"\x10\x00\x00\x00\x63test\x00\x01\x00\x00\x00\x00"; + + #[derive(Deserialize)] + #[allow(dead_code)] + struct TestDoc { + test: i32, + } + + let result: Result = from_bytes(bson); + assert!(result.is_err()); + } + + #[test] + fn test_truncated_document() { + // Document claims to be longer than actual data + let bson = b"\xff\x00\x00\x00\x10test\x00"; + + #[derive(Deserialize)] + #[allow(dead_code)] + struct TestDoc { + test: i32, + } + + let result: Result = from_bytes(bson); + assert!(result.is_err()); + } + + #[test] + fn test_invalid_string_length() { + // String with invalid length + let bson = b"\x15\x00\x00\x00\x02test\x00\xff\xff\xff\xff\x00"; + + #[derive(Deserialize)] + #[allow(dead_code)] + struct TestDoc<'a> { + test: &'a str, + } + + let result: Result = from_bytes(bson); + assert!(result.is_err()); + } + + #[test] + fn test_unterminated_cstring() { + // Document with field name that doesn't have null terminator + let bson = b"\x10\x00\x00\x00\x10test\x01\x00\x00\x00\x00\x00"; + + #[derive(Deserialize)] + #[allow(dead_code)] + struct TestDoc { + test: i32, + } + + let result: Result = from_bytes(bson); + assert!(result.is_err()); + } + + #[test] + fn test_document_without_terminator() { + // Document missing the final null byte + let bson = b"\x0d\x00\x00\x00\x10test\x00*\x00\x00\x00"; + + #[derive(Deserialize)] + #[allow(dead_code)] + struct TestDoc { + test: i32, + } + + let result: Result = from_bytes(bson); + assert!(result.is_err()); + } + + #[test] + fn test_invalid_document_size() { + // Document with size less than minimum (5 bytes) + let bson = b"\x04\x00\x00\x00\x00"; + + #[derive(Deserialize)] + struct TestDoc {} + + let result: Result = from_bytes(bson); + assert!(result.is_err()); + } } diff --git a/crates/core/src/bson/parser.rs b/crates/core/src/bson/parser.rs index 1287ade..ddd31b1 100644 --- a/crates/core/src/bson/parser.rs +++ b/crates/core/src/bson/parser.rs @@ -203,6 +203,305 @@ impl<'de> Parser<'de> { } } +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_read_int64_negative_values() { + let neg_one_bytes = (-1i64).to_le_bytes(); + let mut parser = Parser::new(&neg_one_bytes); + assert_eq!(parser.read_int64().unwrap(), -1); + + let min_bytes = (i64::MIN).to_le_bytes(); + let mut parser = Parser::new(&min_bytes); + assert_eq!(parser.read_int64().unwrap(), i64::MIN); + + let neg_42_bytes = (-42i64).to_le_bytes(); + let mut parser = Parser::new(&neg_42_bytes); + assert_eq!(parser.read_int64().unwrap(), -42); + } + + #[test] + fn test_read_int32_negative_values() { + let neg_one_bytes = (-1i32).to_le_bytes(); + let mut parser = Parser::new(&neg_one_bytes); + assert_eq!(parser.read_int32().unwrap(), -1); + + let min_bytes = (i32::MIN).to_le_bytes(); + let mut parser = Parser::new(&min_bytes); + assert_eq!(parser.read_int32().unwrap(), i32::MIN); + + let neg_42_bytes = (-42i32).to_le_bytes(); + let mut parser = Parser::new(&neg_42_bytes); + assert_eq!(parser.read_int32().unwrap(), -42); + } + + #[test] + fn test_read_double_negative_and_special() { + let neg_pi_bytes = (-3.14159f64).to_le_bytes(); + let mut parser = Parser::new(&neg_pi_bytes); + let val = parser.read_double().unwrap(); + assert!((val - (-3.14159)).abs() < 0.00001); + + let neg_inf_bytes = f64::NEG_INFINITY.to_le_bytes(); + let mut parser = Parser::new(&neg_inf_bytes); + assert_eq!(parser.read_double().unwrap(), f64::NEG_INFINITY); + + let nan_bytes = f64::NAN.to_le_bytes(); + let mut parser = Parser::new(&nan_bytes); + assert!(parser.read_double().unwrap().is_nan()); + } + + #[test] + fn test_read_bool_edge_cases() { + let mut parser = Parser::new(&[0x00]); + assert_eq!(parser.read_bool().unwrap(), false); + + let mut parser = Parser::new(&[0x01]); + assert_eq!(parser.read_bool().unwrap(), true); + + let mut parser = Parser::new(&[0xFF]); + assert_eq!(parser.read_bool().unwrap(), true); + + let mut parser = Parser::new(&[0x7F]); + assert_eq!(parser.read_bool().unwrap(), true); + } + + #[test] + fn test_read_string_empty() { + // Empty string: length=1, content=null terminator + let data = &[0x01, 0x00, 0x00, 0x00, 0x00]; + let mut parser = Parser::new(data); + assert_eq!(parser.read_string().unwrap(), ""); + } + + #[test] + fn test_read_string_unicode() { + // String "🦀" (4 UTF-8 bytes + null terminator) + let data = &[0x05, 0x00, 0x00, 0x00, 0xf0, 0x9f, 0xa6, 0x80, 0x00]; + let mut parser = Parser::new(data); + assert_eq!(parser.read_string().unwrap(), "🦀"); + } + + #[test] + fn test_read_cstr_empty() { + let data = &[0x00]; + let mut parser = Parser::new(data); + assert_eq!(parser.read_cstr().unwrap(), ""); + } + + #[test] + fn test_read_cstr_unicode() { + let data = &[0xf0, 0x9f, 0xa6, 0x80, 0x00]; // "🦀\0" + let mut parser = Parser::new(data); + assert_eq!(parser.read_cstr().unwrap(), "🦀"); + } + + #[test] + fn test_element_type_all_valid() { + let valid_types = [ + (1, ElementType::Double), + (2, ElementType::String), + (3, ElementType::Document), + (4, ElementType::Array), + (5, ElementType::Binary), + (6, ElementType::Undefined), + (7, ElementType::ObjectId), + (8, ElementType::Boolean), + (9, ElementType::DatetimeUtc), + (10, ElementType::Null), + (16, ElementType::Int32), + (17, ElementType::Timestamp), + (18, ElementType::Int64), + ]; + + for (byte, expected) in valid_types { + let data = [byte]; + let mut parser = Parser::new(&data); + let result = parser.read_element_type().unwrap(); + assert_eq!(result as u8, expected as u8); + } + } + + #[test] + fn test_element_type_invalid() { + let invalid_types = [0, 11, 12, 13, 14, 15, 19, 20, 99, 255]; + + for invalid_type in invalid_types { + let data = [invalid_type]; + let mut parser = Parser::new(&data); + let result = parser.read_element_type(); + assert!(result.is_err()); + } + } + + #[test] + fn test_document_scope_minimum_size() { + // Minimum valid document: 5 bytes total + let data = &[0x05, 0x00, 0x00, 0x00, 0x00]; + let mut parser = Parser::new(data); + let sub_parser = parser.document_scope().unwrap(); + assert_eq!(sub_parser.remaining().len(), 1); // Just the terminator + } + + #[test] + fn test_document_scope_invalid_size() { + // Document claiming size < 5 + let data = &[0x04, 0x00, 0x00, 0x00]; + let mut parser = Parser::new(data); + assert!(parser.document_scope().is_err()); + } + + #[test] + fn test_binary_data_empty() { + // Binary with length 0, subtype 0 + let data = &[0x00, 0x00, 0x00, 0x00, 0x00]; + let mut parser = Parser::new(data); + let (subtype, binary) = parser.read_binary().unwrap(); + assert_eq!(subtype.0, 0); + assert_eq!(binary.len(), 0); + } + + #[test] + fn test_binary_data_with_content() { + // Binary with length 3, subtype 5, content [1,2,3] + let data = &[0x03, 0x00, 0x00, 0x00, 0x05, 0x01, 0x02, 0x03]; + let mut parser = Parser::new(data); + let (subtype, binary) = parser.read_binary().unwrap(); + assert_eq!(subtype.0, 5); + assert_eq!(binary, &[1, 2, 3]); + } + + #[test] + fn test_object_id_exact_size() { + let data = &[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c]; + let mut parser = Parser::new(data); + let oid = parser.read_object_id().unwrap(); + assert_eq!(oid, data); + } + + #[test] + fn test_advance_checked_boundary() { + let data = &[0x01, 0x02, 0x03]; + let mut parser = Parser::new(data); + + // Should succeed + assert!(parser.advance_checked(3).is_ok()); + assert_eq!(parser.remaining().len(), 0); + + // Should fail - no more data + assert!(parser.advance_checked(1).is_err()); + } + + #[test] + fn test_end_document_valid() { + let data = &[0x00]; + let mut parser = Parser::new(data); + assert_eq!(parser.end_document().unwrap(), true); + assert_eq!(parser.remaining().len(), 0); + } + + #[test] + fn test_end_document_invalid_terminator() { + let data = &[0x01]; + let mut parser = Parser::new(data); + assert!(parser.end_document().is_err()); + } + + #[test] + fn test_end_document_not_at_end() { + let data = &[0x01, 0x02, 0x03]; + let mut parser = Parser::new(data); + assert_eq!(parser.end_document().unwrap(), false); + } + + // Error boundary tests + + #[test] + fn test_unexpected_eof_int32() { + let data = &[0x01, 0x02]; // Only 2 bytes, need 4 + let mut parser = Parser::new(data); + assert!(parser.read_int32().is_err()); + } + + #[test] + fn test_unexpected_eof_int64() { + let data = &[0x01, 0x02, 0x03, 0x04]; // Only 4 bytes, need 8 + let mut parser = Parser::new(data); + assert!(parser.read_int64().is_err()); + } + + #[test] + fn test_unexpected_eof_double() { + let data = &[0x01, 0x02, 0x03, 0x04]; // Only 4 bytes, need 8 + let mut parser = Parser::new(data); + assert!(parser.read_double().is_err()); + } + + #[test] + fn test_unexpected_eof_object_id() { + let data = &[0x01, 0x02, 0x03, 0x04]; // Only 4 bytes, need 12 + let mut parser = Parser::new(data); + assert!(parser.read_object_id().is_err()); + } + + #[test] + fn test_string_length_overflow() { + // Invalid negative length + let data = &[0xff, 0xff, 0xff, 0xff, 0x00]; + let mut parser = Parser::new(data); + assert!(parser.read_string().is_err()); + } + + #[test] + fn test_string_insufficient_data() { + // Claims length 10 but only has 5 bytes total + let data = &[0x0a, 0x00, 0x00, 0x00, 0x00]; + let mut parser = Parser::new(data); + assert!(parser.read_string().is_err()); + } + + #[test] + fn test_binary_length_overflow() { + // Invalid negative length + let data = &[0xff, 0xff, 0xff, 0xff, 0x00]; + let mut parser = Parser::new(data); + assert!(parser.read_binary().is_err()); + } + + #[test] + fn test_binary_insufficient_data() { + // Claims length 10 but only has 2 bytes after subtype + let data = &[0x0a, 0x00, 0x00, 0x00, 0x05, 0x01, 0x02]; + let mut parser = Parser::new(data); + assert!(parser.read_binary().is_err()); + } + + #[test] + fn test_cstr_unterminated() { + let data = &[0x48, 0x65, 0x6c, 0x6c, 0x6f]; // "Hello" without null terminator + let mut parser = Parser::new(data); + assert!(parser.read_cstr().is_err()); + } + + #[test] + fn test_invalid_utf8_string() { + // Invalid UTF-8 sequence in string + let data = &[0x05, 0x00, 0x00, 0x00, 0xff, 0xfe, 0xfd, 0xfc, 0x00]; + let mut parser = Parser::new(data); + assert!(parser.read_string().is_err()); + } + + #[test] + fn test_invalid_utf8_cstr() { + // Invalid UTF-8 sequence in cstring + let data = &[0xff, 0xfe, 0xfd, 0xfc, 0x00]; + let mut parser = Parser::new(data); + assert!(parser.read_cstr().is_err()); + } +} + #[repr(transparent)] pub struct BinarySubtype(pub u8);