From 73b4b0a50cf420f214c0b97ddefec44032f4758d Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 14 Feb 2026 18:03:37 +0100 Subject: [PATCH] improve the code coverage --- src/items/builder.rs | 143 +++++++++++++++++++++++++++++++++++++++++ src/items/primitive.rs | 61 ++++++++++++++++++ 2 files changed, 204 insertions(+) diff --git a/src/items/builder.rs b/src/items/builder.rs index c9778bf..3b07699 100644 --- a/src/items/builder.rs +++ b/src/items/builder.rs @@ -333,3 +333,146 @@ impl TryFrom> for DateTimeBuilder { Ok(builder) } } + +#[cfg(test)] +mod tests { + use super::*; + + // Helper functions to create test items by parsing + fn timestamp() -> epoch::Timestamp { + let mut input = "@1234567890"; + epoch::parse(&mut input).unwrap() + } + + fn date() -> date::Date { + let mut input = "2023-06-15"; + date::parse(&mut input).unwrap() + } + + fn time() -> time::Time { + let mut input = "12:30:00"; + time::parse(&mut input).unwrap() + } + + fn time_with_offset() -> time::Time { + let mut input = "12:30:00+05:00"; + time::parse(&mut input).unwrap() + } + + fn weekday() -> weekday::Weekday { + let mut input = "monday"; + weekday::parse(&mut input).unwrap() + } + + fn offset() -> offset::Offset { + let mut input = "+05:00"; + offset::timezone_offset(&mut input).unwrap() + } + + fn relative() -> relative::Relative { + let mut input = "1 day"; + relative::parse(&mut input).unwrap() + } + + fn timezone() -> jiff::tz::TimeZone { + jiff::tz::TimeZone::UTC + } + + #[test] + fn test_duplicate_items_errors() { + let test_cases = vec![ + ( + vec![Item::TimeZone(timezone()), Item::TimeZone(timezone())], + "timezone rule cannot appear more than once", + ), + ( + vec![Item::Timestamp(timestamp()), Item::Timestamp(timestamp())], + "timestamp cannot appear more than once", + ), + ( + vec![Item::Date(date()), Item::Date(date())], + "date cannot appear more than once", + ), + ( + vec![Item::Time(time()), Item::Time(time())], + "time cannot appear more than once", + ), + ( + vec![Item::Weekday(weekday()), Item::Weekday(weekday())], + "weekday cannot appear more than once", + ), + ( + vec![Item::Offset(offset()), Item::Offset(offset())], + "time offset cannot appear more than once", + ), + ]; + + for (items, expected_err) in test_cases { + let result = DateTimeBuilder::try_from(items); + assert_eq!(result.unwrap_err(), expected_err); + } + } + + #[test] + fn test_timestamp_cannot_be_combined_with_other_items() { + let test_cases = vec![ + vec![Item::Date(date()), Item::Timestamp(timestamp())], + vec![Item::Time(time()), Item::Timestamp(timestamp())], + vec![Item::Weekday(weekday()), Item::Timestamp(timestamp())], + vec![Item::Offset(offset()), Item::Timestamp(timestamp())], + vec![Item::Relative(relative()), Item::Timestamp(timestamp())], + vec![Item::Timestamp(timestamp()), Item::Date(date())], + vec![Item::Timestamp(timestamp()), Item::Time(time())], + vec![Item::Timestamp(timestamp()), Item::Weekday(weekday())], + vec![Item::Timestamp(timestamp()), Item::Relative(relative())], + vec![Item::Timestamp(timestamp()), Item::Offset(offset())], + vec![Item::Timestamp(timestamp()), Item::Pure("2023".to_string())], + ]; + + for items in test_cases { + let result = DateTimeBuilder::try_from(items); + assert_eq!( + result.unwrap_err(), + "timestamp cannot be combined with other date/time items" + ); + } + } + + #[test] + fn test_time_offset_conflicts() { + // Time with offset followed by separate Offset item + let items1 = vec![Item::Time(time_with_offset()), Item::Offset(offset())]; + assert_eq!( + DateTimeBuilder::try_from(items1).unwrap_err(), + "time offset cannot appear more than once" + ); + + // Offset item followed by Time with offset + let items2 = vec![Item::Offset(offset()), Item::Time(time_with_offset())]; + assert_eq!( + DateTimeBuilder::try_from(items2).unwrap_err(), + "time offset and timezone are mutually exclusive" + ); + } + + #[test] + fn test_valid_combination_date_time() { + let items = vec![Item::Date(date()), Item::Time(time())]; + let result = DateTimeBuilder::try_from(items); + assert!(result.is_ok()); + } + + #[test] + fn test_valid_combination_date_weekday() { + let items = vec![Item::Date(date()), Item::Weekday(weekday())]; + let result = DateTimeBuilder::try_from(items); + assert!(result.is_ok()); + } + + #[test] + fn test_valid_timestamp_alone() { + let items = vec![Item::Timestamp(timestamp())]; + let result = DateTimeBuilder::try_from(items); + assert!(result.is_ok()); + } +} diff --git a/src/items/primitive.rs b/src/items/primitive.rs index d4e13b3..ba7d32e 100644 --- a/src/items/primitive.rs +++ b/src/items/primitive.rs @@ -142,3 +142,64 @@ pub(super) fn ctx_err(reason: &'static str) -> ContextError { err.push(StrContext::Expected(StrContextValue::Description(reason))); err } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_dec_int() { + for (input, expected) in [ + ("123", 123), // positive without sign + ("+123", 123), // positive with '+' sign + ("-123", -123), // negative with '-' sign + ("0", 0), // zero + ("+0", 0), // zero with '+' sign + ("-0", 0), // zero with '-' sign (parses as 0) + ("012", 12), // zero-prefixed (the main reason for this function) + ("+012", 12), // zero-prefixed with '+' sign + ("-012", -12), // zero-prefixed with '-' sign + ("00123", 123), // multiple leading zeros + ("2147483647", 2147483647), // i32::MAX + ("-2147483648", -2147483648), // i32::MIN + ] { + let mut s = input; + assert_eq!( + dec_int::(&mut s).unwrap(), + expected, + "{input}" + ); + } + + for input in [ + "", // empty string + "+", // sign without digits + "-", // sign without digits + "abc", // non-numeric + "12a", // starts with digits but has non-digit after (but should parse "12" successfully) + ] { + let mut s = input; + let result = dec_int::(&mut s); + // Note: "12a" will actually succeed and parse "12", leaving "a" unparsed + if input == "12a" { + assert_eq!(result.unwrap(), 12, "{input}"); + assert_eq!(s, "a", "Should leave 'a' unparsed"); + } else { + assert!(result.is_err(), "{input} should fail"); + } + } + + // Test overflow cases + for input in [ + "2147483648", // i32::MAX + 1 + "-2147483649", // i32::MIN - 1 + "99999999999", // way too large + ] { + let mut s = input; + assert!( + dec_int::(&mut s).is_err(), + "{input} should overflow" + ); + } + } +}