use http::{HeaderMap, HeaderValue};
use jiff::Timestamp;
use proptest::property_test;
use crate::{
Client,
types::{
error::RateLimitError,
response::{RateLimit, SearchResult},
},
};
mod error;
mod get;
mod post;
const RATE_LIMIT_REMAINING: u16 = 200;
const RATE_LIMIT_LIMIT: u16 = 300;
const RATE_LIMIT_RESET: Timestamp = Timestamp::constant(1_474_243_200, 0);
#[test]
fn rate_limit() {
let headers = rate_limit_headers();
let rate_limit = RateLimit::try_from(&headers);
assert!(rate_limit.is_ok());
let rate_limit = rate_limit.unwrap();
assert_eq!(rate_limit.remaining, RATE_LIMIT_REMAINING);
assert_eq!(rate_limit.limit, RATE_LIMIT_LIMIT);
assert_eq!(rate_limit.reset, RATE_LIMIT_RESET);
assert_eq!(rate_limit.used(), RATE_LIMIT_LIMIT - RATE_LIMIT_REMAINING);
}
#[property_test]
#[test]
fn arbitrary_rate_limit(
a: u16,
b: u16,
#[strategy = Timestamp::MIN.as_second()..=Timestamp::MAX.as_second()] reset_seconds: i64,
) {
let remaining = a.min(b);
let limit = a.max(b);
let mut headers = HeaderMap::new();
headers.insert("X-Rate-Limit-Remaining", remaining.into());
headers.insert("X-Rate-Limit-Limit", limit.into());
headers.insert("X-Rate-Limit-Reset", reset_seconds.into());
let rate_limit = RateLimit::try_from(&headers);
assert!(rate_limit.is_ok());
let rate_limit = rate_limit.unwrap();
assert_eq!(rate_limit.remaining, remaining);
assert_eq!(rate_limit.limit, limit);
assert_eq!(rate_limit.reset, Timestamp::constant(reset_seconds, 0));
assert_eq!(rate_limit.used(), limit - remaining);
}
#[test]
fn invalid_rate_limit() {
let mut headers = rate_limit_headers();
headers.insert("X-Rate-Limit-Remaining", HeaderValue::from_str("\u{1F430}").unwrap());
let rate_limit = RateLimit::try_from(&headers);
assert!(matches!(rate_limit, Err(RateLimitError::NonAsciiHeader)));
headers.insert("X-Rate-Limit-Remaining", HeaderValue::from_str("xyzzy").unwrap());
let rate_limit = RateLimit::try_from(&headers);
assert!(matches!(rate_limit, Err(RateLimitError::InvalidInteger(_))));
let mut headers = rate_limit_headers();
headers.insert("X-Rate-Limit-Reset", (Timestamp::MAX.as_second() + 1).into());
let rate_limit = RateLimit::try_from(&headers);
assert!(matches!(rate_limit, Err(RateLimitError::InvalidTimestamp(_))));
headers.remove("X-Rate-Limit-Reset");
let rate_limit = RateLimit::try_from(&headers);
assert!(matches!(rate_limit, Err(RateLimitError::MissingValues)));
}
#[test]
fn search() {
let mut response = Client::reader(include_str!("../../../cases/search.csv"));
let results: Vec<SearchResult> = response.deserialize().collect::<Result<_, _>>().unwrap();
assert_eq!(results.len(), 7);
assert_eq!(results[0].name, "Albany Creek Enphase");
assert_eq!(results[0].last_output.map(|s| s.get_days()), Some(0));
assert_eq!(results[3].last_output.map(|s| s.get_days()), Some(1));
assert_eq!(results[4].last_output.map(|s| s.get_days()), Some(5));
assert_eq!(results[5].last_output.map(|s| s.get_days()), None);
assert_eq!(results[6].last_output.map(|s| s.get_weeks()), Some(524));
}
fn rate_limit_headers() -> HeaderMap {
let mut headers = HeaderMap::new();
headers.insert("X-Rate-Limit-Remaining", RATE_LIMIT_REMAINING.into());
headers.insert("X-Rate-Limit-Limit", RATE_LIMIT_LIMIT.into());
headers.insert("X-Rate-Limit-Reset", RATE_LIMIT_RESET.as_second().into());
headers
}