use http_date::{Date, DateTime, DayName, HttpDate, Time, decode, encode};
use proptest::prelude::*;
fn dayname() -> impl Strategy<Value = DayName> {
prop_oneof![
Just(DayName::Mon),
Just(DayName::Tue),
Just(DayName::Wed),
Just(DayName::Thu),
Just(DayName::Fri),
Just(DayName::Sat),
Just(DayName::Sun),
]
}
fn datetime(four_digit_year: bool) -> impl Strategy<Value = DateTime> {
let year = if four_digit_year {
0..10_000i32
} else {
0..100i32
};
(
dayname(),
year,
1..13i32, 1..29i32, 0..24i32, 0..60i32, 0..60i32, )
.prop_map(
|(dayname, year, month, day, hour, minute, second)| DateTime {
dayname,
date: Date::new(year, month, day).expect("generator ranges are valid"),
time: Time::new(hour, minute, second).expect("generator ranges are valid"),
},
)
}
proptest! {
#[test]
fn decode_never_panics(input in any::<String>()) {
let _ = decode(&input);
}
#[test]
fn decode_encode_decode_is_stable(input in any::<String>()) {
if let Ok(date) = decode(&input) {
let s = encode(&date);
prop_assert_eq!(
decode(&s),
Ok(date),
"encode({:?}) produced {:?}, which did not round-trip",
input,
s
);
}
}
#[test]
fn imf_fixdate_round_trips(dt in datetime(true)) {
let date = HttpDate::imf_fixdate(dt);
prop_assert_eq!(decode(&encode(&date)), Ok(date));
}
#[test]
fn rfc850_round_trips(dt in datetime(false)) {
let date = HttpDate::rfc850(dt).unwrap();
prop_assert_eq!(decode(&encode(&date)), Ok(date));
}
#[test]
fn asctime_round_trips(dt in datetime(true)) {
let date = HttpDate::asctime(dt);
prop_assert_eq!(decode(&encode(&date)), Ok(date));
}
}
fn dt(
dayname: DayName,
year: i32,
month: i32,
day: i32,
hour: i32,
minute: i32,
second: i32,
) -> DateTime {
DateTime {
dayname,
date: Date::new(year, month, day).expect("valid date"),
time: Time::new(hour, minute, second).expect("valid time"),
}
}
#[test]
fn corpus_decodes_and_round_trips() {
let cases: &[(&str, HttpDate)] = &[
(
"Sun, 06 Nov 1994 08:49:37 GMT",
HttpDate::imf_fixdate(dt(DayName::Sun, 1994, 11, 6, 8, 49, 37)),
),
(
"Sunday, 06-Nov-94 08:49:37 GMT",
HttpDate::rfc850(dt(DayName::Sun, 94, 11, 6, 8, 49, 37)).unwrap(),
),
(
"Sun Nov 6 08:49:37 1994",
HttpDate::asctime(dt(DayName::Sun, 1994, 11, 6, 8, 49, 37)),
),
(
"Sun Nov 16 08:49:37 1994",
HttpDate::asctime(dt(DayName::Sun, 1994, 11, 16, 8, 49, 37)),
),
(
"Mon, 01 Jan 2000 00:00:00 GMT",
HttpDate::imf_fixdate(dt(DayName::Mon, 2000, 1, 1, 0, 0, 0)),
),
(
"Saturday, 01-Jan-00 00:00:00 GMT",
HttpDate::rfc850(dt(DayName::Sat, 0, 1, 1, 0, 0, 0)).unwrap(),
),
(
"Fri Dec 31 23:59:59 9999",
HttpDate::asctime(dt(DayName::Fri, 9999, 12, 31, 23, 59, 59)),
),
(
"Tue, 15 Mar 2022 12:30:00 GMT",
HttpDate::imf_fixdate(dt(DayName::Tue, 2022, 3, 15, 12, 30, 0)),
),
(
"Wednesday, 25-Dec-99 18:00:00 GMT",
HttpDate::rfc850(dt(DayName::Wed, 99, 12, 25, 18, 0, 0)).unwrap(),
),
(
"Thu Jan 10 06:15:45 2008",
HttpDate::asctime(dt(DayName::Thu, 2008, 1, 10, 6, 15, 45)),
),
];
for (input, expected) in cases {
assert_eq!(&decode(input).unwrap(), expected, "decode({input:?})");
let s = encode(expected);
assert_eq!(&decode(&s).unwrap(), expected, "round-trip of {input:?}");
}
}
#[test]
fn asctime_single_digit_day_padding() {
let cases = [
"Sun Nov 1 00:00:00 2000",
"Sun Nov 9 00:00:00 2000",
"Sun Nov 10 00:00:00 2000",
"Sun Nov 28 00:00:00 2000",
];
for input in cases {
let date = decode(input).unwrap();
assert_eq!(
decode(&encode(&date)).unwrap(),
date,
"round-trip of {input:?}"
);
}
}
#[test]
fn malformed_inputs_are_rejected() {
let cases = [
"",
"XXX, 06 Nov 1994 08:49:37 GMT",
"Sun, 06 Xxx 1994 08:49:37 GMT",
"Sun, 06 Nov 1994 08:49:37 PST",
"Sun, 6 Nov 1994 08:49:37 GMT",
"Sunday, 06-Nov-1994 08:49:37 GMT",
"Sun, 06 Nov 1994",
"not a date",
"Sun Nov 6 08:49:37",
"Sunday, 06-Nov-94",
"Sun, 32 Nov 1994 08:49:37 GMT",
"Sun, 06 Nov 1994 24:00:00 GMT",
"Sun, 06 Nov 1994 08:60:00 GMT",
];
for input in cases {
assert!(decode(input).is_err(), "expected {input:?} to be rejected");
}
}