cch23-validator 22.0.1

Validate solutions to challenges from Shuttle's Christmas Code Hunt 2023
Documentation
pub mod args;

use std::{ops::Deref, sync::Arc};

use base64::{engine::general_purpose, Engine};
use futures_util::{
    stream::{SplitSink, SplitStream},
    SinkExt, StreamExt,
};
use reqwest::{
    header::{HeaderValue, CONTENT_TYPE},
    multipart::{Form, Part},
    redirect::Policy,
    StatusCode,
};
use tokio::{
    net::TcpStream,
    sync::mpsc::Sender,
    time::{sleep, Duration},
};
use tokio_tungstenite::{tungstenite::Message, MaybeTlsStream, WebSocketStream};
use tracing::info;
use uuid::Uuid;

pub const SUPPORTED_CHALLENGES: &[i32] =
    &[-1, 1, 4, 5, 6, 7, 8, 11, 12, 13, 14, 15, 18, 19, 20, 21, 22];
pub const SUBMISSION_TIMEOUT: u64 = 60;

#[derive(Debug)]
pub enum SubmissionState {
    Waiting,
    Running,
    Done,
    Error,
}
impl std::fmt::Display for SubmissionState {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{self:?}")
    }
}

#[derive(Debug)]
pub enum SubmissionUpdate {
    /// State update
    State(SubmissionState),
    /// bool is true if this task was the last core task, int is amount of bonus points
    TaskCompleted(bool, i32),
    /// Append line to log
    LogLine(String),
    /// Save changes to db
    Save,
}
impl From<SubmissionState> for SubmissionUpdate {
    fn from(value: SubmissionState) -> Self {
        Self::State(value)
    }
}
impl From<(bool, i32)> for SubmissionUpdate {
    fn from((b, i): (bool, i32)) -> Self {
        Self::TaskCompleted(b, i)
    }
}
impl From<String> for SubmissionUpdate {
    fn from(value: String) -> Self {
        Self::LogLine(value)
    }
}

pub async fn run(url: String, id: Uuid, number: i32, 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)) => {
            // if the validation task timed out
            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");
}

/// Task number and Test number in the current challenge
type TaskTest = (i32, i32);
/// If failure, return tuple with task number and test number that failed
type ValidateResult = std::result::Result<(), TaskTest>;

pub async fn validate(url: &str, number: i32, tx: Sender<SubmissionUpdate>) {
    if !SUPPORTED_CHALLENGES.contains(&number) {
        tx.send(
            format!("Validating Challenge {number} is not supported yet! Check for updates.")
                .into(),
        )
        .await
        .unwrap();
        return;
    }
    let txc = tx.clone();
    if let Err((task, test)) = match number {
        -1 => validate_minus1(url, txc).await,
        1 => validate_1(url, txc).await,
        4 => validate_4(url, txc).await,
        5 => validate_5(url, txc).await,
        6 => validate_6(url, txc).await,
        7 => validate_7(url, txc).await,
        8 => validate_8(url, txc).await,
        11 => validate_11(url, txc).await,
        12 => validate_12(url, txc).await,
        13 => validate_13(url, txc).await,
        14 => validate_14(url, txc).await,
        15 => validate_15(url, txc).await,
        18 => validate_18(url, txc).await,
        19 => validate_19(url, txc).await,
        20 => validate_20(url, txc).await,
        21 => validate_21(url, txc).await,
        22 => validate_22(url, txc).await,
        _ => unreachable!(),
    } {
        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();
}

pub fn make_url(pn: Option<&str>) -> String {
    match pn {
        Some(pn) => format!("https://{pn}.shuttleapp.rs"),
        None => "http://localhost:8000".to_owned(),
    }
}

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()
}

async fn validate_minus1(base_url: &str, tx: Sender<SubmissionUpdate>) -> ValidateResult {
    let client = new_client();
    let mut test: TaskTest;
    // TASK 1: respond 200
    test = (1, 1);
    let url = &format!("{}/", base_url);
    let res = client.get(url).send().await.map_err(|_| test)?;
    if res.status() != StatusCode::OK {
        return Err(test);
    }
    // TASK 1 DONE
    tx.send((true, 0).into()).await.unwrap();
    tx.send(SubmissionUpdate::Save).await.unwrap();

    // TASK 2: respond 500
    test = (2, 1);
    let url = &format!("{}/-1/error", base_url);
    let res = client.get(url).send().await.map_err(|_| test)?;
    if res.status() != StatusCode::INTERNAL_SERVER_ERROR {
        return Err(test);
    }
    // TASK 2 DONE
    tx.send((false, 0).into()).await.unwrap();

    Ok(())
}

async fn validate_1(base_url: &str, tx: Sender<SubmissionUpdate>) -> ValidateResult {
    let client = new_client();
    let mut test: TaskTest;
    // TASK 1: basic formula
    test = (1, 1);
    let url = &format!("{}/1/2/3", base_url);
    let res = client.get(url).send().await.map_err(|_| test)?;
    let text = res.text().await.map_err(|_| test)?;
    if text != "1" {
        return Err(test);
    }
    test = (1, 2);
    let url = &format!("{}/1/12/16", base_url);
    let res = client.get(url).send().await.map_err(|_| test)?;
    let text = res.text().await.map_err(|_| test)?;
    if text != "21952" {
        return Err(test);
    }
    // TASK 1 DONE
    tx.send((true, 0).into()).await.unwrap();
    tx.send(SubmissionUpdate::Save).await.unwrap();

    // TASK 2: multiple and zero and negative numbers
    test = (2, 1);
    let url = &format!("{}/1/3/5/7/9", base_url);
    let res = client.get(url).send().await.map_err(|_| test)?;
    let text = res.text().await.map_err(|_| test)?;
    if text != "512" {
        return Err(test);
    }
    test = (2, 2);
    let url = &format!("{}/1/0/0/0", base_url);
    let res = client.get(url).send().await.map_err(|_| test)?;
    let text = res.text().await.map_err(|_| test)?;
    if text != "0" {
        return Err(test);
    }
    test = (2, 3);
    let url = &format!("{}/1/-3/1", base_url);
    let res = client.get(url).send().await.map_err(|_| test)?;
    let text = res.text().await.map_err(|_| test)?;
    if text != "-64" {
        return Err(test);
    }
    test = (2, 4);
    let url = &format!("{}/1/3/5/7/9/2/13/12/16/18", base_url);
    let res = client.get(url).send().await.map_err(|_| test)?;
    let text = res.text().await.map_err(|_| test)?;
    if text != "729" {
        return Err(test);
    }
    tx.send((false, 100).into()).await.unwrap();

    Ok(())
}

async fn validate_4(base_url: &str, tx: Sender<SubmissionUpdate>) -> ValidateResult {
    let client = new_client();
    let mut test: TaskTest;
    // TASK 1
    test = (1, 1);
    let url = &format!("{}/4/strength", base_url);
    let res = client
        .post(url)
        .json(&serde_json::json!([
            {
              "name": "Zeus",
              "strength": 8
            },
            {
              "name": "Oner",
              "strength": 6
            },
            {
              "name": "Faker",
              "strength": 7
            },
            {
              "name": "Gumayusi",
              "strength": 6
            },
            {
              "name": "Keria",
              "strength": 6
            }
        ]))
        .send()
        .await
        .map_err(|_| test)?;
    let text = res.text().await.map_err(|_| test)?;
    if text != "33" {
        return Err(test);
    }
    // TASK 1 DONE
    tx.send((true, 0).into()).await.unwrap();
    tx.send(SubmissionUpdate::Save).await.unwrap();

    // TASK 2
    test = (2, 1);
    let url = &format!("{}/4/contest", base_url);
    let res = client
        .post(url)
        .json(&serde_json::json!([
        {
            "name": "Zeus",
            "strength": 8,
            "speed": 51.2,
            "height": 81,
            "antler_width": 31,
            "snow_magic_power": 311,
            "favorite_food": "pizza",
            "cAnD13s_3ATeN-yesT3rdAy": 4
        },
        {
            "name": "Oner",
            "strength": 6,
            "speed": 41.3,
            "height": 51,
            "antler_width": 30,
            "snow_magic_power": 321,
            "favorite_food": "burger",
            "cAnD13s_3ATeN-yesT3rdAy": 1
        },
        {
            "name": "Faker",
            "strength": 7,
            "speed": 50,
            "height": 50,
            "antler_width": 37,
            "snow_magic_power": 6667,
            "favorite_food": "broccoli",
            "cAnD13s_3ATeN-yesT3rdAy": 1
        },
        {
            "name": "Gumayusi",
            "strength": 6,
            "speed": 60.1,
            "height": 50,
            "antler_width": 34,
            "snow_magic_power": 2323,
            "favorite_food": "pizza",
            "cAnD13s_3ATeN-yesT3rdAy": 1
        },
        {
            "name": "Keria",
            "strength": 6,
            "speed": 48.2,
            "height": 65,
            "antler_width": 33,
            "snow_magic_power": 5014,
            "favorite_food": "wok",
            "cAnD13s_3ATeN-yesT3rdAy": 5
        }
        ]))
        .send()
        .await
        .map_err(|_| test)?;
    let json = res.json::<serde_json::Value>().await.map_err(|_| test)?;
    if json
        != serde_json::json!({
            "fastest":"Speeding past the finish line with a strength of 6 is Gumayusi",
            "tallest":"Zeus is standing tall with his 31 cm wide antlers",
            "magician":"Faker could blast you away with a snow magic power of 6667",
            "consumer":"Keria ate lots of candies, but also some wok"
        })
    {
        return Err(test);
    }
    tx.send((false, 150).into()).await.unwrap();

    Ok(())
}

async fn validate_5(base_url: &str, tx: Sender<SubmissionUpdate>) -> ValidateResult {
    // TASK 1
    let t = JSONTester::new(format!("{}/5?offset=0&limit=8", base_url));
    t.test(
        (1, 1),
        &serde_json::json!(["Ava", "Caleb", "Mia", "Owen", "Lily", "Ethan", "Zoe", "Nolan"]),
        StatusCode::OK,
        &serde_json::json!(["Ava", "Caleb", "Mia", "Owen", "Lily", "Ethan", "Zoe", "Nolan"]),
    )
    .await?;
    let t = JSONTester::new(format!("{}/5?offset=10&limit=4", base_url));
    t.test(
        (1, 2),
        &serde_json::json!([
            "Ava", "Caleb", "Mia", "Owen", "Lily", "Ethan", "Zoe", "Nolan", "Harper", "Lucas",
            "Stella", "Mason", "Olivia", "Wyatt", "Isabella", "Logan",
        ]),
        StatusCode::OK,
        &serde_json::json!(["Stella", "Mason", "Olivia", "Wyatt"]),
    )
    .await?;
    // TASK 1 DONE
    tx.send((true, 0).into()).await.unwrap();
    tx.send(SubmissionUpdate::Save).await.unwrap();

    // TASK 2
    let t = JSONTester::new(format!("{}/5?offset=0&limit=5", base_url));
    t.test(
        (2, 1),
        &serde_json::json!([]),
        StatusCode::OK,
        &serde_json::json!([]),
    )
    .await?;
    let t = JSONTester::new(format!("{}/5", base_url));
    t.test(
        (2, 2),
        &serde_json::json!(["Alice", "Bob", "Charlie", "David"]),
        StatusCode::OK,
        &serde_json::json!(["Alice", "Bob", "Charlie", "David"]),
    )
    .await?;
    let t = JSONTester::new(format!("{}/5?offset=2", base_url));
    t.test(
        (2, 3),
        &serde_json::json!(["Alice", "Bob", "Charlie", "David"]),
        StatusCode::OK,
        &serde_json::json!(["Charlie", "David"]),
    )
    .await?;
    let t = JSONTester::new(format!("{}/5?offset=2&limit=0", base_url));
    t.test(
        (2, 4),
        &serde_json::json!(["Alice", "Bob", "Charlie", "David"]),
        StatusCode::OK,
        &serde_json::json!([]),
    )
    .await?;
    let t = JSONTester::new(format!("{}/5?split=6", base_url));
    t.test(
        (2, 5),
        &serde_json::json!([
            "Alice", "Bob", "Charlie", "David", "Eva", "Frank", "Grace", "Hank", "Ivy", "Jack",
            "Katie", "Liam", "Mia", "Nathan", "Olivia", "Paul", "Quinn", "Rachel", "Samuel",
            "Tara", "Aria", "Jackson"
        ]),
        StatusCode::OK,
        &serde_json::json!([
            ["Alice", "Bob", "Charlie", "David", "Eva", "Frank"],
            ["Grace", "Hank", "Ivy", "Jack", "Katie", "Liam"],
            ["Mia", "Nathan", "Olivia", "Paul", "Quinn", "Rachel"],
            ["Samuel", "Tara", "Aria", "Jackson"]
        ]),
    )
    .await?;
    let t = JSONTester::new(format!("{}/5?offset=2&limit=4&split=1", base_url));
    t.test(
        (2, 6),
        &serde_json::json!([
            "Alice", "Bob", "Charlie", "David", "Alice", "Bob", "Charlie", "David"
        ]),
        StatusCode::OK,
        &serde_json::json!([["Charlie"], ["David"], ["Alice"], ["Bob"],]),
    )
    .await?;
    let t = JSONTester::new(format!("{}/5?limit=0", base_url));
    t.test(
        (2, 7),
        &serde_json::json!(["Alice", "Bob", "Charlie", "David"]),
        StatusCode::OK,
        &serde_json::json!([]),
    )
    .await?;
    let t = JSONTester::new(format!("{}/5?offset=0&limit=0", base_url));
    t.test(
        (2, 8),
        &serde_json::json!(["Alice", "Bob", "Charlie", "David"]),
        StatusCode::OK,
        &serde_json::json!([]),
    )
    .await?;
    tx.send((false, 150).into()).await.unwrap();

    Ok(())
}

async fn validate_6(base_url: &str, tx: Sender<SubmissionUpdate>) -> ValidateResult {
    let client = new_client();
    let mut test: TaskTest;
    let url = &format!("{}/6", base_url);
    // TASK 1: elf
    test = (1, 1);
    let res = client
        .post(url)
        .body("elf elf elf")
        .send()
        .await
        .map_err(|_| test)?;
    let json = res.json::<serde_json::Value>().await.map_err(|_| test)?;
    if json["elf"] != serde_json::Value::Number(3.into()) {
        return Err(test);
    }
    test = (1, 2);
    let res = client
        .post(url)
        .body("In the quirky town of Elf stood an enchanting shop named 'The Elf & Shelf.' Managed by Wally, a mischievous elf with a knack for crafting exquisite shelves, the shop was a bustling hub of elf after elf who wanter to see their dear elf in Belfast.")
        .send()
        .await
        .map_err(|_| test)?;
    let json = res.json::<serde_json::Value>().await.map_err(|_| test)?;
    if json["elf"] != serde_json::Value::Number(6.into()) {
        return Err(test);
    }
    // TASK 1 DONE
    tx.send((true, 0).into()).await.unwrap();
    tx.send(SubmissionUpdate::Save).await.unwrap();

    // TASK 2: more strings
    test = (2, 1);
    let res = client
        .post(url)
        .body("elf elf elf on a shelf")
        .send()
        .await
        .map_err(|_| test)?;
    let json = res.json::<serde_json::Value>().await.map_err(|_| test)?;
    if json
        != serde_json::json!({
            "elf":4,
            "elf on a shelf":1,
            "shelf with no elf on it":0
        })
    {
        return Err(test);
    }
    test = (2, 2);
    let res = client
        .post(url)
        .body("In Belfast I heard an elf on a shelf on a shelf on a ")
        .send()
        .await
        .map_err(|_| test)?;
    let json = res.json::<serde_json::Value>().await.map_err(|_| test)?;
    if json
        != serde_json::json!({
            "elf":4,
            "elf on a shelf":2,
            "shelf with no elf on it":0
        })
    {
        return Err(test);
    }
    test = (2, 3);
    let res = client
        .post(url)
        .body("Somewhere in Belfast under a shelf store but above the shelf realm there's an elf on a shelf on a shelf on a shelf on a elf on a shelf on a shelf on a shelf on a shelf on a elf on a elf on a elf on a shelf on a ")
        .send()
        .await
        .map_err(|_| test)?;
    let json = res.json::<serde_json::Value>().await.map_err(|_| test)?;
    if json
        != serde_json::json!({
            "elf":16,
            "elf on a shelf":8,
            "shelf with no elf on it":2
        })
    {
        return Err(test);
    }
    // TASK 2 DONE
    tx.send((false, 200).into()).await.unwrap();

    Ok(())
}

async fn validate_7(base_url: &str, tx: Sender<SubmissionUpdate>) -> ValidateResult {
    let client = new_client();
    let mut test: TaskTest;
    // TASK 1
    test = (1, 1);
    let url = &format!("{}/7/decode", base_url);
    let data = serde_json::json!({
        "recipe": {
            "flour": 4,
            "sugar": 3,
            "butter": 3,
            "baking powder": 1,
            "raisins": 50
        },
    });
    let b64 = general_purpose::STANDARD.encode(serde_json::to_vec(&data).unwrap());
    let res = client
        .get(url)
        .header("Cookie", format!("recipe={b64}"))
        .send()
        .await
        .map_err(|_| test)?;
    let json = res.json::<serde_json::Value>().await.map_err(|_| test)?;
    if json != data {
        return Err(test);
    }
    test = (1, 2);
    let data = serde_json::json!({
        "recipe": {
            "peanuts": 26,
            "dough": 37,
            "extra salt": 1,
            "raisins": 50
        },
    });
    let b64 = general_purpose::STANDARD.encode(serde_json::to_vec(&data).unwrap());
    let res = client
        .get(url)
        .header("Cookie", format!("recipe={b64}"))
        .send()
        .await
        .map_err(|_| test)?;
    let json = res.json::<serde_json::Value>().await.map_err(|_| test)?;
    if json != data {
        return Err(test);
    }
    // TASK 1 DONE
    tx.send((true, 0).into()).await.unwrap();
    tx.send(SubmissionUpdate::Save).await.unwrap();

    // TASK 2
    let url = &format!("{}/7/bake", base_url);
    let test_bake = |test: (i32, i32), i: serde_json::Value, o: serde_json::Value| async move {
        let client = new_client();
        let b64 = general_purpose::STANDARD.encode(serde_json::to_vec(&i).unwrap());
        let res = client
            .get(url)
            .header("Cookie", format!("recipe={b64}"))
            .send()
            .await
            .map_err(|_| test)?;
        let json = res.json::<serde_json::Value>().await.map_err(|_| test)?;
        if json != o {
            return Err(test);
        }
        Ok(())
    };
    test = (2, 1);
    test_bake(
        test,
        serde_json::json!({
            "recipe": {
                "flour": 35,
                "sugar": 56,
                "butter": 3,
                "baking powder": 1001,
                "chocolate chips": 55
            },
            "pantry": {
                "flour": 4045,
                "sugar": 9606,
                "butter": 99, // will land at 0
                "baking powder": 8655432,
                "chocolate chips": 4587
            }
        }),
        serde_json::json!({
            "cookies": 33,
            "pantry": {
                "flour": 2890,
                "sugar": 7758,
                "butter": 0,
                "baking powder": 8622399,
                "chocolate chips": 2772
            }
        }),
    )
    .await?;
    test = (2, 2);
    test_bake(
        test,
        serde_json::json!({
            "recipe": {
                "flour": 35,
                "sugar": 56,
                "butter": 3,
                "baking powder": 1001,
                "chocolate chips": 55
            },
            "pantry": {
                "flour": 4045,
                "sugar": 7606,
                "butter": 100,
                "baking powder": 865543211516164409i64,
                "chocolate chips": 4587
            }
        }),
        serde_json::json!({
            "cookies": 33,
            "pantry": {
                "flour": 2890,
                "sugar": 5758,
                "butter": 1,
                "baking powder": 865543211516131376i64,
                "chocolate chips": 2772
            }
        }),
    )
    .await?;
    // TASK 2 DONE
    tx.send((false, 120).into()).await.unwrap();
    tx.send(SubmissionUpdate::Save).await.unwrap();

    // TASK 3
    test = (3, 1);
    test_bake(
        test,
        serde_json::json!({
            "recipe": {
                "chicken": 1,
            },
            "pantry": {
                "chicken": 0,
            }
        }),
        serde_json::json!({
            "cookies": 0,
            "pantry": {
                "chicken": 0,
            }
        }),
    )
    .await?;
    test = (3, 2);
    test_bake(
        test,
        serde_json::json!({
            "recipe": {
                "cocoa bean": 1,
                "chicken": 0,
            },
            "pantry": {
                "cocoa bean": 5,
                "corn": 5,
                "cucumber": 0,
            }
        }),
        serde_json::json!({
            "cookies": 5,
            "pantry": {
                "cocoa bean": 0,
                "corn": 5,
                "cucumber": 0,
            }
        }),
    )
    .await?;
    test = (3, 3);
    test_bake(
        test,
        serde_json::json!({
            "recipe": {
                "cocoa bean": 1,
                "chicken": 0,
            },
            "pantry": {
                "cocoa bean": 5,
                "chicken": 0,
            }
        }),
        serde_json::json!({
            "cookies": 5,
            "pantry": {
                "cocoa bean": 0,
                "chicken": 0,
            }
        }),
    )
    .await?;
    test = (3, 4);
    test_bake(
        test,
        serde_json::json!({
            "recipe": {
                "cocoa bean": 1,
                "chicken": 0,
            },
            "pantry": {
                "cocoa bean": 5,
            }
        }),
        serde_json::json!({
            "cookies": 5,
            "pantry": {
                "cocoa bean": 0,
            }
        }),
    )
    .await?;
    // TASK 3 DONE
    tx.send((false, 100).into()).await.unwrap();

    Ok(())
}

async fn validate_8(base_url: &str, tx: Sender<SubmissionUpdate>) -> ValidateResult {
    let client = new_client();
    let mut test: TaskTest;
    let tol = 0.001f64;
    // TASK 1
    test = (1, 1);
    let url = &format!("{}/8/weight/225", base_url);
    let res = client.get(url).send().await.map_err(|_| test)?;
    let text = res.text().await.map_err(|_| test)?;
    let num: f64 = text.parse().map_err(|_| test)?;
    if !(num.is_finite() && (num - 16f64).abs() < tol) {
        return Err(test);
    }
    test = (1, 2);
    let url = &format!("{}/8/weight/393", base_url);
    let res = client.get(url).send().await.map_err(|_| test)?;
    let text = res.text().await.map_err(|_| test)?;
    let num: f64 = text.parse().map_err(|_| test)?;
    if !(num.is_finite() && (num - 5.2f64).abs() < tol) {
        return Err(test);
    }
    test = (1, 3);
    let url = &format!("{}/8/weight/92", base_url);
    let res = client.get(url).send().await.map_err(|_| test)?;
    let text = res.text().await.map_err(|_| test)?;
    let num: f64 = text.parse().map_err(|_| test)?;
    if !(num.is_finite() && (num - 0.1f64).abs() < tol) {
        return Err(test);
    }
    // TASK 1 DONE
    tx.send((true, 0).into()).await.unwrap();
    tx.send(SubmissionUpdate::Save).await.unwrap();

    // TASK 2
    test = (2, 1);
    let url = &format!("{}/8/drop/383", base_url);
    let res = client.get(url).send().await.map_err(|_| test)?;
    let text = res.text().await.map_err(|_| test)?;
    let num: f64 = text.parse().map_err(|_| test)?;
    if !(num.is_finite() && (num - 13316.953480432378f64).abs() < tol) {
        return Err(test);
    }
    test = (2, 2);
    let url = &format!("{}/8/drop/16", base_url);
    let res = client.get(url).send().await.map_err(|_| test)?;
    let text = res.text().await.map_err(|_| test)?;
    let num: f64 = text.parse().map_err(|_| test)?;
    if !(num.is_finite() && (num - 25.23212238397714f64).abs() < tol) {
        return Err(test);
    }
    test = (2, 3);
    let url = &format!("{}/8/drop/143", base_url);
    let res = client.get(url).send().await.map_err(|_| test)?;
    let text = res.text().await.map_err(|_| test)?;
    let num: f64 = text.parse().map_err(|_| test)?;
    if !(num.is_finite() && (num - 6448.2090536830465f64).abs() < tol) {
        return Err(test);
    }
    // TASK 2 DONE
    tx.send((false, 160).into()).await.unwrap();

    Ok(())
}

async fn validate_11(base_url: &str, tx: Sender<SubmissionUpdate>) -> ValidateResult {
    let client = new_client();
    let mut test: TaskTest;
    // TASK 1
    test = (1, 1);
    let url = &format!("{}/11/assets/decoration.png", base_url);
    let res = client.get(url).send().await.map_err(|_| test)?;
    let headers = res.headers();
    if !headers
        .get("content-type")
        .is_some_and(|v| v == "image/png")
    {
        return Err(test);
    }
    if !headers.get("content-length").is_some_and(|v| v == "787297") {
        return Err(test);
    }
    let bytes = res.bytes().await.map_err(|_| test)?;
    const EXPECTED: &[u8] = include_bytes!("../assets/decoration.png");
    if bytes.to_vec().as_slice() != EXPECTED {
        return Err(test);
    }
    // TASK 1 DONE
    tx.send((true, 0).into()).await.unwrap();
    tx.send(SubmissionUpdate::Save).await.unwrap();

    // TASK 2
    test = (2, 1);
    let url = &format!("{}/11/red_pixels", base_url);
    let form = Form::new().part(
        "image",
        Part::bytes(include_bytes!("../assets/decoration2.png").as_slice())
            .file_name("decoration2.png")
            .mime_str("image/png")
            .unwrap(),
    );
    let res = client
        .post(url)
        .multipart(form)
        .send()
        .await
        .map_err(|_| test)?;
    let text = res.text().await.map_err(|_| test)?;
    if text != "152107" {
        return Err(test);
    }
    test = (2, 2);
    let form = Form::new().part(
        "image",
        Part::bytes(include_bytes!("../assets/decoration3.png").as_slice())
            .file_name("decoration3.png")
            .mime_str("image/png")
            .unwrap(),
    );
    let res = client
        .post(url)
        .multipart(form.into())
        .send()
        .await
        .map_err(|_| test)?;
    let text = res.text().await.map_err(|_| test)?;
    if text != "40263" {
        return Err(test);
    }
    test = (2, 3);
    let form = Form::new().part(
        "image",
        Part::bytes(include_bytes!("../assets/decoration4.png").as_slice())
            .file_name("decoration4.png")
            .mime_str("image/png")
            .unwrap(),
    );
    let res = client
        .post(url)
        .multipart(form.into())
        .send()
        .await
        .map_err(|_| test)?;
    let text = res.text().await.map_err(|_| test)?;
    if text != "86869" {
        return Err(test);
    }
    // TASK 2 DONE
    tx.send((false, 200).into()).await.unwrap();

    Ok(())
}

async fn validate_12(base_url: &str, tx: Sender<SubmissionUpdate>) -> ValidateResult {
    let client = new_client();
    let mut test: TaskTest;
    // TASK 1
    test = (1, 1);
    let url = &format!("{}/12/save/cch23", base_url);
    let res = client.post(url).send().await.map_err(|_| test)?;
    if res.status() != StatusCode::OK {
        return Err(test);
    }
    sleep(Duration::from_secs(2)).await;
    let url = &format!("{}/12/load/cch23", base_url);
    let res = client.get(url).send().await.map_err(|_| test)?;
    let text = res.text().await.map_err(|_| test)?;
    if text != "2" {
        return Err(test);
    }
    sleep(Duration::from_secs(2)).await;
    let url = &format!("{}/12/load/cch23", base_url);
    let res = client.get(url).send().await.map_err(|_| test)?;
    let text = res.text().await.map_err(|_| test)?;
    if text != "4" {
        return Err(test);
    }
    test = (1, 2);
    let url = &format!("{}/12/save/alpha", base_url);
    let res = client.post(url).send().await.map_err(|_| test)?;
    if res.status() != StatusCode::OK {
        return Err(test);
    }
    sleep(Duration::from_secs(2)).await;
    let url = &format!("{}/12/save/omega", base_url);
    let res = client.post(url).send().await.map_err(|_| test)?;
    if res.status() != StatusCode::OK {
        return Err(test);
    }
    sleep(Duration::from_secs(2)).await;
    let url = &format!("{}/12/load/alpha", base_url);
    let res = client.get(url).send().await.map_err(|_| test)?;
    let text = res.text().await.map_err(|_| test)?;
    if text != "4" {
        return Err(test);
    }
    let url = &format!("{}/12/save/alpha", base_url);
    let res = client.post(url).send().await.map_err(|_| test)?;
    if res.status() != StatusCode::OK {
        return Err(test);
    }
    sleep(Duration::from_secs(1)).await;
    let url = &format!("{}/12/load/omega", base_url);
    let res = client.get(url).send().await.map_err(|_| test)?;
    let text = res.text().await.map_err(|_| test)?;
    if text != "3" {
        return Err(test);
    }
    let url = &format!("{}/12/load/alpha", base_url);
    let res = client.get(url).send().await.map_err(|_| test)?;
    let text = res.text().await.map_err(|_| test)?;
    if text != "1" {
        return Err(test);
    }
    // TASK 1 DONE
    tx.send((true, 0).into()).await.unwrap();
    tx.send(SubmissionUpdate::Save).await.unwrap();

    // TASK 2
    test = (2, 1);
    let url = &format!("{}/12/ulids", base_url);
    let res = client
        .post(url)
        .json(&serde_json::json!([
            "01BJQ0E1C3Z56ABCD0E11HYX4M",
            "01BJQ0E1C3Z56ABCD0E11HYX5N",
            "01BJQ0E1C3Z56ABCD0E11HYX6Q",
            "01BJQ0E1C3Z56ABCD0E11HYX7R",
            "01BJQ0E1C3Z56ABCD0E11HYX8P"
        ]))
        .send()
        .await
        .map_err(|_| test)?;
    let json = res.json::<serde_json::Value>().await.map_err(|_| test)?;
    if json
        != serde_json::json!([
            "015cae07-0583-f94c-a5b1-a070431f7516",
            "015cae07-0583-f94c-a5b1-a070431f74f8",
            "015cae07-0583-f94c-a5b1-a070431f74d7",
            "015cae07-0583-f94c-a5b1-a070431f74b5",
            "015cae07-0583-f94c-a5b1-a070431f7494"
        ])
    {
        return Err(test);
    }
    test = (2, 2);
    let res = client
        .post(url)
        .json(&serde_json::json!([]))
        .send()
        .await
        .map_err(|_| test)?;
    let json = res.json::<serde_json::Value>().await.map_err(|_| test)?;
    if json != serde_json::json!([]) {
        return Err(test);
    }
    // TASK 2 DONE
    tx.send((false, 100).into()).await.unwrap();
    tx.send(SubmissionUpdate::Save).await.unwrap();

    // TASK 3
    test = (3, 1);
    let ids = serde_json::json!([
        "00WEGGF0G0J5HEYXS3D7RWZGV8",
        "76EP4G39R8JD1N8AQNYDVJBRCF",
        "018CJ7KMG0051CDCS3B7BFJ3AK",
        "00Y986KPG0AMGB78RD45E9109K",
        "010451HTG0NYWMPWCEXG6AJ8F2",
        "01HH9SJEG0KY16H81S3N1BMXM4",
        "01HH9SJEG0P9M22Z9VGHH9C8CX",
        "017F8YY0G0NQA16HHC2QT5JD6X",
        "03QCPC7P003V1NND3B3QJW72QJ"
    ]);
    let url = &format!("{}/12/ulids/5", base_url);
    let res = client.post(url).json(&ids).send().await.map_err(|_| test)?;
    let json = res.json::<serde_json::Value>().await.map_err(|_| test)?;
    if json
        != serde_json::json!({
            "christmas eve": 3,
            "weekday": 1,
            "in the future": 2,
            "LSB is 1": 5
        })
    {
        return Err(test);
    }
    test = (3, 2);
    let url = &format!("{}/12/ulids/0", base_url);
    let res = client.post(url).json(&ids).send().await.map_err(|_| test)?;
    let json = res.json::<serde_json::Value>().await.map_err(|_| test)?;
    if json
        != serde_json::json!({
            "christmas eve": 3,
            "weekday": 0,
            "in the future": 2,
            "LSB is 1": 5
        })
    {
        return Err(test);
    }
    test = (3, 3);
    let url = &format!("{}/12/ulids/2", base_url);
    let res = client
        .post(url)
        .json(&serde_json::json!(["04BJK8N300BAMR9SQQWPWHVYKZ"]))
        .send()
        .await
        .map_err(|_| test)?;
    let json = res.json::<serde_json::Value>().await.map_err(|_| test)?;
    if json
        != serde_json::json!({
            "christmas eve": 1,
            "weekday": 1,
            "in the future": 1,
            "LSB is 1": 1
        })
    {
        return Err(test);
    }
    // TASK 3 DONE
    tx.send((false, 200).into()).await.unwrap();

    Ok(())
}

async fn validate_13(base_url: &str, tx: Sender<SubmissionUpdate>) -> ValidateResult {
    let client = new_client();
    let mut test: TaskTest;
    // TASK 1
    test = (1, 1);
    let url = &format!("{}/13/sql", base_url);
    let res = client.get(url).send().await.map_err(|_| test)?;
    let text = res.text().await.map_err(|_| test)?;
    if text != "20231213" {
        return Err(test);
    }
    // TASK 1 DONE
    tx.send((false, 0).into()).await.unwrap();
    tx.send(SubmissionUpdate::Save).await.unwrap();

    // TASK 2
    test = (2, 1);
    let reset_url = &format!("{}/13/reset", base_url);
    let order_url = &format!("{}/13/orders", base_url);
    let total_url = &format!("{}/13/orders/total", base_url);
    let res = client.post(reset_url).send().await.map_err(|_| test)?;
    if res.status() != StatusCode::OK {
        return Err(test);
    }
    let res = client
        .post(order_url)
        .json(&serde_json::json!([
            {"id":1,"region_id":2,"gift_name":"Toy Train","quantity":5},
            {"id":2,"region_id":2,"gift_name":"Doll","quantity":8},
            {"id":3,"region_id":3,"gift_name":"Action Figure","quantity":12},
            {"id":4,"region_id":4,"gift_name":"Board Game","quantity":10},
            {"id":5,"region_id":2,"gift_name":"Teddy Bear","quantity":6},
            {"id":6,"region_id":3,"gift_name":"Toy Train","quantity":3},
        ]))
        .send()
        .await
        .map_err(|_| test)?;
    if res.status() != StatusCode::OK {
        return Err(test);
    }
    let res = client.get(total_url).send().await.map_err(|_| test)?;
    let json = res.json::<serde_json::Value>().await.map_err(|_| test)?;
    if json != serde_json::json!({"total": 44}) {
        return Err(test);
    }
    test = (2, 2);
    let res = client
        .post(order_url)
        .json(&serde_json::json!([
            {"id":123,"region_id":6,"gift_name":"Unknown","quantity":333},
        ]))
        .send()
        .await
        .map_err(|_| test)?;
    if res.status() != StatusCode::OK {
        return Err(test);
    }
    let res = client.get(total_url).send().await.map_err(|_| test)?;
    let json = res.json::<serde_json::Value>().await.map_err(|_| test)?;
    if json != serde_json::json!({"total": 377}) {
        return Err(test);
    }
    // TASK 2 DONE
    tx.send((true, 0).into()).await.unwrap();
    tx.send(SubmissionUpdate::Save).await.unwrap();

    // TASK 3
    test = (3, 1);
    let popular_url = &format!("{}/13/orders/popular", base_url);
    let res = client.post(reset_url).send().await.map_err(|_| test)?;
    if res.status() != StatusCode::OK {
        return Err(test);
    }
    let res = client.get(popular_url).send().await.map_err(|_| test)?;
    let json = res.json::<serde_json::Value>().await.map_err(|_| test)?;
    if json != serde_json::json!({"popular": null}) {
        return Err(test);
    }
    test = (3, 2);
    let res = client
        .post(order_url)
        .json(&serde_json::json!([
            {"id":1,"region_id":2,"gift_name":"Lego Rocket","quantity":12},
            {"id":2,"region_id":2,"gift_name":"Action Figure","quantity":18},
            {"id":3,"region_id":5,"gift_name":"Toy Train","quantity":19},
            {"id":4,"region_id":5,"gift_name":"Lego Rocket","quantity":12},
            {"id":5,"region_id":4,"gift_name":"Toy Train","quantity":15},
            {"id":6,"region_id":2,"gift_name":"Toy Train","quantity":7},
            {"id":7,"region_id":3,"gift_name":"Toy Train","quantity":19},
            {"id":8,"region_id":4,"gift_name":"Action Figure","quantity":8},
            {"id":9,"region_id":2,"gift_name":"Toy Axe","quantity":15},
            {"id":10,"region_id":4,"gift_name":"Toy Axe","quantity":1},
            {"id":11,"region_id":2,"gift_name":"Toy Train","quantity":17},
            {"id":12,"region_id":4,"gift_name":"Toy Train","quantity":5},
            {"id":13,"region_id":4,"gift_name":"Sweater","quantity":20},
            {"id":14,"region_id":4,"gift_name":"Action Figure","quantity":7},
            {"id":15,"region_id":2,"gift_name":"Toy Train","quantity":16},
            {"id":16,"region_id":3,"gift_name":"Action Figure","quantity":12},
            {"id":17,"region_id":4,"gift_name":"Toy Axe","quantity":2},
            {"id":18,"region_id":3,"gift_name":"Toy Train","quantity":9},
            {"id":19,"region_id":2,"gift_name":"Sweater","quantity":9},
            {"id":20,"region_id":5,"gift_name":"Toy Train","quantity":9},
            {"id":21,"region_id":4,"gift_name":"Action Figure","quantity":11},
            {"id":22,"region_id":3,"gift_name":"Toy Train","quantity":7},
            {"id":23,"region_id":2,"gift_name":"Action Figure","quantity":5},
            {"id":24,"region_id":4,"gift_name":"Action Figure","quantity":17},
            {"id":25,"region_id":5,"gift_name":"Lego Rocket","quantity":6},
            {"id":26,"region_id":2,"gift_name":"Sweater","quantity":5},
            {"id":27,"region_id":5,"gift_name":"Toy Train","quantity":4},
            {"id":28,"region_id":4,"gift_name":"Lego Rocket","quantity":8},
            {"id":29,"region_id":2,"gift_name":"Toy Train","quantity":3},
            {"id":30,"region_id":4,"gift_name":"Toy Axe","quantity":20},
            {"id":31,"region_id":2,"gift_name":"Action Figure","quantity":5},
            {"id":32,"region_id":2,"gift_name":"Lego Rocket","quantity":10},
            {"id":33,"region_id":5,"gift_name":"Toy Train","quantity":4},
            {"id":34,"region_id":2,"gift_name":"Toy Axe","quantity":14},
            {"id":35,"region_id":3,"gift_name":"Action Figure","quantity":18},
            {"id":36,"region_id":5,"gift_name":"Toy Axe","quantity":10},
            {"id":37,"region_id":4,"gift_name":"Lego Rocket","quantity":6},
            {"id":38,"region_id":4,"gift_name":"Action Figure","quantity":16},
            {"id":39,"region_id":4,"gift_name":"Toy Axe","quantity":15},
            {"id":40,"region_id":5,"gift_name":"Lego Rocket","quantity":15},
            {"id":41,"region_id":5,"gift_name":"Action Figure","quantity":7},
            {"id":42,"region_id":3,"gift_name":"Action Figure","quantity":16},
            {"id":43,"region_id":3,"gift_name":"Toy Train","quantity":8},
            {"id":44,"region_id":4,"gift_name":"Action Figure","quantity":13},
            {"id":45,"region_id":3,"gift_name":"Lego Rocket","quantity":12},
            {"id":46,"region_id":3,"gift_name":"Toy Train","quantity":1},
            {"id":47,"region_id":2,"gift_name":"Toy Train","quantity":11},
            {"id":48,"region_id":5,"gift_name":"Action Figure","quantity":1},
            {"id":49,"region_id":4,"gift_name":"Toy Train","quantity":13},
            {"id":50,"region_id":5,"gift_name":"Action Figure","quantity":16},
            {"id":51,"region_id":4,"gift_name":"Toy Axe","quantity":19},
            {"id":52,"region_id":2,"gift_name":"Toy Train","quantity":14},
            {"id":53,"region_id":3,"gift_name":"Action Figure","quantity":16},
        ])