pub mod args;
use chrono::{TimeDelta, Utc};
use reqwest::{
header::{self, HeaderValue},
redirect::Policy,
StatusCode,
};
use serde_json::json;
use shuttlings::{SubmissionState, SubmissionUpdate};
use tokio::{
sync::mpsc::Sender,
time::{sleep, Duration},
};
use tracing::info;
use uuid::Uuid;
pub const SUPPORTED_CHALLENGES: &[&str] = &[
"-1", "2", "5", "9",
];
pub const SUBMISSION_TIMEOUT: u64 = 60;
pub async fn run(url: String, id: Uuid, number: &str, tx: Sender<SubmissionUpdate>) {
info!(%id, %url, %number, "Starting submission");
tx.send(SubmissionState::Running.into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
tokio::select! {
_ = validate(url.as_str(), number, tx.clone()) => (),
_ = sleep(Duration::from_secs(SUBMISSION_TIMEOUT)) => {
info!(%id, %url, %number, "Submission timed out");
tx.send("Timed out".to_owned().into()).await.unwrap();
tx.send(SubmissionState::Done.into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
},
};
info!(%id, %url, %number, "Completed submission");
}
type TaskTest = (i32, i32);
type ValidateResult = std::result::Result<(), TaskTest>;
pub async fn validate(url: &str, number: &str, tx: Sender<SubmissionUpdate>) {
let txc = tx.clone();
if let Err((task, test)) = match number {
"-1" => validate_minus1(url, txc).await,
"2" => validate_2(url, txc).await,
"5" => validate_5(url, txc).await,
"9" => validate_9(url, txc).await,
_ => {
tx.send(
format!("Validating Challenge {number} is not supported yet! Check for updates.")
.into(),
)
.await
.unwrap();
return;
}
} {
info!(%url, %number, %task, %test, "Submission failed");
tx.send(format!("Task {task}: test #{test} failed 🟥").into())
.await
.unwrap();
}
tx.send(SubmissionState::Done.into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
}
fn new_client() -> reqwest::Client {
reqwest::ClientBuilder::new()
.http1_only()
.connect_timeout(Duration::from_secs(3))
.redirect(Policy::limited(3))
.referer(false)
.timeout(Duration::from_secs(60))
.build()
.unwrap()
}
macro_rules! assert_status {
($res:expr, $test:expr, $expected_status:expr) => {
if $res.status() != $expected_status {
return Err($test);
}
};
}
macro_rules! assert_text {
($res:expr, $test:expr, $expected_text:expr) => {
if $res.text().await.map_err(|_| $test)? != $expected_text {
return Err($test);
}
};
}
macro_rules! assert_ {
($test:expr, $expected_true:expr) => {
if !$expected_true {
return Err($test);
}
};
}
async fn validate_minus1(base_url: &str, tx: Sender<SubmissionUpdate>) -> ValidateResult {
let client = new_client();
let mut test: TaskTest;
test = (1, 1);
let url = &format!("{}/", base_url);
let res = client.get(url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
assert_text!(res, test, "Hello, bird!");
tx.send((true, 0).into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
test = (2, 1);
let url = &format!("{}/-1/seek", base_url);
let client_no_redir = reqwest::ClientBuilder::new()
.http1_only()
.connect_timeout(Duration::from_secs(3))
.redirect(Policy::none())
.referer(false)
.timeout(Duration::from_secs(60))
.build()
.unwrap();
let res = client_no_redir.get(url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::FOUND);
if res.headers().get(header::LOCATION)
!= Some(&HeaderValue::from_static(
"https://www.youtube.com/watch?v=9Gc4QTqslN4",
))
{
return Err(test);
}
tx.send((false, 0).into()).await.unwrap();
Ok(())
}
async fn validate_2(base_url: &str, tx: Sender<SubmissionUpdate>) -> ValidateResult {
let client = new_client();
let mut test: TaskTest;
test = (1, 1);
let url = &format!("{}/2/dest?from=10.0.0.0&key=1.2.3.255", base_url);
let res = client.get(url).send().await.map_err(|_| test)?;
assert_text!(res, test, "11.2.3.255");
test = (1, 2);
let url = &format!("{}/2/dest?from=128.128.33.0&key=255.0.255.33", base_url);
let res = client.get(url).send().await.map_err(|_| test)?;
assert_text!(res, test, "127.128.32.33");
test = (1, 3);
let url = &format!("{}/2/dest?from=192.168.0.1&key=72.96.8.7", base_url);
let res = client.get(url).send().await.map_err(|_| test)?;
assert_text!(res, test, "8.8.8.8");
tx.send((false, 0).into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
test = (2, 1);
let url = &format!("{}/2/key?from=10.0.0.0&to=11.2.3.255", base_url);
let res = client.get(url).send().await.map_err(|_| test)?;
assert_text!(res, test, "1.2.3.255");
test = (2, 2);
let url = &format!("{}/2/key?from=128.128.33.0&to=127.128.32.33", base_url);
let res = client.get(url).send().await.map_err(|_| test)?;
assert_text!(res, test, "255.0.255.33");
test = (2, 3);
let url = &format!("{}/2/key?from=192.168.0.1&to=8.8.8.8", base_url);
let res = client.get(url).send().await.map_err(|_| test)?;
assert_text!(res, test, "72.96.8.7");
tx.send((true, 0).into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
test = (3, 1);
let url = &format!("{}/2/v6/dest?from=fe80::1&key=5:6:7::3333", base_url);
let res = client.get(url).send().await.map_err(|_| test)?;
assert_text!(res, test, "fe85:6:7::3332");
test = (3, 2);
let url = &format!(
"{}/2/v6/dest?from=aaaa:0:0:0::aaaa&key=ffff:ffff:c:0:0:c:1234:ffff",
base_url
);
let res = client.get(url).send().await.map_err(|_| test)?;
assert_text!(res, test, "5555:ffff:c::c:1234:5555");
test = (3, 3);
let url = &format!(
"{}/2/v6/dest?from=feed:beef:deaf:bad:cafe::&key=::dab:bed:ace:dad",
base_url
);
let res = client.get(url).send().await.map_err(|_| test)?;
assert_text!(res, test, "feed:beef:deaf:bad:c755:bed:ace:dad");
test = (3, 4);
let url = &format!("{}/2/v6/key?from=fe80::1&to=fe85:6:7::3332", base_url);
let res = client.get(url).send().await.map_err(|_| test)?;
assert_text!(res, test, "5:6:7::3333");
test = (3, 5);
let url = &format!(
"{}/2/v6/key?from=aaaa::aaaa&to=5555:ffff:c:0:0:c:1234:5555",
base_url
);
let res = client.get(url).send().await.map_err(|_| test)?;
assert_text!(res, test, "ffff:ffff:c::c:1234:ffff");
test = (3, 6);
let url = &format!(
"{}/2/v6/key?from=feed:beef:deaf:bad:cafe::&to=feed:beef:deaf:bad:c755:bed:ace:dad",
base_url
);
let res = client.get(url).send().await.map_err(|_| test)?;
assert_text!(res, test, "::dab:bed:ace:dad");
tx.send((false, 50).into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
Ok(())
}
async fn validate_5(base_url: &str, tx: Sender<SubmissionUpdate>) -> ValidateResult {
let client = new_client();
let mut test: TaskTest;
let url = &format!("{}/5/manifest", base_url);
const CT: &str = "Content-Type";
const TOML: &str = "application/toml";
const YAML: &str = "application/yaml";
const JSON: &str = "application/json";
test = (1, 1);
let res = client
.post(url)
.header(CT, TOML)
.body(
r#"
[package]
name = "not-a-gift-order"
authors = ["Not Santa"]
keywords = ["Christmas 2024"]
[[package.metadata.orders]]
item = "Toy car"
quantity = 2
[[package.metadata.orders]]
item = "Lego brick"
quantity = 230
"#,
)
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
assert_text!(res, test, "Toy car: 2\nLego brick: 230");
test = (1, 2);
let res = client
.post(url)
.header(CT, TOML)
.body(
r#"
[package]
name = "coal-in-a-bowl"
authors = ["H4CK3R_13E7"]
keywords = ["Christmas 2024"]
[[package.metadata.orders]]
item = "Coal"
quantity = "Hahaha get rekt"
"#,
)
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::NO_CONTENT);
test = (1, 3);
let res = client
.post(url)
.header(CT, TOML)
.body(
r#"
[package]
name = "coal-in-a-bowl"
authors = ["H4CK3R_13E7"]
keywords = ["Christmas 2024"]
package.metadata.orders = []
"#,
)
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::NO_CONTENT);
test = (1, 4);
let res = client
.post(url)
.header(CT, TOML)
.body(
r#"
[package]
name = "not-a-gift-order"
authors = ["Not Santa"]
keywords = ["Christmas 2024"]
[[package.metadata.orders]]
item = "Toy car"
quantity = 2
[[package.metadata.orders]]
item = "Lego brick"
quantity = 1.5
[[package.metadata.orders]]
item = "Doll"
quantity = 2
[[package.metadata.orders]]
quantity = 5
item = "Cookie:::\n"
[[package.metadata.orders]]
item = "Thing"
count = 3
"#,
)
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
assert_text!(res, test, "Toy car: 2\nDoll: 2\nCookie:::\n: 5");
tx.send((false, 0).into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
test = (2, 1);
let res = client
.post(url)
.header(CT, TOML)
.body(
r#"
[package]
name = false
authors = ["Not Santa"]
keywords = ["Christmas 2024"]
"#,
)
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::BAD_REQUEST);
assert_text!(res, test, "Invalid manifest");
test = (2, 2);
let res = client
.post(url)
.header(CT, TOML)
.body(
r#"
[package]
name = "not-a-gift-order"
authors = ["Not Santa"]
keywords = ["Christmas 2024"]
[profile.release]
incremental = "stonks"
"#,
)
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::BAD_REQUEST);
assert_text!(res, test, "Invalid manifest");
test = (2, 3);
let res = client
.post(url)
.header(CT, TOML)
.body(
r#"
[package]
name = "big-chungus"
version = "2.0.24"
edition = "2024"
resolver = "2"
readme.workspace = true
keywords = ["Christmas 2024"]
[dependencies]
shuttle-runtime = "1.0.0+when"
[target.shuttlings.dependencies]
cch24-validator = "5+more"
[profile.release]
incremental = false
[package.metadata.stuff]
thing = ["yes", "no"]
"#,
)
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::NO_CONTENT);
test = (2, 4);
let res = client
.post(url)
.header(CT, TOML)
.body(
r#"
[package]
name = "chig-bungus"
edition = "2023"
[workspace.dependencies]
shuttle-bring-your-own-cloud = "0.0.0"
"#,
)
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::BAD_REQUEST);
assert_text!(res, test, "Invalid manifest");
test = (2, 5);
let res = client
.post(url)
.header(CT, TOML)
.body(
r#"
[package]
name = "chig-bungus"
[workspace]
resolver = "135"
[workspace.dependencies]
shuttle-bring-your-own-cloud = "0.0.0"
"#,
)
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::BAD_REQUEST);
assert_text!(res, test, "Invalid manifest");
tx.send((false, 0).into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
test = (3, 1);
let res = client
.post(url)
.header(CT, TOML)
.body(
r#"
[package]
name = "grass"
authors = ["A vegan cow"]
keywords = ["Moooooo"]
"#,
)
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::BAD_REQUEST);
assert_text!(res, test, "Magic keyword not provided");
test = (3, 2);
let res = client
.post(url)
.header(CT, TOML)
.body(
r#"
[package]
name = "chig-bungus"
[workspace]
resolver = "2"
[workspace.dependencies]
shuttle-bring-your-own-cloud = "0.0.0"
"#,
)
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::BAD_REQUEST);
assert_text!(res, test, "Magic keyword not provided");
test = (3, 3);
let res = client
.post(url)
.header(CT, TOML)
.body(
r#"
[package]
name = "slurp"
authors = ["A crazy cow"]
keywords = ["MooOooooooOOOOoo00oo=oOooooo", "Mew", "Moh", "Christmas 2024"]
metadata.orders = [{ item = "Milk 🥛", quantity = 1 }]
"#,
)
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
assert_text!(res, test, "Milk 🥛: 1");
test = (3, 4);
let res = client
.post(url)
.header(CT, TOML)
.body(
r#"
[package]
name = "snow"
authors = ["The Cow of Christmas"]
keywords = ["Moooooo Merry Christmas 2024"]
"#,
)
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::BAD_REQUEST);
assert_text!(res, test, "Magic keyword not provided");
tx.send((true, 0).into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
test = (4, 1);
let res = client
.post(url)
.header(CT, "text/html")
.body("<h1>Hello, bird!</h1>")
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::UNSUPPORTED_MEDIA_TYPE);
test = (4, 2);
let res = client
.post(url)
.header(CT, YAML)
.body(
r#"
package:
name: big-chungus-sleigh
version: "2.0.24"
metadata:
orders:
- item: "Toy train"
quantity: 5
- item: "Toy car"
quantity: 3
rust-version: "1.69"
keywords:
- "Christmas 2024"
"#,
)
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
assert_text!(res, test, "Toy train: 5\nToy car: 3");
test = (4, 3);
let res = client
.post(url)
.header(CT, YAML)
.body(
r#"
package:
name: big-chungus-sleigh
metadata:
orders:
- item: "Toy train"
quantity: 5
- item: "Coal"
- item: "Horse"
quantity: 2
keywords:
- "Christmas 2024"
"#,
)
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
assert_text!(res, test, "Toy train: 5\nHorse: 2");
test = (4, 4);
let res = client
.post(url)
.header(CT, YAML)
.body(
r#"
package:
name: big-chungus-sleigh
metadata:
orders:
- item: "Toy train"
quantity: 5
rust-version: true
keywords:
- "Christmas 2024"
"#,
)
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::BAD_REQUEST);
assert_text!(res, test, "Invalid manifest");
test = (4, 5);
let res = client
.post(url)
.header(CT, JSON)
.body(
r#"
{
"package": {
"name": "big-chungus-sleigh",
"version": "2.0.24",
"metadata": {
"orders": [
{
"item": "Toy train",
"quantity": 5
},
{
"item": "Toy car",
"quantity": 3
}
]
},
"rust-version": "1.69",
"keywords": [
"Christmas 2024"
]
}
}
"#,
)
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
assert_text!(res, test, "Toy train: 5\nToy car: 3");
test = (4, 6);
let res = client
.post(url)
.header(CT, JSON)
.body(
r#"
{
"package": {
"name": "big-chungus-sleigh",
"metadata": {
"orders": [
{
"item": "Toy train",
"quantity": 5
},
{
"item": "Coal"
},
{
"item": "Horse",
"quantity": 2
}
]
},
"keywords": [
"Christmas 2024"
]
}
}
"#,
)
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
assert_text!(res, test, "Toy train: 5\nHorse: 2");
test = (4, 7);
let res = client
.post(url)
.header(CT, JSON)
.body(
r#"
{
"package": {
"name": "big-chungus-sleigh",
"metadata": {
"orders": [
{
"item": "Toy train",
"quantity": 5
}
]
}
}
}
"#,
)
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::BAD_REQUEST);
assert_text!(res, test, "Magic keyword not provided");
tx.send((false, 70).into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
Ok(())
}
async fn validate_9(base_url: &str, tx: Sender<SubmissionUpdate>) -> ValidateResult {
let client = new_client();
let mut test: TaskTest;
test = (1, 1);
let url = &format!("{}/9/milk", base_url);
let start = Utc::now();
let res = client.post(url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
assert_text!(res, test, "Milk withdrawn\n");
let res = client.post(url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
assert_text!(res, test, "Milk withdrawn\n");
let res = client.post(url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
assert_text!(res, test, "Milk withdrawn\n");
let res = client.post(url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
assert_text!(res, test, "Milk withdrawn\n");
let res = client.post(url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
assert_text!(res, test, "Milk withdrawn\n");
let end = Utc::now();
if end - start > TimeDelta::milliseconds(500) {
tx.send(SubmissionUpdate::LogLine(
"Info: High network latency detected. This test is timing-sensitive and might therefore fail.".to_owned()
))
.await
.unwrap();
}
let res = client.post(url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::TOO_MANY_REQUESTS);
assert_text!(res, test, "No milk available\n");
sleep(Duration::from_secs(1)).await;
let res = client.post(url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
assert_text!(res, test, "Milk withdrawn\n");
let res = client.post(url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::TOO_MANY_REQUESTS);
assert_text!(res, test, "No milk available\n");
sleep(Duration::from_secs(2)).await;
let res = client.post(url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
assert_text!(res, test, "Milk withdrawn\n");
let res = client.post(url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
assert_text!(res, test, "Milk withdrawn\n");
let res = client.post(url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::TOO_MANY_REQUESTS);
assert_text!(res, test, "No milk available\n");
let res = client.post(url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::TOO_MANY_REQUESTS);
assert_text!(res, test, "No milk available\n");
tx.send((false, 0).into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
sleep(Duration::from_secs(5)).await;
test = (2, 1);
let res = client
.post(url)
.json(&json!({"liters": 2}))
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
let j = res.json::<serde_json::Value>().await.map_err(|_| test)?;
assert_!(
test,
j.as_object().is_some_and(|o| o.len() == 1
&& o.get("gallons")
.is_some_and(|g| g.as_f64().is_some_and(|f| (f - 0.5283441).abs() < 0.0001)))
);
test = (2, 2);
let res = client
.post(url)
.json(&json!({"gallons": -2.000000000000001}))
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
let j = res.json::<serde_json::Value>().await.map_err(|_| test)?;
assert_!(
test,
j.as_object().is_some_and(|o| o.len() == 1
&& o.get("liters")
.is_some_and(|g| g.as_f64().is_some_and(|f| (f - -7.5708237).abs() < 0.0001)))
);
test = (2, 3);
let res = client.post(url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
assert_text!(res, test, "Milk withdrawn\n");
test = (2, 4);
let res = client
.post(url)
.json(&json!({}))
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::BAD_REQUEST);
test = (2, 5);
let res = client
.post(url)
.json(&json!({"liters": 0, "gallons": 1337}))
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::BAD_REQUEST);
test = (2, 6);
let res = client.post(url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::TOO_MANY_REQUESTS);
assert_text!(res, test, "No milk available\n");
test = (2, 7);
sleep(Duration::from_secs(1)).await;
let res = client
.post(url)
.header("Content-Type", "application/json")
.body("")
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::BAD_REQUEST);
test = (2, 8);
sleep(Duration::from_secs(1)).await;
let res = client
.post(url)
.header("Content-Type", "application/json")
.body("")
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::BAD_REQUEST);
test = (2, 9);
sleep(Duration::from_secs(1)).await;
let res = client
.post(url)
.header("Content-Type", "application/json")
.body("{'liters':0}")
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::BAD_REQUEST);
test = (2, 10);
sleep(Duration::from_secs(1)).await;
let res = client
.post(url)
.json(&json!({"liters": 123123123123.0}))
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
let j = res.json::<serde_json::Value>().await.map_err(|_| test)?;
assert_!(
test,
j.as_object().is_some_and(|o| o.len() == 1
&& o.get("gallons").is_some_and(|g| g
.as_f64()
.is_some_and(|f| (f - 32525687000.0).abs() < 0.0001)))
);
test = (2, 11);
sleep(Duration::from_secs(1)).await;
let res = client
.post(url)
.header("Content-Type", "text/html")
.body(r#"{"liters":0}"#)
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
assert_text!(res, test, "Milk withdrawn\n");
tx.send((false, 0).into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
sleep(Duration::from_secs(5)).await;
test = (3, 1);
let res = client
.post(url)
.json(&json!({"litres": 7.4}))
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
let j = res.json::<serde_json::Value>().await.map_err(|_| test)?;
assert_!(
test,
j.as_object().is_some_and(|o| o.len() == 1
&& o.get("pints")
.is_some_and(|g| g.as_f64().is_some_and(|f| (f - 13.02218).abs() < 0.0001)))
);
test = (3, 2);
let res = client
.post(url)
.json(&json!({"pints": 32630.25}))
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
let j = res.json::<serde_json::Value>().await.map_err(|_| test)?;
assert_!(
test,
j.as_object().is_some_and(|o| o.len() == 1
&& o.get("litres")
.is_some_and(|g| g.as_f64().is_some_and(|f| (f - 18542.508).abs() < 0.0001)))
);
test = (3, 3);
let res = client
.post(url)
.json(&json!({"litres": -0.0}))
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
let j = res.json::<serde_json::Value>().await.map_err(|_| test)?;
assert_!(
test,
j.as_object().is_some_and(|o| o.len() == 1
&& o.get("pints")
.is_some_and(|g| g.as_f64().is_some_and(|f| (f - -0.0).abs() < 0.0001)))
);
test = (3, 4);
let res = client
.post(url)
.json(&json!({"litres": 7.4, "liters": 7.4}))
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::BAD_REQUEST);
test = (3, 5);
let res = client
.post(url)
.json(r#"{"litres": 7.4, "litres": 7.6}"#)
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::BAD_REQUEST);
test = (3, 6);
sleep(Duration::from_secs(1)).await;
let res = client
.post(url)
.json(&json!({"gallons": 2, "pints": 0}))
.send()
.await
.map_err(|_| test)?;
assert_status!(res, test, StatusCode::BAD_REQUEST);
test = (3, 7);
let res = client.post(url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::TOO_MANY_REQUESTS);
assert_text!(res, test, "No milk available\n");
tx.send((true, 0).into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
test = (4, 1);
let refill_url = &format!("{}/9/refill", base_url);
let res = client.post(refill_url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
test = (4, 2);
let res = client.post(url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
assert_text!(res, test, "Milk withdrawn\n");
let res = client.post(url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
assert_text!(res, test, "Milk withdrawn\n");
let res = client.post(url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
assert_text!(res, test, "Milk withdrawn\n");
let res = client.post(url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
assert_text!(res, test, "Milk withdrawn\n");
let res = client.post(url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
assert_text!(res, test, "Milk withdrawn\n");
let res = client.post(url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::TOO_MANY_REQUESTS);
assert_text!(res, test, "No milk available\n");
let res = client.post(refill_url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
let res = client.post(url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
assert_text!(res, test, "Milk withdrawn\n");
let res = client.post(url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
assert_text!(res, test, "Milk withdrawn\n");
let res = client.post(url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
assert_text!(res, test, "Milk withdrawn\n");
let res = client.post(url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
assert_text!(res, test, "Milk withdrawn\n");
let res = client.post(url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::OK);
assert_text!(res, test, "Milk withdrawn\n");
let res = client.post(url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::TOO_MANY_REQUESTS);
assert_text!(res, test, "No milk available\n");
let res = client.post(url).send().await.map_err(|_| test)?;
assert_status!(res, test, StatusCode::TOO_MANY_REQUESTS);
assert_text!(res, test, "No milk available\n");
tx.send((false, 75).into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
Ok(())
}