pub mod args;
use base64::{engine::general_purpose, Engine};
use reqwest::{
header::{HeaderValue, CONTENT_TYPE},
multipart::{Form, Part},
redirect::Policy,
StatusCode,
};
use tokio::{
sync::mpsc::Sender,
time::{sleep, Duration},
};
use tracing::info;
use uuid::Uuid;
pub const SUPPORTED_CHALLENGES: &[i32] = &[-1, 1, 4, 6, 7, 8, 11, 12, 13, 14, 15];
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(SubmissionState),
TaskCompleted(bool, i32),
LogLine(String),
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)) => {
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: 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,
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,
_ => 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;
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);
}
tx.send((true, 0).into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
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);
}
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;
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);
}
tx.send((true, 0).into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
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;
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);
}
tx.send((true, 0).into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
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_6(base_url: &str, tx: Sender<SubmissionUpdate>) -> ValidateResult {
let client = new_client();
let mut test: TaskTest;
let url = &format!("{}/6", base_url);
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);
}
tx.send((true, 0).into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
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);
}
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;
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);
}
tx.send((true, 0).into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
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, "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?;
tx.send((false, 120).into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
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?;
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;
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);
}
tx.send((true, 0).into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
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);
}
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;
test = (1, 1);
let url = &format!("{}/11/assets/decoration.png", base_url);
let res = client.get(url).send().await.map_err(|_| test)?;
if res.status() != StatusCode::OK {}
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);
}
tx.send((true, 0).into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
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);
}
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;
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);
}
tx.send((true, 0).into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
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);
}
tx.send((false, 100).into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
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);
}
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;
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);
}
tx.send((false, 0).into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
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);
}
tx.send((true, 0).into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
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},
]))
.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": "Action Figure"}) {
return Err(test);
}
tx.send((false, 100).into()).await.unwrap();
Ok(())
}
async fn validate_14(base_url: &str, tx: Sender<SubmissionUpdate>) -> ValidateResult {
let client = new_client();
let mut test: TaskTest;
test = (1, 1);
let url = &format!("{}/14/unsafe", base_url);
let res = client
.post(url)
.json(&serde_json::json!({"content": "Bing Chilling 🥶🍦"}))
.send()
.await
.map_err(|_| test)?;
let text = res.text().await.map_err(|_| test)?;
if text
!= "\
<html>
<head>
<title>CCH23 Day 14</title>
</head>
<body>
Bing Chilling 🥶🍦
</body>
</html>"
{
return Err(test);
}
test = (1, 2);
let res = client
.post(url)
.json(&serde_json::json!({"content": r#"<script>alert("XSS Attack Success!")</script>"#}))
.send()
.await
.map_err(|_| test)?;
let text = res.text().await.map_err(|_| test)?;
if text
!= "\
<html>
<head>
<title>CCH23 Day 14</title>
</head>
<body>
<script>alert(\"XSS Attack Success!\")</script>
</body>
</html>"
{
return Err(test);
}
tx.send((true, 0).into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
test = (2, 1);
let url = &format!("{}/14/safe", base_url);
let res = client
.post(url)
.json(&serde_json::json!({"content": r#"<script>alert("XSS Attack Failed!")</script>"#}))
.send()
.await
.map_err(|_| test)?;
let text = res.text().await.map_err(|_| test)?;
if text
!= "\
<html>
<head>
<title>CCH23 Day 14</title>
</head>
<body>
<script>alert("XSS Attack Failed!")</script>
</body>
</html>"
{
return Err(test);
}
tx.send((false, 100).into()).await.unwrap();
Ok(())
}
struct JSONTester {
client: reqwest::Client,
url: String,
}
impl JSONTester {
fn new(url: String) -> Self {
Self {
client: new_client(),
url,
}
}
async fn test(
&self,
test: TaskTest,
i: &serde_json::Value,
code: StatusCode,
o: &serde_json::Value,
) -> ValidateResult {
let res = self
.client
.post(&self.url)
.json(i)
.send()
.await
.map_err(|_| test)?;
if res.status() != code {
return Err(test);
}
let json = res.json::<serde_json::Value>().await.map_err(|_| test)?;
if json != *o {
return Err(test);
}
Ok(())
}
}
async fn validate_15(base_url: &str, tx: Sender<SubmissionUpdate>) -> ValidateResult {
let t = JSONTester::new(format!("{}/15/nice", base_url));
t.test(
(1, 1),
&serde_json::json!({"input": "hello there"}),
StatusCode::OK,
&serde_json::json!({"result": "nice"}),
)
.await?;
t.test(
(1, 2),
&serde_json::json!({"input": "he77o there"}),
StatusCode::BAD_REQUEST,
&serde_json::json!({"result": "naughty"}),
)
.await?;
t.test(
(1, 3),
&serde_json::json!({"input": "hello"}),
StatusCode::BAD_REQUEST,
&serde_json::json!({"result": "naughty"}),
)
.await?;
t.test(
(1, 4),
&serde_json::json!({"input": "hello xylophone"}),
StatusCode::BAD_REQUEST,
&serde_json::json!({"result": "naughty"}),
)
.await?;
t.test(
(1, 5),
&serde_json::json!({"input": "password"}),
StatusCode::BAD_REQUEST,
&serde_json::json!({"result": "naughty"}),
)
.await?;
let test = (1, 6);
let res = new_client()
.post(format!("{}/15/nice", base_url))
.header(CONTENT_TYPE, HeaderValue::from_static("application/json"))
.body("WooooOOOooOOOoooOO 👻")
.send()
.await
.map_err(|_| test)?;
if res.status() != StatusCode::BAD_REQUEST {
return Err(test);
}
tx.send((true, 0).into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
let t = JSONTester::new(format!("{}/15/game", base_url));
t.test(
(2, 1),
&serde_json::json!({"input": "mario"}),
StatusCode::BAD_REQUEST,
&serde_json::json!({"result": "naughty", "reason": "8 chars"}),
)
.await?;
t.test(
(2, 2),
&serde_json::json!({"input": "mariobro"}),
StatusCode::BAD_REQUEST,
&serde_json::json!({"result": "naughty", "reason": "more types of chars"}),
)
.await?;
t.test(
(2, 3),
&serde_json::json!({"input": "EEEEEEEEEEE"}),
StatusCode::BAD_REQUEST,
&serde_json::json!({"result": "naughty", "reason": "more types of chars"}),
)
.await?;
t.test(
(2, 4),
&serde_json::json!({"input": "E3E3E3E3E3E"}),
StatusCode::BAD_REQUEST,
&serde_json::json!({"result": "naughty", "reason": "more types of chars"}),
)
.await?;
t.test(
(2, 5),
&serde_json::json!({"input": "e3E3e#eE#ee3#EeE3"}),
StatusCode::BAD_REQUEST,
&serde_json::json!({"result": "naughty", "reason": "55555"}),
)
.await?;
t.test(
(2, 6),
&serde_json::json!({"input": "Password12345"}),
StatusCode::BAD_REQUEST,
&serde_json::json!({"result": "naughty", "reason": "math is hard"}),
)
.await?;
t.test(
(2, 7),
&serde_json::json!({"input": "2 00 2 3 OOgaBooga"}),
StatusCode::BAD_REQUEST,
&serde_json::json!({"result": "naughty", "reason": "math is hard"}),
)
.await?;
t.test(
(2, 8),
&serde_json::json!({"input": "2+2/2-8*8 = 1-2000 OOgaBooga"}),
StatusCode::NOT_ACCEPTABLE,
&serde_json::json!({"result": "naughty", "reason": "not joyful enough"}),
)
.await?;
t.test(
(2, 9),
&serde_json::json!({"input": "2000.23.A yoyoj"}),
StatusCode::NOT_ACCEPTABLE,
&serde_json::json!({"result": "naughty", "reason": "not joyful enough"}),
)
.await?;
t.test(
(2, 10),
&serde_json::json!({"input": "2000.23.A joy joy"}),
StatusCode::NOT_ACCEPTABLE,
&serde_json::json!({"result": "naughty", "reason": "not joyful enough"}),
)
.await?;
t.test(
(2, 11),
&serde_json::json!({"input": "2000.23.A joyo"}),
StatusCode::NOT_ACCEPTABLE,
&serde_json::json!({"result": "naughty", "reason": "not joyful enough"}),
)
.await?;
t.test(
(2, 12),
&serde_json::json!({"input": "2000.23.A j ;) o ;) y "}),
StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS,
&serde_json::json!({"result": "naughty", "reason": "illegal: no sandwich"}),
)
.await?;
t.test(
(2, 13),
&serde_json::json!({"input": "2020.3.A j ;) o ;) y"}),
StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS,
&serde_json::json!({"result": "naughty", "reason": "illegal: no sandwich"}),
)
.await?;
t.test(
(2, 14),
&serde_json::json!({"input": "2000.23.A j ;) o ;) y AzA"}),
StatusCode::RANGE_NOT_SATISFIABLE,
&serde_json::json!({"result": "naughty", "reason": "outranged"}),
)
.await?;
t.test(
(2, 15),
&serde_json::json!({"input": "2000.23.A j ;) o ;) y⥿ AzA"}),
StatusCode::RANGE_NOT_SATISFIABLE,
&serde_json::json!({"result": "naughty", "reason": "outranged"}),
)
.await?;
t.test(
(2, 16),
&serde_json::json!({"input": "2000.23.A j ;) o ;) y ⦄AzA"}),
StatusCode::UPGRADE_REQUIRED,
&serde_json::json!({"result": "naughty", "reason": "😳"}),
)
.await?;
t.test(
(2, 17),
&serde_json::json!({"input": "2000.23.A j 🥶 o 🍦 y ⦄AzA"}),
StatusCode::IM_A_TEAPOT,
&serde_json::json!({"result": "naughty", "reason": "not a coffee brewer"}),
)
.await?;
t.test(
(2, 18),
&serde_json::json!({"input": "2000.23.A j ⦖⦖⦖⦖⦖⦖⦖⦖ 🥶 o 🍦 y ⦄AzA"}),
StatusCode::OK,
&serde_json::json!({"result": "nice", "reason": "that's a nice password"}),
)
.await?;
tx.send((false, 400).into()).await.unwrap();
Ok(())
}