use jiff::{
Timestamp,
civil::{Date, Time},
};
use proptest::property_test;
use crate::{
Client, parser,
types::{
Conditions,
params::{GetOutputParams, GetStatisticsParams, GetSystemParams},
regions::Australia,
response::{
AggregateDate, ExtendedData, History, Insolation, Ladder, Statistics, Status, Supply, System, Tariffs, Team,
TeamOutput,
},
},
};
#[test]
fn status() {
let mut response = Client::reader(include_str!("../../../cases/get_status.csv"));
let status: Status = response.deserialize().next().unwrap().unwrap();
assert_eq!(status.date, Date::constant(2025, 9, 13));
assert_eq!(status.time, Time::constant(17, 15, 0, 0));
assert_eq!(status.extended_value_2, Some(2.0));
assert!(status.voltage.is_some_and(|v| (234.6..=234.8).contains(&v)));
}
#[test]
fn statistics() {
let statistics =
parser::statistics(include_str!("../../../cases/get_statistics.csv"), &GetStatisticsParams::default()).unwrap();
validate_statistics(&statistics);
assert_eq!(statistics.consumption, None);
assert_eq!(statistics.credit, None);
}
#[test]
fn statistics_with_consumption() {
let statistics = parser::statistics(
include_str!("../../../cases/get_statistics_consumption.csv"),
&GetStatisticsParams {
consumption: true,
..Default::default()
},
)
.unwrap();
validate_statistics(&statistics);
assert_eq!(statistics.consumption.unwrap().energy_consumed, 10800);
assert_eq!(statistics.credit, None);
}
#[test]
fn statistics_with_credit_and_debit() {
let statistics = parser::statistics(
include_str!("../../../cases/get_statistics_credit_and_debit.csv"),
&GetStatisticsParams {
credit_and_debit: true,
..Default::default()
},
)
.unwrap();
validate_statistics(&statistics);
assert_eq!(statistics.consumption, None);
assert!((12.2..12.4).contains(&statistics.credit.unwrap()));
}
#[test]
fn statistics_full() {
let statistics = parser::statistics(
include_str!("../../../cases/get_statistics_full.csv"),
&GetStatisticsParams {
consumption: true,
credit_and_debit: true,
..Default::default()
},
)
.unwrap();
validate_statistics(&statistics);
assert_eq!(statistics.consumption.unwrap().energy_consumed, 10800);
assert!((12.2..12.4).contains(&statistics.credit.unwrap()));
}
#[test]
fn system() {
let system = parser::system(include_str!("../../../cases/get_system.csv"), &GetSystemParams::default()).unwrap();
validate_system(&system.system);
assert_eq!(system.donations, 1);
assert_eq!(system.tariffs, Tariffs::default());
}
#[test]
fn system_tariffs() {
let system = parser::system(
include_str!("../../../cases/get_system_tariffs.csv"),
&GetSystemParams {
tariffs: true,
..Default::default()
},
)
.unwrap();
validate_system(&system.system);
assert_eq!(system.donations, 1);
assert_eq!(system.tariffs.export.map(f32::round), Some(1.0));
}
#[test]
fn ladder() {
let mut response = Client::reader(include_str!("../../../cases/get_ladder.csv"));
let ladder: Ladder = response.deserialize().next().unwrap().unwrap();
assert_eq!(ladder.date, Date::constant(2025, 9, 13));
assert_eq!(ladder.generation_rank, Some(27351));
assert_eq!(ladder.efficiency_rank, None);
}
#[test]
fn daily_status() {
let status = parser::daily_status(include_str!("../../../cases/get_status_stats.csv")).unwrap();
assert_eq!(status.peak_time, Time::constant(10, 45, 0, 0));
assert!(status.consumption.is_some());
let consumption = status.consumption.unwrap();
assert_eq!(consumption.standby_power_time, Some(Time::constant(4, 5, 0, 0)));
assert!(status.temperatures.is_some());
let temperatures = status.temperatures.unwrap();
assert!(temperatures.average_temperature.is_some_and(|t| (22.19..=22.21).contains(&t)));
}
#[test]
fn history_status() {
let csv = include_str!("../../../cases/get_status_history.csv").replace(';', "\n");
let mut response = Client::reader(&csv);
let history: Vec<History> = response.deserialize().collect::<Result<_, _>>().unwrap();
assert_eq!(history.len(), 30);
assert_eq!(history[0].time, Time::constant(11, 45, 0, 0));
}
#[test]
fn outputs() {
let outputs = parser::outputs(include_str!("../../../cases/get_outputs.csv"), &GetOutputParams::default()).unwrap();
assert_eq!(outputs.len(), 50);
assert_eq!(outputs[0].conditions, Conditions::NotSure);
assert_eq!(outputs[1].conditions, Conditions::MostlyCloudy);
}
#[test]
fn outputs_with_exports() {
let outputs = parser::outputs(
include_str!("../../../cases/get_outputs_exports.csv"),
&GetOutputParams {
time_of_export: true,
..Default::default()
},
)
.unwrap();
assert_eq!(outputs.len(), 50);
assert_eq!(outputs[0].conditions, Conditions::MostlyCloudy);
assert!(outputs[0].export.is_some());
}
#[test]
fn outputs_with_insolation() {
let outputs = parser::outputs(
include_str!("../../../cases/get_outputs_insolation.csv"),
&GetOutputParams {
insolation: true,
..Default::default()
},
)
.unwrap();
assert_eq!(outputs.len(), 50);
assert_eq!(outputs[0].conditions, Conditions::MostlyCloudy);
assert_eq!(outputs[0].insolation, Some(2_680_812));
}
#[test]
fn outputs_full() {
let outputs = parser::outputs(
include_str!("../../../cases/get_outputs_full.csv"),
&GetOutputParams {
insolation: true,
time_of_export: true,
..Default::default()
},
)
.unwrap();
assert_eq!(outputs.len(), 50);
assert_eq!(outputs[0].conditions, Conditions::MostlyCloudy);
assert_eq!(outputs[0].insolation, Some(2_680_812));
assert!(outputs[0].export.is_some());
}
#[test]
fn aggregated_output() {
let outputs = parser::aggregate_outputs(include_str!("../../../cases/get_outputs_aggregated.csv"), false).unwrap();
assert_eq!(outputs.len(), 50);
assert_eq!(outputs[0].date, AggregateDate::YearAndMonth(2025, 9));
assert_eq!(outputs[0].outputs, 14);
}
#[test]
fn aggregated_output_full() {
let outputs =
parser::aggregate_outputs(include_str!("../../../cases/get_outputs_aggregated_full.csv"), true).unwrap();
assert_eq!(outputs.len(), 2);
assert_eq!(outputs[0].date, AggregateDate::Year(2025));
assert_eq!(outputs[0].outputs, 258);
assert!(outputs[0].export.is_some());
assert_eq!(outputs[0].export.as_ref().unwrap().off_peak_energy_export, Some(0));
}
#[property_test]
#[allow(clippy::cast_possible_wrap)]
fn aggregated_date(#[strategy = 0..=9999u16] year: u16, #[strategy = 1..=12u8] month: u8, with_month: bool) {
let (string, expected) = if with_month {
(format!("{year:04}{month:02}"), AggregateDate::YearAndMonth(year, month))
} else {
(format!("{year:04}"), AggregateDate::Year(year))
};
let mut response = Client::reader(&string);
let actual = response.deserialize().next().unwrap().unwrap();
assert_eq!(expected, actual);
assert_eq!(expected.year(), year);
assert_eq!(
expected.as_arbitrary_date().unwrap(),
Date::new(year as i16, if with_month { month as i8 } else { 1 }, 1).unwrap()
);
}
#[test]
fn team_outputs() {
let csv = include_str!("../../../cases/get_outputs_team.csv").replace(';', "\n");
let mut response = Client::reader(&csv);
let outputs: Vec<TeamOutput> = response.deserialize().collect::<Result<_, _>>().unwrap();
assert_eq!(outputs[0].date, Date::constant(2025, 9, 14));
assert_eq!(outputs[0].outputs, 4);
}
#[test]
fn extended_data() {
let csv = include_str!("../../../cases/get_extended.csv").replace(';', "\n");
let mut response = Client::reader(&csv);
let extended: Vec<ExtendedData> = response.deserialize().collect::<Result<_, _>>().unwrap();
assert_eq!(extended.len(), 2);
assert_eq!(extended[0].date, Date::constant(2021, 2, 27));
}
#[test]
fn favourites() {
let favourites = parser::favourites(include_str!("../../../cases/get_favourites.csv")).unwrap();
assert_eq!(favourites.len(), 2);
validate_system(&favourites[0].system);
assert_eq!(favourites[1].system.inverter_brand, Some(String::from("Lun-a-matic")));
}
#[test]
fn missing() {
let mut response = Client::reader(include_str!("../../../cases/get_missing.csv"));
let missing: Vec<Date> = response.deserialize().next().unwrap().unwrap();
assert_eq!(missing.len(), 4);
assert_eq!(missing[0], Date::constant(2021, 1, 1));
}
#[test]
fn insolation() {
let csv = include_str!("../../../cases/get_insolation.csv").replace(';', "\n");
let mut response = Client::reader(&csv);
let insolation: Vec<Insolation> = response.deserialize().collect::<Result<_, _>>().unwrap();
assert_eq!(insolation.len(), 8);
assert_eq!(insolation[0].time, Time::constant(6, 0, 0, 0));
}
#[test]
fn team() {
let mut response = Client::semicolon_reader(include_str!("../../../cases/get_team.csv"));
let team: Team = response.deserialize().next().unwrap().unwrap();
assert_eq!(team.name, "PVOutput iOS app");
assert_eq!(team.size, 21_834_213);
assert_eq!(team.created, Date::constant(2014, 11, 12));
}
#[test]
fn regions() {
let csv = include_str!("../../../cases/get_supply.csv").replace(';', "\n");
let mut response = Client::reader(&csv);
let supplies: Vec<Supply> = response.deserialize().collect::<Result<_, _>>().unwrap();
assert_eq!(supplies.len(), 30);
assert_eq!(supplies[0].timestamp, Timestamp::constant(1_758_427_800, 0));
assert_eq!(supplies[0].region, Australia::Queensland.into());
}
#[test]
fn regions_qld() {
let csv = include_str!("../../../cases/get_supply_qld.csv").replace(';', "\n");
let mut response = Client::reader(&csv);
let supplies: Vec<Supply> = response.deserialize().collect::<Result<_, _>>().unwrap();
assert_eq!(supplies.len(), 144);
assert_eq!(supplies[0].timestamp, Timestamp::constant(1_760_542_200, 0));
assert_eq!(supplies[0].region, Australia::Queensland.into());
}
fn validate_statistics(statistics: &Statistics) {
assert_eq!(statistics.energy_generated, 11_353_459);
assert_eq!(statistics.energy_exported, 1_476_067);
assert_eq!(statistics.date_from, Date::constant(2010, 7, 5));
assert_eq!(statistics.date_to, Date::constant(2013, 2, 25));
}
fn validate_system(system: &System) {
assert_eq!(system.name, "Example System");
assert_eq!(system.size, 6600);
assert_eq!(system.panels, Some(14));
assert_eq!(system.panel_power, Some(400));
assert_eq!(system.brand, Some(String::from("Contoso Maximus")));
assert_eq!(system.tilt, Some(20.0));
}