Add basic top-level tests
This starts to address #14 but didn't turn into a full MR yet. * Use crates `assert_float_eq` and `assert_matches` for extra assertions * Split off a function to build a Rocket `rocket()` that can be used in the tests
This commit is contained in:
parent
738409c3a8
commit
32964cea21
|
@ -41,6 +41,18 @@ dependencies = [
|
|||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "assert_float_eq"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cea652ffbedecf29e9cd41bb4c066881057a42c0c119040f022802b26853e77"
|
||||
|
||||
[[package]]
|
||||
name = "assert_matches"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9"
|
||||
|
||||
[[package]]
|
||||
name = "async-mutex"
|
||||
version = "1.4.0"
|
||||
|
@ -2142,6 +2154,8 @@ dependencies = [
|
|||
name = "sinoptik"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"assert_float_eq",
|
||||
"assert_matches",
|
||||
"cached",
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
|
|
|
@ -22,6 +22,10 @@ image = "0.24.1"
|
|||
reqwest = { version = "0.11.9", features = ["json"] }
|
||||
rocket = { version = "0.5.0-rc.1", features = ["json"] }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_float_eq = "1.1.3"
|
||||
assert_matches = "1.5.0"
|
||||
|
||||
[package.metadata.deb]
|
||||
maintainer = "Paul van Tilburg <paul@luon.net>"
|
||||
copyright = "2022, Paul van Tilburg"
|
||||
|
|
191
src/main.rs
191
src/main.rs
|
@ -14,7 +14,7 @@ use rocket::http::ContentType;
|
|||
use rocket::response::content::Custom;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::tokio::{self, select};
|
||||
use rocket::{get, routes, State};
|
||||
use rocket::{get, routes, Build, Rocket, State};
|
||||
|
||||
pub(crate) use self::forecast::Metric;
|
||||
use self::forecast::{forecast, Forecast};
|
||||
|
@ -85,6 +85,14 @@ async fn map_geo(
|
|||
image_data.map(|id| Custom(ContentType::PNG, id))
|
||||
}
|
||||
|
||||
/// Sets up Rocket.
|
||||
fn rocket(maps_handle: MapsHandle) -> Rocket<Build> {
|
||||
rocket::build().manage(maps_handle).mount(
|
||||
"/",
|
||||
routes![forecast_address, forecast_geo, map_address, map_geo],
|
||||
)
|
||||
}
|
||||
|
||||
/// Starts the main maps refresh loop and sets up and launches Rocket.
|
||||
///
|
||||
/// See [`maps::run`] for the maps refresh loop.
|
||||
|
@ -96,14 +104,7 @@ async fn main() -> Result<()> {
|
|||
let maps_handle = Arc::new(Mutex::new(maps));
|
||||
let maps_updater = tokio::spawn(maps::run(Arc::clone(&maps_handle)));
|
||||
|
||||
let rocket = rocket::build()
|
||||
.manage(maps_handle)
|
||||
.mount(
|
||||
"/",
|
||||
routes![forecast_address, forecast_geo, map_address, map_geo],
|
||||
)
|
||||
.ignite()
|
||||
.await?;
|
||||
let rocket = rocket(maps_handle).ignite().await?;
|
||||
let shutdown = rocket.shutdown();
|
||||
|
||||
select! {
|
||||
|
@ -118,3 +119,175 @@ async fn main() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use assert_float_eq::*;
|
||||
use assert_matches::assert_matches;
|
||||
use image::{DynamicImage, Rgba, RgbaImage};
|
||||
use rocket::http::Status;
|
||||
use rocket::local::blocking::Client;
|
||||
use rocket::serde::json::Value as JsonValue;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn maps_stub(map_count: u32) -> DynamicImage {
|
||||
let map_color = Rgba::from([73, 218, 33, 255]); // First color from map key.
|
||||
|
||||
DynamicImage::ImageRgba8(RgbaImage::from_pixel(820 * map_count, 988, map_color))
|
||||
}
|
||||
|
||||
fn maps_handle_stub() -> MapsHandle {
|
||||
let mut maps = Maps::new();
|
||||
maps.pollen = Some(maps_stub(24));
|
||||
maps.uvi = Some(maps_stub(5));
|
||||
|
||||
Arc::new(Mutex::new(maps))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forecast_address() {
|
||||
let maps_handle = maps_handle_stub();
|
||||
let client = Client::tracked(rocket(maps_handle)).expect("Not a valid Rocket instance");
|
||||
|
||||
// Get an empty forecast for the provided address.
|
||||
let response = client.get("/forecast?address=eindhoven").dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
let json = response.into_json::<JsonValue>().expect("Not valid JSON");
|
||||
assert_f64_near!(json["lat"].as_f64().unwrap(), 51.4392648);
|
||||
assert_f64_near!(json["lon"].as_f64().unwrap(), 5.478633);
|
||||
assert_matches!(json["time"], JsonValue::Number(_));
|
||||
assert_matches!(json.get("AQI"), None);
|
||||
assert_matches!(json.get("NO2"), None);
|
||||
assert_matches!(json.get("O3"), None);
|
||||
assert_matches!(json.get("PAQI"), None);
|
||||
assert_matches!(json.get("PM10"), None);
|
||||
assert_matches!(json.get("pollen"), None);
|
||||
assert_matches!(json.get("precipitation"), None);
|
||||
assert_matches!(json.get("UVI"), None);
|
||||
|
||||
// Get a forecast with all metrics for the provided address.
|
||||
let response = client
|
||||
.get("/forecast?address=eindhoven&metrics=all")
|
||||
.dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
let json = response.into_json::<JsonValue>().expect("Not valid JSON");
|
||||
assert_f64_near!(json["lat"].as_f64().unwrap(), 51.4392648);
|
||||
assert_f64_near!(json["lon"].as_f64().unwrap(), 5.478633);
|
||||
assert_matches!(json["time"], JsonValue::Number(_));
|
||||
assert_matches!(json.get("AQI"), Some(JsonValue::Array(_)));
|
||||
assert_matches!(json.get("NO2"), Some(JsonValue::Array(_)));
|
||||
assert_matches!(json.get("O3"), Some(JsonValue::Array(_)));
|
||||
assert_matches!(json.get("PAQI"), Some(JsonValue::Array(_)));
|
||||
assert_matches!(json.get("PM10"), Some(JsonValue::Array(_)));
|
||||
assert_matches!(json.get("pollen"), Some(JsonValue::Array(_)));
|
||||
assert_matches!(json.get("precipitation"), Some(JsonValue::Array(_)));
|
||||
assert_matches!(json.get("UVI"), Some(JsonValue::Array(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forecast_geo() {
|
||||
let maps_handle = maps_handle_stub();
|
||||
let client = Client::tracked(rocket(maps_handle)).expect("valid Rocket instance");
|
||||
|
||||
// Get an empty forecast for the geocoded location.
|
||||
let response = client.get("/forecast?lat=51.4&lon=5.5").dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
let json = response.into_json::<JsonValue>().expect("Not valid JSON");
|
||||
assert_f64_near!(json["lat"].as_f64().unwrap(), 51.4);
|
||||
assert_f64_near!(json["lon"].as_f64().unwrap(), 5.5);
|
||||
assert_matches!(json["time"], JsonValue::Number(_));
|
||||
assert_matches!(json.get("AQI"), None);
|
||||
assert_matches!(json.get("NO2"), None);
|
||||
assert_matches!(json.get("O3"), None);
|
||||
assert_matches!(json.get("PAQI"), None);
|
||||
assert_matches!(json.get("PM10"), None);
|
||||
assert_matches!(json.get("pollen"), None);
|
||||
assert_matches!(json.get("precipitation"), None);
|
||||
assert_matches!(json.get("UVI"), None);
|
||||
|
||||
// Get a forecast with all metrics for the geocoded location.
|
||||
let response = client
|
||||
.get("/forecast?lat=51.4&lon=5.5&metrics=all")
|
||||
.dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
let json = response.into_json::<JsonValue>().expect("Not valid JSON");
|
||||
assert_f64_near!(json["lat"].as_f64().unwrap(), 51.4);
|
||||
assert_f64_near!(json["lon"].as_f64().unwrap(), 5.5);
|
||||
assert_matches!(json["time"], JsonValue::Number(_));
|
||||
assert_matches!(json.get("AQI"), Some(JsonValue::Array(_)));
|
||||
assert_matches!(json.get("NO2"), Some(JsonValue::Array(_)));
|
||||
assert_matches!(json.get("O3"), Some(JsonValue::Array(_)));
|
||||
assert_matches!(json.get("PAQI"), Some(JsonValue::Array(_)));
|
||||
assert_matches!(json.get("PM10"), Some(JsonValue::Array(_)));
|
||||
assert_matches!(json.get("pollen"), Some(JsonValue::Array(_)));
|
||||
assert_matches!(json.get("precipitation"), Some(JsonValue::Array(_)));
|
||||
assert_matches!(json.get("UVI"), Some(JsonValue::Array(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_address() {
|
||||
let maps_handle = Arc::new(Mutex::new(Maps::new()));
|
||||
let maps_handle_clone = Arc::clone(&maps_handle);
|
||||
let client = Client::tracked(rocket(maps_handle)).expect("Not a valid Rocket instance");
|
||||
|
||||
// No maps available yet.
|
||||
let response = client
|
||||
.get("/map?address=eindhoven&metric=pollen")
|
||||
.dispatch();
|
||||
assert_eq!(response.status(), Status::NotFound);
|
||||
|
||||
// Load some dummy map.
|
||||
let mut maps = maps_handle_clone
|
||||
.lock()
|
||||
.expect("Maps handle mutex was poisoned");
|
||||
maps.pollen = Some(maps_stub(24));
|
||||
drop(maps);
|
||||
|
||||
// There should be a map now.
|
||||
let response = client
|
||||
.get("/map?address=eindhoven&metric=pollen")
|
||||
.dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
assert_eq!(response.content_type(), Some(ContentType::PNG));
|
||||
|
||||
// ... but not if it is out of bounds.
|
||||
let response = client.get("/map?address=berlin&metric=pollen").dispatch();
|
||||
assert_eq!(response.status(), Status::NotFound);
|
||||
|
||||
// No metric selected, don't know which map to show?
|
||||
let response = client.get("/map?address=eindhoven").dispatch();
|
||||
assert_eq!(response.status(), Status::NotFound);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_geo() {
|
||||
let maps_handle = Arc::new(Mutex::new(Maps::new()));
|
||||
let maps_handle_clone = Arc::clone(&maps_handle);
|
||||
let client = Client::tracked(rocket(maps_handle)).expect("Not a valid Rocket instance");
|
||||
|
||||
// No metric passed, don't know which map to show?
|
||||
let response = client.get("/map?lat=51.4&lon=5.5").dispatch();
|
||||
assert_eq!(response.status(), Status::NotFound);
|
||||
|
||||
// No maps available yet.
|
||||
let response = client.get("/map?lat=51.4&lon=5.5&metric=pollen").dispatch();
|
||||
assert_eq!(response.status(), Status::NotFound);
|
||||
|
||||
// Load some dummy map.
|
||||
let mut maps = maps_handle_clone
|
||||
.lock()
|
||||
.expect("Maps handle mutex was poisoned");
|
||||
maps.pollen = Some(maps_stub(24));
|
||||
drop(maps);
|
||||
|
||||
// There should be a map now.
|
||||
let response = client.get("/map?lat=51.4&lon=5.5&metric=pollen").dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
assert_eq!(response.content_type(), Some(ContentType::PNG));
|
||||
|
||||
// No metric passed, don't know which map to show?
|
||||
let response = client.get("/map?lat=51.4&lon=5.5").dispatch();
|
||||
assert_eq!(response.status(), Status::NotFound);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue