use jiff::civil::{Date, Time};
use proptest::property_test;
use crate::{
parser::{parse_add_output_error, parse_add_status_error, parse_datetime_error, parse_post_system_error},
types::error::{AddOutputError, AddStatusError, DateTimeError, ExtendedDataField, PostSystemError},
};
#[test]
fn missing_data() {
assert_eq!(
parse_add_status_error("Invalid data format []"),
Some(AddStatusError::NoData),
"parse_add_status_error should return AddStatusError::NoData for an empty data format"
);
assert_eq!(
parse_add_status_error("Invalid data format [xyz]"),
Some(AddStatusError::InvalidDataFormat(String::from("xyz"))),
"parse_add_status_error should return AddStatusError::InvalidDataFormat(\"xyz\")"
);
}
#[test]
fn date_time_edge_cases() {
assert_eq!(
parse_datetime_error("Invalid date: [test]"),
Some(DateTimeError::InvalidDate(String::from("[test]")))
);
assert_eq!(parse_datetime_error("Invalid Date [test]"), Some(DateTimeError::InvalidDate(String::from("test"))));
assert_eq!(
parse_datetime_error("Invalid time: [test]"),
Some(DateTimeError::InvalidTime(String::from("[test]")))
);
assert_eq!(parse_datetime_error("Invalid Time [test]"), Some(DateTimeError::InvalidTime(String::from("test"))));
}
#[property_test]
#[test]
fn invalid_date(#[strategy = ".+"] date: String) {
let error = DateTimeError::InvalidDate(date.clone());
assert_eq!(parse_datetime_error(&format!("Date [{date}] invalid")), Some(error.clone()));
assert_eq!(parse_datetime_error(&format!("Invalid Date [{date}]")), Some(error.clone()));
assert_eq!(parse_datetime_error(&format!("Date is invalid [{date}]")), Some(error.clone()));
assert_eq!(parse_datetime_error(&format!("Invalid date: {date}")), Some(error));
}
#[property_test]
#[test]
fn invalid_time(#[strategy = ".+"] time: String) {
let error = DateTimeError::InvalidTime(time.clone());
assert_eq!(parse_datetime_error(&format!("Invalid Time [{time}]")), Some(error.clone()));
assert_eq!(parse_datetime_error(&format!("Time is invalid [{time}]")), Some(error.clone()));
assert_eq!(parse_datetime_error(&format!("Invalid time: {time}")), Some(error));
}
#[property_test]
#[test]
fn future_date(#[strategy = 0..=9999i16] year: i16, #[strategy = 1..=12i8] month: i8, #[strategy = 1..=28i8] day: i8) {
let date = Date::new(year, month, day).unwrap();
let error = DateTimeError::DateTooNew(date);
let date_str = format!("{year:04}{month:02}{day:02}");
assert_eq!(parse_datetime_error(&format!("Date [{date_str}] too new")), Some(error.clone()));
assert_eq!(parse_datetime_error(&format!("Invalid future date [{date_str}]")), Some(error.clone()));
assert_eq!(parse_datetime_error(&format!("Date is in the future [{date_str}]")), Some(error));
}
#[property_test]
#[test]
fn date_too_old(
#[strategy = 0..=9999i16] year: i16,
#[strategy = 1..=12i8] month: i8,
#[strategy = 1..=28i8] day: i8,
number: u64,
) {
let date = Date::new(year, month, day).unwrap();
let error = DateTimeError::DateTooOld(date);
let date_str = format!("{year:04}{month:02}{day:02}");
assert_eq!(parse_datetime_error(&format!("Date [{date_str}] too old")), Some(error.clone()));
assert_eq!(parse_datetime_error(&format!("Date is older than {number} days [{date_str}]")), Some(error));
}
#[property_test]
#[test]
fn missing_temperature(
which: bool,
#[strategy = 0..=9999i16] year: i16,
#[strategy = 1..=12i8] month: i8,
#[strategy = 1..=28i8] day: i8,
) {
let date = Date::new(year, month, day).unwrap();
let (which, error) = if which {
("Max", AddOutputError::MinTemperatureWithoutMax(date))
} else {
("Min", AddOutputError::MaxTemperatureWithoutMin(date))
};
assert_eq!(
parse_add_output_error(&format!("{which} temp missing on [{year:04}{month:02}{day:02}]")),
Some(error)
);
}
#[property_test]
#[test]
fn production_too_high(which: bool, value: u32, system_size: u32) {
let (which, error) = if which {
(
"Power",
AddStatusError::ExcessivePowerGeneration {
power: value,
system_size,
},
)
} else {
(
"Energy",
AddStatusError::ExcessiveEnergyGeneration {
energy: value,
system_size,
},
)
};
assert_eq!(
parse_add_status_error(&format!("{which} value [{value}] too high for system size [{system_size}]")),
Some(error)
);
}
#[property_test]
#[test]
fn energy_too_high(energy: u32, #[strategy = 1..=23i8] hour: i8, #[strategy = 0..=59i8] minute: i8) {
assert_eq!(
parse_add_status_error(&format!("Energy value [{energy}] too high for [{hour:02}:{minute:02}]")),
Some(AddStatusError::EnergyTooHighForTime(energy, Time::new(hour, minute, 0, 0).unwrap()))
);
}
#[property_test]
#[test]
fn invalid_data_format(#[strategy = ".+"] data: String) {
assert_eq!(
parse_add_status_error(&format!("Invalid data format [{data}]")),
Some(AddStatusError::InvalidDataFormat(data))
);
}
#[test]
fn generation_too_high() {
let error = include_str!("../../../cases/errors/generation_too_high.txt").trim();
assert_eq!(
parse_add_output_error(error.trim_start_matches("Bad request 400: ")),
Some(AddOutputError::GenerationTooHigh {
generation: 10_000_000_000.0,
system_size: 6200,
date: Date::constant(2025, 9, 22)
})
);
}
#[test]
fn invalid_colour() {
let error = include_str!("../../../cases/errors/invalid_extended_colour.txt").trim();
assert_eq!(
parse_post_system_error(error.trim_start_matches("Bad Request 400: ")),
Some(PostSystemError::InvalidExtendedDataField(7, ExtendedDataField::Colour))
);
}