cch24_validator/
lib.rs

1pub mod args;
2
3use chrono::{DateTime, TimeDelta, Utc};
4use html_compare_rs::{HtmlCompareOptions, HtmlComparer};
5use jsonwebtoken::decode_header;
6use reqwest::{
7    header::{self, HeaderValue},
8    multipart::{Form, Part},
9    redirect::Policy,
10    Client, StatusCode,
11};
12use serde_json::json;
13use shuttlings::{SubmissionState, SubmissionUpdate};
14use tokio::{
15    sync::mpsc::Sender,
16    time::{sleep, Duration},
17};
18use tracing::info;
19use uuid::Uuid;
20
21pub const SUPPORTED_CHALLENGES: &[&str] = &["-1", "2", "5", "9", "12", "16", "19", "23"];
22pub const SUBMISSION_TIMEOUT: u64 = 60;
23
24pub async fn run(url: String, id: Uuid, number: &str, tx: Sender<SubmissionUpdate>) {
25    info!(%id, %url, %number, "Starting submission");
26
27    tx.send(SubmissionState::Running.into()).await.unwrap();
28    tx.send(SubmissionUpdate::Save).await.unwrap();
29
30    tokio::select! {
31        _ = validate(url.as_str(), number, tx.clone()) => (),
32        _ = sleep(Duration::from_secs(SUBMISSION_TIMEOUT)) => {
33            // if the validation task timed out
34            info!(%id, %url, %number, "Submission timed out");
35            tx.send("Timed out".to_owned().into()).await.unwrap();
36            tx.send(SubmissionState::Done.into()).await.unwrap();
37            tx.send(SubmissionUpdate::Save).await.unwrap();
38        },
39    };
40    info!(%id, %url, %number, "Completed submission");
41}
42
43/// Task number and Test number in the current challenge
44type TaskTest = (i32, i32);
45/// If failure, return tuple with task number and test number that failed
46type ValidateResult = std::result::Result<(), TaskTest>;
47
48pub async fn validate(url: &str, number: &str, tx: Sender<SubmissionUpdate>) {
49    let txc = tx.clone();
50    if let Err((task, test)) = match number {
51        "-1" => validate_minus1(url, txc).await,
52        "2" => validate_2(url, txc).await,
53        "5" => validate_5(url, txc).await,
54        "9" => validate_9(url, txc).await,
55        "12" => validate_12(url, txc).await,
56        "16" => validate_16(url, txc).await,
57        "19" => validate_19(url, txc).await,
58        "23" => validate_23(url, txc).await,
59        _ => {
60            tx.send(
61                format!("Validating Challenge {number} is not supported yet! Check for updates.")
62                    .into(),
63            )
64            .await
65            .unwrap();
66            return;
67        }
68    } {
69        info!(%url, %number, %task, %test, "Submission failed");
70        tx.send(format!("Task {task}: test #{test} failed 🟥").into())
71            .await
72            .unwrap();
73    }
74    tx.send(SubmissionState::Done.into()).await.unwrap();
75    tx.send(SubmissionUpdate::Save).await.unwrap();
76}
77
78fn new_client_base() -> reqwest::ClientBuilder {
79    reqwest::ClientBuilder::new()
80        .http1_only()
81        .connect_timeout(Duration::from_secs(3))
82        .redirect(Policy::limited(3))
83        .referer(false)
84        .timeout(Duration::from_secs(60))
85}
86fn new_client() -> reqwest::Client {
87    new_client_base().build().unwrap()
88}
89fn new_client_with_cookies() -> reqwest::Client {
90    new_client_base().cookie_store(true).build().unwrap()
91}
92
93macro_rules! assert_status {
94    ($res:expr, $test:expr, $expected_status:expr) => {
95        if $res.status() != $expected_status {
96            return Err($test);
97        }
98    };
99}
100
101macro_rules! assert_text {
102    ($res:expr, $test:expr, $expected_text:expr) => {
103        if $res.text().await.map_err(|_| $test)? != $expected_text {
104            return Err($test);
105        }
106    };
107}
108
109macro_rules! assert_json {
110    ($res:expr, $test:expr, $expected_json:expr) => {
111        if $res.json::<serde_json::Value>().await.map_err(|_| $test)? != $expected_json {
112            return Err($test);
113        }
114    };
115}
116
117macro_rules! assert_text_starts_with {
118    ($res:expr, $test:expr, $expected_text:expr) => {
119        if !$res
120            .text()
121            .await
122            .map_err(|_| $test)?
123            .starts_with($expected_text)
124        {
125            return Err($test);
126        }
127    };
128}
129
130macro_rules! assert_ {
131    ($test:expr, $expected_true:expr) => {
132        if !$expected_true {
133            return Err($test);
134        }
135    };
136}
137
138macro_rules! assert_eq_ {
139    ($test:expr, $left:expr, $right:expr) => {
140        if $left != $right {
141            return Err($test);
142        }
143    };
144}
145
146macro_rules! assert_neq_ {
147    ($test:expr, $left:expr, $right:expr) => {
148        if $left == $right {
149            return Err($test);
150        }
151    };
152}
153
154async fn validate_minus1(base_url: &str, tx: Sender<SubmissionUpdate>) -> ValidateResult {
155    let client = new_client();
156    let mut test: TaskTest;
157    // TASK 1: respond 200 with Hello, bird!
158    test = (1, 1);
159    let url = &format!("{}/", base_url);
160    let res = client.get(url).send().await.map_err(|_| test)?;
161    assert_status!(res, test, StatusCode::OK);
162    assert_text!(res, test, "Hello, bird!");
163    // TASK 1 DONE
164    tx.send((true, 0).into()).await.unwrap();
165    tx.send(SubmissionUpdate::Save).await.unwrap();
166
167    // TASK 2: respond 302
168    test = (2, 1);
169    let url = &format!("{}/-1/seek", base_url);
170    let client_no_redir = reqwest::ClientBuilder::new()
171        .http1_only()
172        .connect_timeout(Duration::from_secs(3))
173        .redirect(Policy::none())
174        .referer(false)
175        .timeout(Duration::from_secs(60))
176        .build()
177        .unwrap();
178    let res = client_no_redir.get(url).send().await.map_err(|_| test)?;
179    assert_status!(res, test, StatusCode::FOUND);
180    if res.headers().get(header::LOCATION)
181        != Some(&HeaderValue::from_static(
182            "https://www.youtube.com/watch?v=9Gc4QTqslN4",
183        ))
184    {
185        return Err(test);
186    }
187    // TASK 2 DONE
188    tx.send((false, 0).into()).await.unwrap();
189
190    Ok(())
191}
192
193async fn validate_2(base_url: &str, tx: Sender<SubmissionUpdate>) -> ValidateResult {
194    let client = new_client();
195    let mut test: TaskTest;
196    // TASK 1: Ipv4 dest
197    test = (1, 1);
198    let url = &format!("{}/2/dest?from=10.0.0.0&key=1.2.3.255", base_url);
199    let res = client.get(url).send().await.map_err(|_| test)?;
200    assert_text!(res, test, "11.2.3.255");
201    test = (1, 2);
202    let url = &format!("{}/2/dest?from=128.128.33.0&key=255.0.255.33", base_url);
203    let res = client.get(url).send().await.map_err(|_| test)?;
204    assert_text!(res, test, "127.128.32.33");
205    test = (1, 3);
206    let url = &format!("{}/2/dest?from=192.168.0.1&key=72.96.8.7", base_url);
207    let res = client.get(url).send().await.map_err(|_| test)?;
208    assert_text!(res, test, "8.8.8.8");
209    // TASK 1 DONE
210    tx.send((false, 0).into()).await.unwrap();
211    tx.send(SubmissionUpdate::Save).await.unwrap();
212
213    // TASK 2: Ipv4 key
214    test = (2, 1);
215    let url = &format!("{}/2/key?from=10.0.0.0&to=11.2.3.255", base_url);
216    let res = client.get(url).send().await.map_err(|_| test)?;
217    assert_text!(res, test, "1.2.3.255");
218    test = (2, 2);
219    let url = &format!("{}/2/key?from=128.128.33.0&to=127.128.32.33", base_url);
220    let res = client.get(url).send().await.map_err(|_| test)?;
221    assert_text!(res, test, "255.0.255.33");
222    test = (2, 3);
223    let url = &format!("{}/2/key?from=192.168.0.1&to=8.8.8.8", base_url);
224    let res = client.get(url).send().await.map_err(|_| test)?;
225    assert_text!(res, test, "72.96.8.7");
226    // TASK 2 DONE
227    tx.send((true, 0).into()).await.unwrap();
228    tx.send(SubmissionUpdate::Save).await.unwrap();
229
230    // TASK 3: Ipv6
231    test = (3, 1);
232    let url = &format!("{}/2/v6/dest?from=fe80::1&key=5:6:7::3333", base_url);
233    let res = client.get(url).send().await.map_err(|_| test)?;
234    assert_text!(res, test, "fe85:6:7::3332");
235    test = (3, 2);
236    let url = &format!(
237        "{}/2/v6/dest?from=aaaa:0:0:0::aaaa&key=ffff:ffff:c:0:0:c:1234:ffff",
238        base_url
239    );
240    let res = client.get(url).send().await.map_err(|_| test)?;
241    assert_text!(res, test, "5555:ffff:c::c:1234:5555");
242    test = (3, 3);
243    let url = &format!(
244        "{}/2/v6/dest?from=feed:beef:deaf:bad:cafe::&key=::dab:bed:ace:dad",
245        base_url
246    );
247    let res = client.get(url).send().await.map_err(|_| test)?;
248    assert_text!(res, test, "feed:beef:deaf:bad:c755:bed:ace:dad");
249    test = (3, 4);
250    let url = &format!("{}/2/v6/key?from=fe80::1&to=fe85:6:7::3332", base_url);
251    let res = client.get(url).send().await.map_err(|_| test)?;
252    assert_text!(res, test, "5:6:7::3333");
253    test = (3, 5);
254    let url = &format!(
255        "{}/2/v6/key?from=aaaa::aaaa&to=5555:ffff:c:0:0:c:1234:5555",
256        base_url
257    );
258    let res = client.get(url).send().await.map_err(|_| test)?;
259    assert_text!(res, test, "ffff:ffff:c::c:1234:ffff");
260    test = (3, 6);
261    let url = &format!(
262        "{}/2/v6/key?from=feed:beef:deaf:bad:cafe::&to=feed:beef:deaf:bad:c755:bed:ace:dad",
263        base_url
264    );
265    let res = client.get(url).send().await.map_err(|_| test)?;
266    assert_text!(res, test, "::dab:bed:ace:dad");
267    // TASK 3 DONE
268    tx.send((false, 50).into()).await.unwrap();
269    tx.send(SubmissionUpdate::Save).await.unwrap();
270
271    Ok(())
272}
273
274async fn validate_5(base_url: &str, tx: Sender<SubmissionUpdate>) -> ValidateResult {
275    let client = new_client();
276    let mut test: TaskTest;
277    let url = &format!("{}/5/manifest", base_url);
278    const CT: &str = "Content-Type";
279    const TOML: &str = "application/toml";
280    const YAML: &str = "application/yaml";
281    const JSON: &str = "application/json";
282    // TASK 1: order list
283    test = (1, 1);
284    let res = client
285        .post(url)
286        .header(CT, TOML)
287        .body(
288            r#"
289[package]
290name = "not-a-gift-order"
291authors = ["Not Santa"]
292keywords = ["Christmas 2024"]
293
294[[package.metadata.orders]]
295item = "Toy car"
296quantity = 2
297
298[[package.metadata.orders]]
299item = "Lego brick"
300quantity = 230
301"#,
302        )
303        .send()
304        .await
305        .map_err(|_| test)?;
306    assert_status!(res, test, StatusCode::OK);
307    assert_text!(res, test, "Toy car: 2\nLego brick: 230");
308    test = (1, 2);
309    let res = client
310        .post(url)
311        .header(CT, TOML)
312        .body(
313            r#"
314[package]
315name = "coal-in-a-bowl"
316authors = ["H4CK3R_13E7"]
317keywords = ["Christmas 2024"]
318
319[[package.metadata.orders]]
320item = "Coal"
321quantity = "Hahaha get rekt"
322"#,
323        )
324        .send()
325        .await
326        .map_err(|_| test)?;
327    assert_status!(res, test, StatusCode::NO_CONTENT);
328    test = (1, 3);
329    let res = client
330        .post(url)
331        .header(CT, TOML)
332        .body(
333            r#"
334[package]
335name = "coal-in-a-bowl"
336authors = ["H4CK3R_13E7"]
337keywords = ["Christmas 2024"]
338
339package.metadata.orders = []
340"#,
341        )
342        .send()
343        .await
344        .map_err(|_| test)?;
345    assert_status!(res, test, StatusCode::NO_CONTENT);
346    test = (1, 4);
347    let res = client
348        .post(url)
349        .header(CT, TOML)
350        .body(
351            r#"
352[package]
353name = "not-a-gift-order"
354authors = ["Not Santa"]
355keywords = ["Christmas 2024"]
356
357[[package.metadata.orders]]
358item = "Toy car"
359quantity = 2
360
361[[package.metadata.orders]]
362item = "Lego brick"
363quantity = 1.5
364
365[[package.metadata.orders]]
366item = "Doll"
367quantity = 2
368
369[[package.metadata.orders]]
370quantity = 5
371item = "Cookie:::\n"
372
373[[package.metadata.orders]]
374item = "Thing"
375count = 3
376"#,
377        )
378        .send()
379        .await
380        .map_err(|_| test)?;
381    assert_status!(res, test, StatusCode::OK);
382    assert_text!(res, test, "Toy car: 2\nDoll: 2\nCookie:::\n: 5");
383    // TASK 1 DONE
384    tx.send((false, 0).into()).await.unwrap();
385    tx.send(SubmissionUpdate::Save).await.unwrap();
386
387    // TASK 2: manifest parsing
388    test = (2, 1);
389    let res = client
390        .post(url)
391        .header(CT, TOML)
392        .body(
393            r#"
394[package]
395name = false
396authors = ["Not Santa"]
397keywords = ["Christmas 2024"]
398"#,
399        )
400        .send()
401        .await
402        .map_err(|_| test)?;
403    assert_status!(res, test, StatusCode::BAD_REQUEST);
404    assert_text!(res, test, "Invalid manifest");
405    test = (2, 2);
406    let res = client
407        .post(url)
408        .header(CT, TOML)
409        .body(
410            r#"
411[package]
412name = "not-a-gift-order"
413authors = ["Not Santa"]
414keywords = ["Christmas 2024"]
415
416[profile.release]
417incremental = "stonks"
418"#,
419        )
420        .send()
421        .await
422        .map_err(|_| test)?;
423    assert_status!(res, test, StatusCode::BAD_REQUEST);
424    assert_text!(res, test, "Invalid manifest");
425    test = (2, 3);
426    let res = client
427        .post(url)
428        .header(CT, TOML)
429        .body(
430            r#"
431[package]
432name = "big-chungus"
433version = "2.0.24"
434edition = "2024"
435resolver = "2"
436readme.workspace = true
437keywords = ["Christmas 2024"]
438
439[dependencies]
440shuttle-runtime = "1.0.0+when"
441
442[target.shuttlings.dependencies]
443cch24-validator = "5+more"
444
445[profile.release]
446incremental = false
447
448[package.metadata.stuff]
449thing = ["yes", "no"]
450"#,
451        )
452        .send()
453        .await
454        .map_err(|_| test)?;
455    assert_status!(res, test, StatusCode::NO_CONTENT);
456    test = (2, 4);
457    let res = client
458        .post(url)
459        .header(CT, TOML)
460        .body(
461            r#"
462[package]
463name = "chig-bungus"
464edition = "2023"
465
466[workspace.dependencies]
467shuttle-bring-your-own-cloud = "0.0.0"
468"#,
469        )
470        .send()
471        .await
472        .map_err(|_| test)?;
473    assert_status!(res, test, StatusCode::BAD_REQUEST);
474    assert_text!(res, test, "Invalid manifest");
475    test = (2, 5);
476    let res = client
477        .post(url)
478        .header(CT, TOML)
479        .body(
480            r#"
481[package]
482name = "chig-bungus"
483
484[workspace]
485resolver = "135"
486
487[workspace.dependencies]
488shuttle-bring-your-own-cloud = "0.0.0"
489"#,
490        )
491        .send()
492        .await
493        .map_err(|_| test)?;
494    assert_status!(res, test, StatusCode::BAD_REQUEST);
495    assert_text!(res, test, "Invalid manifest");
496    // TASK 2 DONE
497    tx.send((false, 0).into()).await.unwrap();
498    tx.send(SubmissionUpdate::Save).await.unwrap();
499
500    // TASK 3: keyword
501    test = (3, 1);
502    let res = client
503        .post(url)
504        .header(CT, TOML)
505        .body(
506            r#"
507[package]
508name = "grass"
509authors = ["A vegan cow"]
510keywords = ["Moooooo"]
511"#,
512        )
513        .send()
514        .await
515        .map_err(|_| test)?;
516    assert_status!(res, test, StatusCode::BAD_REQUEST);
517    assert_text!(res, test, "Magic keyword not provided");
518    test = (3, 2);
519    let res = client
520        .post(url)
521        .header(CT, TOML)
522        .body(
523            r#"
524[package]
525name = "chig-bungus"
526
527[workspace]
528resolver = "2"
529
530[workspace.dependencies]
531shuttle-bring-your-own-cloud = "0.0.0"
532"#,
533        )
534        .send()
535        .await
536        .map_err(|_| test)?;
537    assert_status!(res, test, StatusCode::BAD_REQUEST);
538    assert_text!(res, test, "Magic keyword not provided");
539    test = (3, 3);
540    let res = client
541        .post(url)
542        .header(CT, TOML)
543        .body(
544            r#"
545[package]
546name = "slurp"
547authors = ["A crazy cow"]
548keywords = ["MooOooooooOOOOoo00oo=oOooooo", "Mew", "Moh", "Christmas 2024"]
549metadata.orders = [{ item = "Milk 🥛", quantity = 1 }]
550"#,
551        )
552        .send()
553        .await
554        .map_err(|_| test)?;
555    assert_status!(res, test, StatusCode::OK);
556    assert_text!(res, test, "Milk 🥛: 1");
557    test = (3, 4);
558    let res = client
559        .post(url)
560        .header(CT, TOML)
561        .body(
562            r#"
563[package]
564name = "snow"
565authors = ["The Cow of Christmas"]
566keywords = ["Moooooo Merry Christmas 2024"]
567"#,
568        )
569        .send()
570        .await
571        .map_err(|_| test)?;
572    assert_status!(res, test, StatusCode::BAD_REQUEST);
573    assert_text!(res, test, "Magic keyword not provided");
574    // TASK 3 DONE
575    tx.send((true, 0).into()).await.unwrap();
576    tx.send(SubmissionUpdate::Save).await.unwrap();
577
578    // TASK 4: Yaml, Json
579    test = (4, 1);
580    let res = client
581        .post(url)
582        .header(CT, "text/html")
583        .body("<h1>Hello, bird!</h1>")
584        .send()
585        .await
586        .map_err(|_| test)?;
587    assert_status!(res, test, StatusCode::UNSUPPORTED_MEDIA_TYPE);
588    test = (4, 2);
589    let res = client
590        .post(url)
591        .header(CT, YAML)
592        .body(
593            r#"
594package:
595  name: big-chungus-sleigh
596  version: "2.0.24"
597  metadata:
598    orders:
599      - item: "Toy train"
600        quantity: 5
601      - item: "Toy car"
602        quantity: 3
603  rust-version: "1.69"
604  keywords:
605    - "Christmas 2024"
606"#,
607        )
608        .send()
609        .await
610        .map_err(|_| test)?;
611    assert_status!(res, test, StatusCode::OK);
612    assert_text!(res, test, "Toy train: 5\nToy car: 3");
613    test = (4, 3);
614    let res = client
615        .post(url)
616        .header(CT, YAML)
617        .body(
618            r#"
619package:
620  name: big-chungus-sleigh
621  metadata:
622    orders:
623      - item: "Toy train"
624        quantity: 5
625      - item: "Coal"
626      - item: "Horse"
627        quantity: 2
628  keywords:
629    - "Christmas 2024"
630"#,
631        )
632        .send()
633        .await
634        .map_err(|_| test)?;
635    assert_status!(res, test, StatusCode::OK);
636    assert_text!(res, test, "Toy train: 5\nHorse: 2");
637    test = (4, 4);
638    let res = client
639        .post(url)
640        .header(CT, YAML)
641        .body(
642            r#"
643package:
644  name: big-chungus-sleigh
645  metadata:
646    orders:
647      - item: "Toy train"
648        quantity: 5
649  rust-version: true
650  keywords:
651    - "Christmas 2024"
652"#,
653        )
654        .send()
655        .await
656        .map_err(|_| test)?;
657    assert_status!(res, test, StatusCode::BAD_REQUEST);
658    assert_text!(res, test, "Invalid manifest");
659    test = (4, 5);
660    let res = client
661        .post(url)
662        .header(CT, JSON)
663        .body(
664            r#"
665{
666  "package": {
667    "name": "big-chungus-sleigh",
668    "version": "2.0.24",
669    "metadata": {
670      "orders": [
671        {
672          "item": "Toy train",
673          "quantity": 5
674        },
675        {
676          "item": "Toy car",
677          "quantity": 3
678        }
679      ]
680    },
681    "rust-version": "1.69",
682    "keywords": [
683      "Christmas 2024"
684    ]
685  }
686}
687"#,
688        )
689        .send()
690        .await
691        .map_err(|_| test)?;
692    assert_status!(res, test, StatusCode::OK);
693    assert_text!(res, test, "Toy train: 5\nToy car: 3");
694    test = (4, 6);
695    let res = client
696        .post(url)
697        .header(CT, JSON)
698        .body(
699            r#"
700{
701  "package": {
702    "name": "big-chungus-sleigh",
703    "metadata": {
704      "orders": [
705        {
706          "item": "Toy train",
707          "quantity": 5
708        },
709        {
710          "item": "Coal"
711        },
712        {
713          "item": "Horse",
714          "quantity": 2
715        }
716      ]
717    },
718    "keywords": [
719      "Christmas 2024"
720    ]
721  }
722}
723"#,
724        )
725        .send()
726        .await
727        .map_err(|_| test)?;
728    assert_status!(res, test, StatusCode::OK);
729    assert_text!(res, test, "Toy train: 5\nHorse: 2");
730    test = (4, 7);
731    let res = client
732        .post(url)
733        .header(CT, JSON)
734        .body(
735            r#"
736{
737  "package": {
738    "name": "big-chungus-sleigh",
739    "metadata": {
740      "orders": [
741        {
742          "item": "Toy train",
743          "quantity": 5
744        }
745      ]
746    }
747  }
748}
749"#,
750        )
751        .send()
752        .await
753        .map_err(|_| test)?;
754    assert_status!(res, test, StatusCode::BAD_REQUEST);
755    assert_text!(res, test, "Magic keyword not provided");
756    // TASK 4 DONE
757    tx.send((false, 70).into()).await.unwrap();
758    tx.send(SubmissionUpdate::Save).await.unwrap();
759
760    Ok(())
761}
762
763async fn validate_9(base_url: &str, tx: Sender<SubmissionUpdate>) -> ValidateResult {
764    let client = new_client();
765    let mut test: TaskTest;
766    // TASK 1: leaky bucket
767    test = (1, 1);
768    let url = &format!("{}/9/milk", base_url);
769    let start = Utc::now();
770    let res = client.post(url).send().await.map_err(|_| test)?;
771    assert_status!(res, test, StatusCode::OK);
772    assert_text!(res, test, "Milk withdrawn\n");
773    let res = client.post(url).send().await.map_err(|_| test)?;
774    assert_status!(res, test, StatusCode::OK);
775    assert_text!(res, test, "Milk withdrawn\n");
776    let res = client.post(url).send().await.map_err(|_| test)?;
777    assert_status!(res, test, StatusCode::OK);
778    assert_text!(res, test, "Milk withdrawn\n");
779    let res = client.post(url).send().await.map_err(|_| test)?;
780    assert_status!(res, test, StatusCode::OK);
781    assert_text!(res, test, "Milk withdrawn\n");
782    let res = client.post(url).send().await.map_err(|_| test)?;
783    assert_status!(res, test, StatusCode::OK);
784    assert_text!(res, test, "Milk withdrawn\n");
785    let end = Utc::now();
786    if end - start > TimeDelta::milliseconds(500) {
787        tx.send(SubmissionUpdate::LogLine(
788            "Info: High network latency detected. This test is timing-sensitive and might therefore fail.".to_owned()
789        ))
790        .await
791        .unwrap();
792    }
793    let res = client.post(url).send().await.map_err(|_| test)?;
794    assert_status!(res, test, StatusCode::TOO_MANY_REQUESTS);
795    assert_text!(res, test, "No milk available\n");
796    sleep(Duration::from_secs(1)).await;
797    let res = client.post(url).send().await.map_err(|_| test)?;
798    assert_status!(res, test, StatusCode::OK);
799    assert_text!(res, test, "Milk withdrawn\n");
800    let res = client.post(url).send().await.map_err(|_| test)?;
801    assert_status!(res, test, StatusCode::TOO_MANY_REQUESTS);
802    assert_text!(res, test, "No milk available\n");
803    sleep(Duration::from_secs(2)).await;
804    let res = client.post(url).send().await.map_err(|_| test)?;
805    assert_status!(res, test, StatusCode::OK);
806    assert_text!(res, test, "Milk withdrawn\n");
807    let res = client.post(url).send().await.map_err(|_| test)?;
808    assert_status!(res, test, StatusCode::OK);
809    assert_text!(res, test, "Milk withdrawn\n");
810    let res = client.post(url).send().await.map_err(|_| test)?;
811    assert_status!(res, test, StatusCode::TOO_MANY_REQUESTS);
812    assert_text!(res, test, "No milk available\n");
813    let res = client.post(url).send().await.map_err(|_| test)?;
814    assert_status!(res, test, StatusCode::TOO_MANY_REQUESTS);
815    assert_text!(res, test, "No milk available\n");
816    // TASK 1 DONE
817    tx.send((false, 0).into()).await.unwrap();
818    tx.send(SubmissionUpdate::Save).await.unwrap();
819
820    // reset bucket
821    sleep(Duration::from_secs(5)).await;
822
823    // TASK 2: gallons
824    test = (2, 1);
825    let res = client
826        .post(url)
827        .json(&json!({"liters": 2}))
828        .send()
829        .await
830        .map_err(|_| test)?;
831    assert_status!(res, test, StatusCode::OK);
832    let j = res.json::<serde_json::Value>().await.map_err(|_| test)?;
833    assert_!(
834        test,
835        j.as_object().is_some_and(|o| o.len() == 1
836            && o.get("gallons").is_some_and(|g| g
837                .as_f64()
838                .is_some_and(|f| (f / 0.5283441 - 1.0).abs() < 0.0001)))
839    );
840    test = (2, 2);
841    let res = client
842        .post(url)
843        .json(&json!({"gallons": -2.000000000000001}))
844        .send()
845        .await
846        .map_err(|_| test)?;
847    assert_status!(res, test, StatusCode::OK);
848    let j = res.json::<serde_json::Value>().await.map_err(|_| test)?;
849    assert_!(
850        test,
851        j.as_object().is_some_and(|o| o.len() == 1
852            && o.get("liters").is_some_and(|g| g
853                .as_f64()
854                .is_some_and(|f| (f / -7.5708237 - 1.0).abs() < 0.0001)))
855    );
856    test = (2, 3);
857    let res = client.post(url).send().await.map_err(|_| test)?;
858    assert_status!(res, test, StatusCode::OK);
859    assert_text!(res, test, "Milk withdrawn\n");
860    test = (2, 4);
861    let res = client
862        .post(url)
863        .json(&json!({}))
864        .send()
865        .await
866        .map_err(|_| test)?;
867    assert_status!(res, test, StatusCode::BAD_REQUEST);
868    test = (2, 5);
869    let res = client
870        .post(url)
871        .json(&json!({"liters": 0, "gallons": 1337}))
872        .send()
873        .await
874        .map_err(|_| test)?;
875    assert_status!(res, test, StatusCode::BAD_REQUEST);
876    test = (2, 6);
877    let res = client.post(url).send().await.map_err(|_| test)?;
878    assert_status!(res, test, StatusCode::TOO_MANY_REQUESTS);
879    assert_text!(res, test, "No milk available\n");
880    test = (2, 7);
881    sleep(Duration::from_secs(1)).await;
882    let res = client
883        .post(url)
884        .header("Content-Type", "application/json")
885        .body("")
886        .send()
887        .await
888        .map_err(|_| test)?;
889    assert_status!(res, test, StatusCode::BAD_REQUEST);
890    test = (2, 8);
891    sleep(Duration::from_secs(1)).await;
892    let res = client
893        .post(url)
894        .header("Content-Type", "application/json")
895        .body("")
896        .send()
897        .await
898        .map_err(|_| test)?;
899    assert_status!(res, test, StatusCode::BAD_REQUEST);
900    test = (2, 9);
901    sleep(Duration::from_secs(1)).await;
902    let res = client
903        .post(url)
904        .header("Content-Type", "application/json")
905        .body("{'liters':0}")
906        .send()
907        .await
908        .map_err(|_| test)?;
909    assert_status!(res, test, StatusCode::BAD_REQUEST);
910    test = (2, 10);
911    sleep(Duration::from_secs(1)).await;
912    let res = client
913        .post(url)
914        // (incoming f32 is truncated)
915        .json(&json!({"liters": 123123123123.0}))
916        .send()
917        .await
918        .map_err(|_| test)?;
919    assert_status!(res, test, StatusCode::OK);
920    let j = res.json::<serde_json::Value>().await.map_err(|_| test)?;
921    assert_!(
922        test,
923        j.as_object().is_some_and(|o| o.len() == 1
924            && o.get("gallons").is_some_and(|g| g
925                .as_f64()
926                .is_some_and(|f| (f / 32525687000.0 - 1.0).abs() < 0.0001)))
927    );
928    test = (2, 11);
929    sleep(Duration::from_secs(1)).await;
930    let res = client
931        .post(url)
932        .header("Content-Type", "text/html")
933        .body(r#"{"liters":0}"#)
934        .send()
935        .await
936        .map_err(|_| test)?;
937    assert_status!(res, test, StatusCode::OK);
938    assert_text!(res, test, "Milk withdrawn\n");
939    // TASK 2 DONE
940    tx.send((false, 0).into()).await.unwrap();
941    tx.send(SubmissionUpdate::Save).await.unwrap();
942
943    // reset bucket
944    sleep(Duration::from_secs(5)).await;
945
946    // TASK 3: litres/pints
947    test = (3, 1);
948    let res = client
949        .post(url)
950        .json(&json!({"litres": 7.4}))
951        .send()
952        .await
953        .map_err(|_| test)?;
954    assert_status!(res, test, StatusCode::OK);
955    let j = res.json::<serde_json::Value>().await.map_err(|_| test)?;
956    assert_!(
957        test,
958        j.as_object().is_some_and(|o| o.len() == 1
959            && o.get("pints").is_some_and(|g| g
960                .as_f64()
961                .is_some_and(|f| (f / 13.02218 - 1.0).abs() < 0.0001)))
962    );
963    test = (3, 2);
964    let res = client
965        .post(url)
966        .json(&json!({"pints": 32630.25}))
967        .send()
968        .await
969        .map_err(|_| test)?;
970    assert_status!(res, test, StatusCode::OK);
971    let j = res.json::<serde_json::Value>().await.map_err(|_| test)?;
972    assert_!(
973        test,
974        j.as_object().is_some_and(|o| o.len() == 1
975            && o.get("litres").is_some_and(|g| g
976                .as_f64()
977                .is_some_and(|f| (f / 18542.508 - 1.0).abs() < 0.0001)))
978    );
979    test = (3, 3);
980    let res = client
981        .post(url)
982        .json(&json!({"litres": -0.0}))
983        .send()
984        .await
985        .map_err(|_| test)?;
986    assert_status!(res, test, StatusCode::OK);
987    let j = res.json::<serde_json::Value>().await.map_err(|_| test)?;
988    assert_!(
989        test,
990        j.as_object().is_some_and(|o| o.len() == 1
991            && o.get("pints")
992                .is_some_and(|g| g.as_f64().is_some_and(|f| f == 0.0)))
993    );
994    test = (3, 4);
995    let res = client
996        .post(url)
997        .json(&json!({"litres": 7.4, "liters": 7.4}))
998        .send()
999        .await
1000        .map_err(|_| test)?;
1001    assert_status!(res, test, StatusCode::BAD_REQUEST);
1002    test = (3, 5);
1003    let res = client
1004        .post(url)
1005        .json(r#"{"litres": 7.4, "litres": 7.6}"#)
1006        .send()
1007        .await
1008        .map_err(|_| test)?;
1009    assert_status!(res, test, StatusCode::BAD_REQUEST);
1010    test = (3, 6);
1011    sleep(Duration::from_secs(1)).await;
1012    let res = client
1013        .post(url)
1014        .json(&json!({"gallons": 2, "pints": 0}))
1015        .send()
1016        .await
1017        .map_err(|_| test)?;
1018    assert_status!(res, test, StatusCode::BAD_REQUEST);
1019    test = (3, 7);
1020    let res = client.post(url).send().await.map_err(|_| test)?;
1021    assert_status!(res, test, StatusCode::TOO_MANY_REQUESTS);
1022    assert_text!(res, test, "No milk available\n");
1023    // TASK 3 DONE
1024    tx.send((true, 0).into()).await.unwrap();
1025    tx.send(SubmissionUpdate::Save).await.unwrap();
1026
1027    // TASK 4: refill
1028    test = (4, 1);
1029    let refill_url = &format!("{}/9/refill", base_url);
1030    let res = client.post(refill_url).send().await.map_err(|_| test)?;
1031    assert_status!(res, test, StatusCode::OK);
1032    test = (4, 2);
1033    let res = client.post(url).send().await.map_err(|_| test)?;
1034    assert_status!(res, test, StatusCode::OK);
1035    assert_text!(res, test, "Milk withdrawn\n");
1036    let res = client.post(url).send().await.map_err(|_| test)?;
1037    assert_status!(res, test, StatusCode::OK);
1038    assert_text!(res, test, "Milk withdrawn\n");
1039    let res = client.post(url).send().await.map_err(|_| test)?;
1040    assert_status!(res, test, StatusCode::OK);
1041    assert_text!(res, test, "Milk withdrawn\n");
1042    let res = client.post(url).send().await.map_err(|_| test)?;
1043    assert_status!(res, test, StatusCode::OK);
1044    assert_text!(res, test, "Milk withdrawn\n");
1045    let res = client.post(url).send().await.map_err(|_| test)?;
1046    assert_status!(res, test, StatusCode::OK);
1047    assert_text!(res, test, "Milk withdrawn\n");
1048    let res = client.post(url).send().await.map_err(|_| test)?;
1049    assert_status!(res, test, StatusCode::TOO_MANY_REQUESTS);
1050    assert_text!(res, test, "No milk available\n");
1051    let res = client.post(refill_url).send().await.map_err(|_| test)?;
1052    assert_status!(res, test, StatusCode::OK);
1053    let res = client.post(url).send().await.map_err(|_| test)?;
1054    assert_status!(res, test, StatusCode::OK);
1055    assert_text!(res, test, "Milk withdrawn\n");
1056    let res = client.post(url).send().await.map_err(|_| test)?;
1057    assert_status!(res, test, StatusCode::OK);
1058    assert_text!(res, test, "Milk withdrawn\n");
1059    let res = client.post(url).send().await.map_err(|_| test)?;
1060    assert_status!(res, test, StatusCode::OK);
1061    assert_text!(res, test, "Milk withdrawn\n");
1062    let res = client.post(url).send().await.map_err(|_| test)?;
1063    assert_status!(res, test, StatusCode::OK);
1064    assert_text!(res, test, "Milk withdrawn\n");
1065    let res = client.post(url).send().await.map_err(|_| test)?;
1066    assert_status!(res, test, StatusCode::OK);
1067    assert_text!(res, test, "Milk withdrawn\n");
1068    let res = client.post(url).send().await.map_err(|_| test)?;
1069    assert_status!(res, test, StatusCode::TOO_MANY_REQUESTS);
1070    assert_text!(res, test, "No milk available\n");
1071    let res = client.post(url).send().await.map_err(|_| test)?;
1072    assert_status!(res, test, StatusCode::TOO_MANY_REQUESTS);
1073    assert_text!(res, test, "No milk available\n");
1074    // TASK 4 DONE
1075    tx.send((false, 75).into()).await.unwrap();
1076    tx.send(SubmissionUpdate::Save).await.unwrap();
1077
1078    Ok(())
1079}
1080
1081async fn validate_12(base_url: &str, tx: Sender<SubmissionUpdate>) -> ValidateResult {
1082    let client = new_client();
1083    let mut test: TaskTest;
1084    // TASK 1: board and reset
1085    test = (1, 1);
1086    let reset_url = &format!("{}/12/reset", base_url);
1087    let res = client.post(reset_url).send().await.map_err(|_| test)?;
1088    assert_status!(res, test, StatusCode::OK);
1089    assert_text!(
1090        res,
1091        test,
1092        "\
1093⬜⬛⬛⬛⬛⬜
1094⬜⬛⬛⬛⬛⬜
1095⬜⬛⬛⬛⬛⬜
1096⬜⬛⬛⬛⬛⬜
1097⬜⬜⬜⬜⬜⬜
1098"
1099    );
1100    test = (1, 2);
1101    let board_url = &format!("{}/12/board", base_url);
1102    let res = client.get(board_url).send().await.map_err(|_| test)?;
1103    assert_status!(res, test, StatusCode::OK);
1104    assert_text!(
1105        res,
1106        test,
1107        "\
1108⬜⬛⬛⬛⬛⬜
1109⬜⬛⬛⬛⬛⬜
1110⬜⬛⬛⬛⬛⬜
1111⬜⬛⬛⬛⬛⬜
1112⬜⬜⬜⬜⬜⬜
1113"
1114    );
1115    // TASK 1 DONE
1116    tx.send((false, 0).into()).await.unwrap();
1117    tx.send(SubmissionUpdate::Save).await.unwrap();
1118
1119    // TASK 2: gameplay
1120    test = (2, 1);
1121    let res = client.post(reset_url).send().await.map_err(|_| test)?;
1122    assert_status!(res, test, StatusCode::OK);
1123    assert_text!(
1124        res,
1125        test,
1126        "\
1127⬜⬛⬛⬛⬛⬜
1128⬜⬛⬛⬛⬛⬜
1129⬜⬛⬛⬛⬛⬜
1130⬜⬛⬛⬛⬛⬜
1131⬜⬜⬜⬜⬜⬜
1132"
1133    );
1134    async fn place(
1135        client: &Client,
1136        base_url: &str,
1137        test: TaskTest,
1138        team: &str,
1139        col: i32,
1140    ) -> Result<reqwest::Response, TaskTest> {
1141        client
1142            .post(format!("{}/12/place/{}/{}", base_url, team, col))
1143            .send()
1144            .await
1145            .map_err(|_| test)
1146    }
1147    let res = place(&client, base_url, test, "cookie", 1).await?;
1148    assert_status!(res, test, StatusCode::OK);
1149    assert_text!(
1150        res,
1151        test,
1152        "\
1153⬜⬛⬛⬛⬛⬜
1154⬜⬛⬛⬛⬛⬜
1155⬜⬛⬛⬛⬛⬜
1156⬜🍪⬛⬛⬛⬜
1157⬜⬜⬜⬜⬜⬜
1158"
1159    );
1160    let res = place(&client, base_url, test, "cookie", 1).await?;
1161    assert_status!(res, test, StatusCode::OK);
1162    assert_text!(
1163        res,
1164        test,
1165        "\
1166⬜⬛⬛⬛⬛⬜
1167⬜⬛⬛⬛⬛⬜
1168⬜🍪⬛⬛⬛⬜
1169⬜🍪⬛⬛⬛⬜
1170⬜⬜⬜⬜⬜⬜
1171"
1172    );
1173    let res = place(&client, base_url, test, "cookie", 1).await?;
1174    assert_status!(res, test, StatusCode::OK);
1175    assert_text!(
1176        res,
1177        test,
1178        "\
1179⬜⬛⬛⬛⬛⬜
1180⬜🍪⬛⬛⬛⬜
1181⬜🍪⬛⬛⬛⬜
1182⬜🍪⬛⬛⬛⬜
1183⬜⬜⬜⬜⬜⬜
1184"
1185    );
1186    let res = place(&client, base_url, test, "cookie", 1).await?;
1187    assert_status!(res, test, StatusCode::OK);
1188    assert_text!(
1189        res,
1190        test,
1191        "\
1192⬜🍪⬛⬛⬛⬜
1193⬜🍪⬛⬛⬛⬜
1194⬜🍪⬛⬛⬛⬜
1195⬜🍪⬛⬛⬛⬜
1196⬜⬜⬜⬜⬜⬜
1197🍪 wins!
1198"
1199    );
1200    let res = place(&client, base_url, test, "cookie", 1).await?;
1201    assert_status!(res, test, StatusCode::SERVICE_UNAVAILABLE);
1202    assert_text!(
1203        res,
1204        test,
1205        "\
1206⬜🍪⬛⬛⬛⬜
1207⬜🍪⬛⬛⬛⬜
1208⬜🍪⬛⬛⬛⬜
1209⬜🍪⬛⬛⬛⬜
1210⬜⬜⬜⬜⬜⬜
1211🍪 wins!
1212"
1213    );
1214    let res = place(&client, base_url, test, "milk", 2).await?;
1215    assert_status!(res, test, StatusCode::SERVICE_UNAVAILABLE);
1216    assert_text!(
1217        res,
1218        test,
1219        "\
1220⬜🍪⬛⬛⬛⬜
1221⬜🍪⬛⬛⬛⬜
1222⬜🍪⬛⬛⬛⬜
1223⬜🍪⬛⬛⬛⬜
1224⬜⬜⬜⬜⬜⬜
1225🍪 wins!
1226"
1227    );
1228    let res = client.get(board_url).send().await.map_err(|_| test)?;
1229    assert_status!(res, test, StatusCode::OK);
1230    assert_text!(
1231        res,
1232        test,
1233        "\
1234⬜🍪⬛⬛⬛⬜
1235⬜🍪⬛⬛⬛⬜
1236⬜🍪⬛⬛⬛⬜
1237⬜🍪⬛⬛⬛⬜
1238⬜⬜⬜⬜⬜⬜
1239🍪 wins!
1240"
1241    );
1242    test = (2, 2);
1243    let res = client.post(reset_url).send().await.map_err(|_| test)?;
1244    assert_status!(res, test, StatusCode::OK);
1245    assert_text!(
1246        res,
1247        test,
1248        "\
1249⬜⬛⬛⬛⬛⬜
1250⬜⬛⬛⬛⬛⬜
1251⬜⬛⬛⬛⬛⬜
1252⬜⬛⬛⬛⬛⬜
1253⬜⬜⬜⬜⬜⬜
1254"
1255    );
1256    let res = place(&client, base_url, test, "cookie", 1).await?;
1257    assert_status!(res, test, StatusCode::OK);
1258    let res = place(&client, base_url, test, "milk", 2).await?;
1259    assert_status!(res, test, StatusCode::OK);
1260    let res = place(&client, base_url, test, "cookie", 2).await?;
1261    assert_status!(res, test, StatusCode::OK);
1262    let res = place(&client, base_url, test, "milk", 3).await?;
1263    assert_status!(res, test, StatusCode::OK);
1264    let res = place(&client, base_url, test, "milk", 3).await?;
1265    assert_status!(res, test, StatusCode::OK);
1266    let res = place(&client, base_url, test, "cookie", 3).await?;
1267    assert_status!(res, test, StatusCode::OK);
1268    let res = place(&client, base_url, test, "milk", 4).await?;
1269    assert_status!(res, test, StatusCode::OK);
1270    let res = place(&client, base_url, test, "milk", 4).await?;
1271    assert_status!(res, test, StatusCode::OK);
1272    let res = place(&client, base_url, test, "milk", 4).await?;
1273    assert_status!(res, test, StatusCode::OK);
1274    let res = place(&client, base_url, test, "cookie", 4).await?;
1275    assert_status!(res, test, StatusCode::OK);
1276    assert_text!(
1277        res,
1278        test,
1279        "\
1280⬜⬛⬛⬛🍪⬜
1281⬜⬛⬛🍪🥛⬜
1282⬜⬛🍪🥛🥛⬜
1283⬜🍪🥛🥛🥛⬜
1284⬜⬜⬜⬜⬜⬜
1285🍪 wins!
1286"
1287    );
1288    tokio::time::sleep(Duration::from_millis(1000)).await;
1289    test = (2, 3);
1290    let res = client.post(reset_url).send().await.map_err(|_| test)?;
1291    assert_status!(res, test, StatusCode::OK);
1292    assert_text!(
1293        res,
1294        test,
1295        "\
1296⬜⬛⬛⬛⬛⬜
1297⬜⬛⬛⬛⬛⬜
1298⬜⬛⬛⬛⬛⬜
1299⬜⬛⬛⬛⬛⬜
1300⬜⬜⬜⬜⬜⬜
1301"
1302    );
1303    let res = place(&client, base_url, test, "cookie", 1).await?;
1304    assert_status!(res, test, StatusCode::OK);
1305    let res = place(&client, base_url, test, "cookie", 1).await?;
1306    assert_status!(res, test, StatusCode::OK);
1307    let res = place(&client, base_url, test, "cookie", 1).await?;
1308    assert_status!(res, test, StatusCode::OK);
1309    let res = place(&client, base_url, test, "milk", 1).await?;
1310    assert_status!(res, test, StatusCode::OK);
1311    let res = place(&client, base_url, test, "milk", 2).await?;
1312    assert_status!(res, test, StatusCode::OK);
1313    let res = place(&client, base_url, test, "milk", 2).await?;
1314    assert_status!(res, test, StatusCode::OK);
1315    let res = place(&client, base_url, test, "milk", 2).await?;
1316    assert_status!(res, test, StatusCode::OK);
1317    let res = place(&client, base_url, test, "cookie", 2).await?;
1318    assert_status!(res, test, StatusCode::OK);
1319    let res = place(&client, base_url, test, "cookie", 3).await?;
1320    assert_status!(res, test, StatusCode::OK);
1321    let res = place(&client, base_url, test, "cookie", 3).await?;
1322    assert_status!(res, test, StatusCode::OK);
1323    let res = place(&client, base_url, test, "cookie", 3).await?;
1324    assert_status!(res, test, StatusCode::OK);
1325    let res = place(&client, base_url, test, "milk", 3).await?;
1326    assert_status!(res, test, StatusCode::OK);
1327    let res = place(&client, base_url, test, "milk", 4).await?;
1328    assert_status!(res, test, StatusCode::OK);
1329    let res = place(&client, base_url, test, "milk", 4).await?;
1330    assert_status!(res, test, StatusCode::OK);
1331    let res = place(&client, base_url, test, "milk", 4).await?;
1332    assert_status!(res, test, StatusCode::OK);
1333    let res = place(&client, base_url, test, "cookie", 4).await?;
1334    assert_status!(res, test, StatusCode::OK);
1335    assert_text!(
1336        res,
1337        test,
1338        "\
1339⬜🥛🍪🥛🍪⬜
1340⬜🍪🥛🍪🥛⬜
1341⬜🍪🥛🍪🥛⬜
1342⬜🍪🥛🍪🥛⬜
1343⬜⬜⬜⬜⬜⬜
1344No winner.
1345"
1346    );
1347    tokio::time::sleep(Duration::from_millis(1000)).await;
1348    test = (2, 4);
1349    let res = client.post(reset_url).send().await.map_err(|_| test)?;
1350    assert_status!(res, test, StatusCode::OK);
1351    assert_text!(
1352        res,
1353        test,
1354        "\
1355⬜⬛⬛⬛⬛⬜
1356⬜⬛⬛⬛⬛⬜
1357⬜⬛⬛⬛⬛⬜
1358⬜⬛⬛⬛⬛⬜
1359⬜⬜⬜⬜⬜⬜
1360"
1361    );
1362    let res = place(&client, base_url, test, "cookie", 1).await?;
1363    assert_status!(res, test, StatusCode::OK);
1364    let res = place(&client, base_url, test, "milk", 2).await?;
1365    assert_status!(res, test, StatusCode::OK);
1366    let res = place(&client, base_url, test, "cookie", 2).await?;
1367    assert_status!(res, test, StatusCode::OK);
1368    let res = place(&client, base_url, test, "milk", 2).await?;
1369    assert_status!(res, test, StatusCode::OK);
1370    let res = place(&client, base_url, test, "milk", 1).await?;
1371    assert_status!(res, test, StatusCode::OK);
1372    let res = place(&client, base_url, test, "cookie", 1).await?;
1373    assert_status!(res, test, StatusCode::OK);
1374    let res = place(&client, base_url, test, "cookie", 3).await?;
1375    assert_status!(res, test, StatusCode::OK);
1376    let res = place(&client, base_url, test, "milk", 3).await?;
1377    assert_status!(res, test, StatusCode::OK);
1378    let res = place(&client, base_url, test, "milk", 2).await?;
1379    assert_status!(res, test, StatusCode::OK);
1380    let res = place(&client, base_url, test, "milk", 4).await?;
1381    assert_status!(res, test, StatusCode::OK);
1382    let res = place(&client, base_url, test, "cookie", 3).await?;
1383    assert_status!(res, test, StatusCode::OK);
1384    let res = place(&client, base_url, test, "milk", 1).await?;
1385    assert_status!(res, test, StatusCode::OK);
1386    assert_text!(
1387        res,
1388        test,
1389        "\
1390⬜🥛🥛⬛⬛⬜
1391⬜🍪🥛🍪⬛⬜
1392⬜🥛🍪🥛⬛⬜
1393⬜🍪🥛🍪🥛⬜
1394⬜⬜⬜⬜⬜⬜
1395🥛 wins!
1396"
1397    );
1398    test = (2, 5);
1399    let res = place(&client, base_url, test, "milk", 4).await?;
1400    assert_status!(res, test, StatusCode::SERVICE_UNAVAILABLE);
1401    let res = client.post(reset_url).send().await.map_err(|_| test)?;
1402    assert_status!(res, test, StatusCode::OK);
1403    let res = place(&client, base_url, test, "cookie", 0).await?;
1404    assert_status!(res, test, StatusCode::BAD_REQUEST);
1405    let res = place(&client, base_url, test, "cookie", 5).await?;
1406    assert_status!(res, test, StatusCode::BAD_REQUEST);
1407    let res = place(&client, base_url, test, "cookie", -2).await?;
1408    assert_status!(res, test, StatusCode::BAD_REQUEST);
1409    let res = client
1410        .post(format!("{}/12/place/cookie/one", base_url))
1411        .send()
1412        .await
1413        .map_err(|_| test)?;
1414    assert_status!(res, test, StatusCode::BAD_REQUEST);
1415    let res = place(&client, base_url, test, "plastic", 1).await?;
1416    assert_status!(res, test, StatusCode::BAD_REQUEST);
1417    // TASK 2 DONE
1418    tx.send((true, 0).into()).await.unwrap();
1419    tx.send(SubmissionUpdate::Save).await.unwrap();
1420
1421    // TASK 3: random
1422    test = (3, 1);
1423    let url = &format!("{}/12/random-board", base_url);
1424    let res = client.post(reset_url).send().await.map_err(|_| test)?;
1425    assert_status!(res, test, StatusCode::OK);
1426    let res = client.get(url).send().await.map_err(|_| test)?;
1427    assert_status!(res, test, StatusCode::OK);
1428    assert_text_starts_with!(
1429        res,
1430        test,
1431        "\
1432⬜🍪🍪🍪🍪⬜
1433⬜🥛🍪🍪🥛⬜
1434⬜🥛🥛🥛🥛⬜
1435⬜🍪🥛🍪🥛⬜
1436⬜⬜⬜⬜⬜⬜
1437"
1438    );
1439    let res = client.get(url).send().await.map_err(|_| test)?;
1440    assert_status!(res, test, StatusCode::OK);
1441    assert_text_starts_with!(
1442        res,
1443        test,
1444        "\
1445⬜🍪🥛🍪🍪⬜
1446⬜🥛🍪🥛🍪⬜
1447⬜🥛🍪🍪🍪⬜
1448⬜🍪🥛🥛🥛⬜
1449⬜⬜⬜⬜⬜⬜
1450"
1451    );
1452    let res = client.get(url).send().await.map_err(|_| test)?;
1453    assert_status!(res, test, StatusCode::OK);
1454    assert_text_starts_with!(
1455        res,
1456        test,
1457        "\
1458⬜🍪🍪🥛🍪⬜
1459⬜🍪🥛🍪🍪⬜
1460⬜🥛🍪🍪🥛⬜
1461⬜🍪🥛🍪🍪⬜
1462⬜⬜⬜⬜⬜⬜
1463"
1464    );
1465    let res = client.get(url).send().await.map_err(|_| test)?;
1466    assert_status!(res, test, StatusCode::OK);
1467    assert_text_starts_with!(
1468        res,
1469        test,
1470        "\
1471⬜🥛🍪🍪🥛⬜
1472⬜🥛🍪🍪🍪⬜
1473⬜🍪🥛🥛🥛⬜
1474⬜🍪🥛🍪🥛⬜
1475⬜⬜⬜⬜⬜⬜
1476"
1477    );
1478    let res = client.get(url).send().await.map_err(|_| test)?;
1479    assert_status!(res, test, StatusCode::OK);
1480    assert_text_starts_with!(
1481        res,
1482        test,
1483        "\
1484⬜🥛🥛🥛🍪⬜
1485⬜🍪🍪🍪🥛⬜
1486⬜🥛🍪🍪🥛⬜
1487⬜🍪🥛🥛🍪⬜
1488⬜⬜⬜⬜⬜⬜
1489"
1490    );
1491    let res = client.post(reset_url).send().await.map_err(|_| test)?;
1492    assert_status!(res, test, StatusCode::OK);
1493    let res = client.get(url).send().await.map_err(|_| test)?;
1494    assert_status!(res, test, StatusCode::OK);
1495    assert_text_starts_with!(
1496        res,
1497        test,
1498        "\
1499⬜🍪🍪🍪🍪⬜
1500⬜🥛🍪🍪🥛⬜
1501⬜🥛🥛🥛🥛⬜
1502⬜🍪🥛🍪🥛⬜
1503⬜⬜⬜⬜⬜⬜
1504"
1505    );
1506    // TASK 3 DONE
1507    tx.send((false, 75).into()).await.unwrap();
1508    tx.send(SubmissionUpdate::Save).await.unwrap();
1509
1510    Ok(())
1511}
1512
1513async fn validate_16(base_url: &str, tx: Sender<SubmissionUpdate>) -> ValidateResult {
1514    let mut test: TaskTest;
1515    // TASK 1: jwt cookie
1516    test = (1, 1);
1517    let url1 = &format!("{}/16/wrap", base_url);
1518    let url2 = &format!("{}/16/unwrap", base_url);
1519    let client = new_client_with_cookies();
1520    let payload = json!({"cookie": "yum"});
1521    let res = client
1522        .post(url1)
1523        .json(&payload)
1524        .send()
1525        .await
1526        .map_err(|_| test)?;
1527    assert_status!(res, test, StatusCode::OK);
1528    let h = res
1529        .headers()
1530        .get(header::SET_COOKIE)
1531        .ok_or(test)?
1532        .to_str()
1533        .map_err(|_| test)?;
1534    let h = h.strip_prefix("gift=").ok_or(test)?;
1535    decode_header(h).map_err(|_| test)?;
1536    let res = client.get(url2).send().await.map_err(|_| test)?;
1537    assert_status!(res, test, StatusCode::OK);
1538    assert_text!(res, test, serde_json::to_string(&payload).unwrap());
1539    test = (1, 2);
1540    let c1 = new_client_with_cookies();
1541    let c2 = new_client_with_cookies();
1542    let c3 = new_client_with_cookies();
1543    let p1 = json!({"recipient": "p1", "gifts": ["Toy train", "Caramel corn", "Potato"]});
1544    let p2 = json!({"recipient": "p2", "gifts": ["Toy train", "Caramel corn", "Potato"]});
1545    let p3 = json!({"recipient": "p3", "gifts": ["Toy train", "Caramel corn", "Potato"]});
1546    let res = c1.post(url1).json(&p1).send().await.map_err(|_| test)?;
1547    assert_status!(res, test, StatusCode::OK);
1548    let res = c2.post(url1).json(&p2).send().await.map_err(|_| test)?;
1549    assert_status!(res, test, StatusCode::OK);
1550    let res = c3.post(url1).json(&p3).send().await.map_err(|_| test)?;
1551    assert_status!(res, test, StatusCode::OK);
1552    let res = c1.get(url2).send().await.map_err(|_| test)?;
1553    assert_status!(res, test, StatusCode::OK);
1554    assert_json!(res, test, p1);
1555    let res = c3.get(url2).send().await.map_err(|_| test)?;
1556    assert_status!(res, test, StatusCode::OK);
1557    assert_json!(res, test, p3);
1558    test = (1, 3);
1559    let client = new_client();
1560    let res = client.get(url2).send().await.map_err(|_| test)?;
1561    assert_status!(res, test, StatusCode::BAD_REQUEST);
1562    test = (1, 4);
1563    let client = new_client();
1564    let res = client
1565        .get(url2)
1566        .header("Cookie", "candy=5")
1567        .send()
1568        .await
1569        .map_err(|_| test)?;
1570    assert_status!(res, test, StatusCode::BAD_REQUEST);
1571    // TASK 1 DONE
1572    tx.send((true, 0).into()).await.unwrap();
1573    tx.send(SubmissionUpdate::Save).await.unwrap();
1574
1575    // TASK 2: decode
1576    let client = new_client();
1577    let url = &format!("{}/16/decode", base_url);
1578    test = (2, 1);
1579    let res = client
1580        .post(url)
1581        .body(
1582            "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJyZWluZGVlclNuYWNrIjoiY2Fycm90cyIsInNhbnRhSGF0Q29sb3IiOiJyZWQiLCJzbm93R2xvYmVDb2xsZWN0aW9uIjo1LCJzdG9ja2luZ1N0dWZmZXJzIjpbInlvLXlvIiwiY2FuZHkiLCJrZXljaGFpbiJdLCJ0cmVlSGVpZ2h0Ijo3fQ.EoWSlwZIMHdtd96U_FkfQ9SkbzskSvgEaRpsUeZQFJixDW57vZud_k-MK1R1LEGoJRPGttJvG_5ewdK9O46OuaGW4DHIOWIFLxSYFTJBdFMVmAWC6snqartAFr2U-LWxTwJ09WNpPBcL67YCx4HQsoGZ2mxRVNIKxR7IEfkZDhmpDkiAUbtKyn0H1EVERP1gdbzHUGpLd7wiuzkJnjenBgLPifUevxGPgj535cp8I6EeE4gLdMEm3lbUW4wX_GG5t6_fDAF4URfiAOkSbiIW6lKcSGD9MBVEGps88lA2REBEjT4c7XHw4Tbxci2-knuJm90zIA9KX92t96tF3VFKEA"
1583        )
1584        .send()
1585        .await
1586        .map_err(|_| test)?;
1587    assert_status!(res, test, StatusCode::OK);
1588    assert_json!(
1589        res,
1590        test,
1591        json!({"stockingStuffers":["yo-yo","candy","keychain"],"reindeerSnack":"carrots","treeHeight":7,"santaHatColor":"red","snowGlobeCollection":5})
1592    );
1593    test = (2, 2);
1594    let res = client
1595        .post(url)
1596        .body(
1597            "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJnaWZ0cyI6WyJDb2FsIl19.DaVXV_czINRO1Cvhw33YSPSpV7_TYTqp7gIB_XiVl5fh3K9zkmDItBFLxJHyb7TRw_CGrAYwfinxn6_Dn9MMhp8d3tc-UnRskOxNHpqwU9EcbDtn31uHStT5sLfzdK0fdAc1XUJnr-9dbiGiYARO9YK7HAijdR8bCRMtvMUgIHsumWHO5BEE4CCeVgypzkebsoaev495OE0VNCfn1rSbTKR12xiIFoPCZALV9_slqoZvO59K0x8DSppx7uHApGjXvS6JmyjVgMJNuJoPrIYzc0nytVCa5uLjYIadS2inw7Sty1Jj-sLi8AgtYCXcpyB59MUXNP5xze_Sat8hmQ_NzQ"
1598        )
1599        .send()
1600        .await
1601        .map_err(|_| test)?;
1602    assert_status!(res, test, StatusCode::UNAUTHORIZED);
1603    test = (2, 3);
1604    let res = client
1605        .post(url)
1606        .body(
1607            "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJjYW5kbGVTY2VudHMiOlsicGluZSIsImNpbm5hbW9uIiwidmFuaWxsYSJdLCJmZXN0aXZlU29ja3MiOjEyLCJnaWZ0VGFncyI6WyJwZXJzb25hbGl6ZWQiLCJibGFuayIsInNwYXJrbHkiXSwiZ2luZ2VyYnJlYWRIb3VzZUtpdHMiOjMsImhvdENvY29hU3RvY2siOjI1fQ.GgYB9NXomy-s_lzmoRC-BFHUvrSMjDMcZ4jFCre6NaPJA2fKr--cadxerpody-H5wV19N2zguNb5gr6dt7-suegC8D2ANe9mExohY9tuqgGKRJdLqtmb8U91T_iRg2kyAyhrv3HlSUHQP3sxvAO7jcwLtbePQehtzb6Hv9tZqNCojxMJmAhrJxz41fnD9wvTsEZVpQVwo21C-GIpZKRUGJnaL6OU9IAY6D4PMUr4X9OjEC1zSdQWpYUW_8CHrGNYPVg-6ZpdEvkejxZGTwPg8pMPPSxRa6g0v7Scx-50pgjcP15VK2OUaF9xce7MReJOgI2dxtF35DpYT-UNsIWDKg"
1608        )
1609        .send()
1610        .await
1611        .map_err(|_| test)?;
1612    assert_status!(res, test, StatusCode::OK);
1613    assert_json!(
1614        res,
1615        test,
1616        json!({"giftTags":["personalized","blank","sparkly"],"hotCocoaStock":25,"candleScents":["pine","cinnamon","vanilla"],"gingerbreadHouseKits":3,"festiveSocks":12})
1617    );
1618    test = (2, 4);
1619    let res = client
1620        .post(url)
1621        .body(
1622            "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzUxMiJ9.eyJjYXJvbGluZ1JvdXRlIjpbIk1haW4gU3RyZWV0IiwiRWxtIEF2ZW51ZSIsIkJha2VyIFN0cmVldCJdLCJjb29raWVSZWNpcGVzIjpbInN1Z2FyIGNvb2tpZXMiLCJzbmlja2VyZG9vZGxlcyIsInNob3J0YnJlYWQiXSwiZmVzdGl2ZVB1bmNoSW5ncmVkaWVudHMiOlsiY3JhbmJlcnJ5IGp1aWNlIiwiZ2luZ2VyIGFsZSIsIm9yYW5nZSBzbGljZXMiXSwiZmlyZXBsYWNlTWFudGxlRGVjb3IiOlsiZ2FybGFuZCIsInN0b2NraW5ncyIsImNhbmRsZXMiXSwiZ2lmdENhcmRPcHRpb25zIjpbImJvb2tzdG9yZSIsImNvZmZlZSBzaG9wIiwib25saW5lIHJldGFpbGVyIl0sImhvbGlkYXlDYXJkTGlzdCI6WyJmYW1pbHkiLCJmcmllbmRzIiwiY293b3JrZXJzIl0sIm51dGNyYWNrZXJDb2xsZWN0aW9uU2l6ZXMiOnsibGFyZ2UiOjEsIm1lZGl1bSI6Mywic21hbGwiOjV9LCJzbm93bWFuQnVpbGRpbmdLaXRzIjo0fQ.ZAThp4qXSV1eY8swvPa9OmQrTglgILGWHzR_DN-gslN1dYNPszb2Hy322hiHIht_ASdXcV7-LNatS-P1yIpg7YnIRpZUgg5_Cb3uvucuna0npqfV3U3tTeqDAikPCs5bc7pWjawVscvabJjDm-WPCwLe9o4YMCSFb_XPra6lAHARRrMyqms2PjjdBE3WcUT_wYQq7WwgChXCXHMCOa1XoKIMoegSesYdSXNbbrckDvwdty9GsASCHaX9TAIY4TNdSdl3RanqDlrRDdwjvs5A9dQUul-JzHLxvSodJAGqxxPODNG_P1l0KRlmlVZVZSRqgFC_wH3sziHyVsM1WayjWQ"
1623        )
1624        .send()
1625        .await
1626        .map_err(|_| test)?;
1627    assert_status!(res, test, StatusCode::OK);
1628    assert_json!(
1629        res,
1630        test,
1631        json!({"cookieRecipes":["sugar cookies","snickerdoodles","shortbread"],"fireplaceMantleDecor":["garland","stockings","candles"],"snowmanBuildingKits":4,"holidayCardList":["family","friends","coworkers"],"nutcrackerCollectionSizes":{"small":5,"medium":3,"large":1},"festivePunchIngredients":["cranberry juice","ginger ale","orange slices"],"carolingRoute":["Main Street","Elm Avenue","Baker Street"],"giftCardOptions":["bookstore","coffee shop","online retailer"]})
1632    );
1633    test = (2, 5);
1634    let res = client
1635        .post(url)
1636        .body(
1637            "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJlbHZlcyI6WyJKaW5nbGUiLCJUd2lua2xlIiwiVGluc2VsIl0sImdpZnRGYWN0b3J5Ijp7ImxvY2F0aW9uIjoiTm9ydGggUG9sZSIsIm91dHB1dFBlckhvdXIiOjUwMDB9LCJnaWZ0SWRlYXMiOlsidG95IHRyYWluIiwiYWN0aW9uIGZpZ3VyZSIsInRlZGR5IGJlYXIiLCJsZWdvIHNldCJdLCJyZWluZGVlciI6IlJ1ZG9scGgiLCJzYW50YSI6eyJhZ2UiOjE3NTAsIm5hbWUiOiJLcmlzIEtyaW5nbGUifSwic3VycHJpc2VFbGVtZW50Ijp0cnVlLCJ3aXNobGlzdCI6eyJicm90aGVyIjoidmlkZW8gZ2FtZSIsImRhZCI6InNvY2tzIiwibW9tIjoiY2hvY29sYXRlcyIsInNpc3RlciI6InBvcCBjdWx0dXJlIHBvc3RlciJ9LCJ3cmFwcGluZyI6eyJwYXBlclR5cGVzIjpbImdsb3NzeSIsIm1hdHRlIiwic3BhcmtsZSJdfX0.lQDLhwqrWAn8jPV-lzPuEQE7fFt30yao5M7jADhg3ipwRYYOB8g9sT5TrIufKKCMpNk8qxxgZX9rGJrGVqmdVLRXmyMMgxhiVuboxtI8RlhAEgzNQR6z7G3OWJ-ZccOEHVjdXBQwtpQeLMwoDDHK6UnVsWSrLai5n-VI87QOyxz_2VVj_cR9mtsSEU9rMxZBly1KD5-f-pQHwOczOlAerdp-bgQpANH6uR94AQGENMRQaY7tr_ldh5DNpP9gL0K3oZD3HbEBvYv8OS498mq_09BqVFrp9nmgB4JGhYzNqyFbad8f52sdBRle-ewNR55uxDHq6e10IdJQ_PR34gGPjw"
1638        )
1639        .send()
1640        .await
1641        .map_err(|_| test)?;
1642    assert_status!(res, test, StatusCode::UNAUTHORIZED);
1643    test = (2, 6);
1644    let res = client
1645        .post(url)
1646        .body(
1647            "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJjYW5keUNhbmVTdG9jayI6MTUwMCwiY2Fyb2xQbGF5bGlzdCI6WyJKaW5nbGUgQmVsbHMiLCJTaWxlbnQgTmlnaHQiLCJEZWNrIHRoZSBIYWxscyJdLCJmYXZvcml0ZUNvb2tpZXMiOlsiY2hvY29sYXRlIGNoaXAiLCJvYXRtZWFsIHJhaXNpbiIsImdpbmdlcmJyZWFkIl0sImdpZnRFeGNoYW5nZVJ1bGVzIjp7Im1heEJ1ZGdldCI6NTAsInRoZW1lIjoiaGFuZG1hZGUifSwicmVpbmRlZXJOYW1lcyI6WyJEYXNoZXIiLCJEYW5jZXIiLCJQcmFuY2VyIiwiVml4ZW4iLCJDb21ldCIsIkN1cGlkIiwiRG9ubmVyIiwiQmxpdHplbiJdLCJzZWNyZXRTYW50YSI6eyJkcmF3RGF0ZSI6IjIwMjMtMTItMDEiLCJwYXJ0aWNpcGFudHMiOlsiQWxpY2UiLCJCb2IiLCJDaGFybGllIl19LCJzbGVpZ2giOnsiY29sb3IiOiJyZWQiLCJmdWVsVHlwZSI6Im1hZ2ljIGR1c3QifSwidHJlZURlY29yYXRpb25zIjpbImxpZ2h0cyIsImJhdWJsZXMiLCJ0aW5zZWwiLCJzdGFyIl19.MGtse2G55XIZTSWa2IdNI6YCKsFKsGEonkH0iIlRUuELY6nBdPnLpI4oFEB4-yK8j2eVcQALS3J3YbVUk-LLpIazaVJ5uJ9r-VvBNZqe_Uih8GQjVmINMEHdQwh6v2T2h4FLOqs2wap4SS6q25BVz2v0urycbCo_6IiHvswgkqRk9ZBA_bFDXEKRCoKLdgcNxnYRbkbLVvOzVpvhHFRYOsiwBxBiMakkjp3ZmvV5vaMQaSFUsmW9CHoU0ffbdwOwyMUXrxphSYB7h4OAZeudnZa7ntoOZ6J3PJQCTvgU7llffTPcdoO6LVoXSD8hiIfvJWPKgsOgasyG_xEQmfGcsA"
1648        )
1649        .send()
1650        .await
1651        .map_err(|_| test)?;
1652    assert_status!(res, test, StatusCode::UNAUTHORIZED);
1653    for (test, txt) in [
1654        ((2, 7), "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJyZWluZGVlclNuYWNrIjoiY2Fycm90cyIsInNhbnRhSGF0Q29sb3IiOiJyZWQiLCJzbm93R2xvYmVDb2xsZWN0aW9uIjo1LCJzdG9ja2luZ1N0dWZmZXJzIjpbInlvLXlvIiwiY2FuZHkiLCJrZXljaGFpbiJdLCJ0cmVlSGVpZ2h0Ijo3fQEoWSlwZIMHdtd96U_FkfQ9SkbzskSvgEaRpsUeZQFJixDW57vZud_k-MK1R1LEGoJRPGttJvG_5ewdK9O46OuaGW4DHIOWIFLxSYFTJBdFMVmAWC6snqartAFr2U-LWxTwJ09WNpPBcL67YCx4HQsoGZ2mxRVNIKxR7IEfkZDhmpDkiAUbtKyn0H1EVERP1gdbzHUGpLd7wiuzkJnjenBgLPifUevxGPgj535cp8I6EeE4gLdMEm3lbUW4wX_GG5t6_fDAF4URfiAOkSbiIW6lKcSGD9MBVEGps88lA2REBEjT4c7XHw4Tbxci2-knuJm90zIA9KX92t96tF3VFKEA"),
1655        ((2, 8), "eyJ0eXAiOiJKV1QiLCJhbGci0iJSUzI1NiJ9.eyJyZWluZGVlclNuYWNrIjoiY2Fycm90cyIsInNhbnRhSGF0Q29sb3IiOiJyZWQiLCJzbm93R2xvYmVDb2xsZWN0aW9uIjo1LCJzdG9ja2luZ1N0dWZmZXJzIjpbInlvLXlvIiwiY2FuZHkiLCJrZXljaGFpbiJdLCJ0cmVlSGVpZ2h0Ijo3fQ.EoWSlwZIMHdtd96U_FkfQ9SkbzskSvgEaRpsUeZQFJixDW57vZud_k-MK1R1LEGoJRPGttJvG_5ewdK9O46OuaGW4DHIOWIFLxSYFTJBdFMVmAWC6snqartAFr2U-LWxTwJ09WNpPBcL67YCx4HQsoGZ2mxRVNIKxR7IEfkZDhmpDkiAUbtKyn0H1EVERP1gdbzHUGpLd7wiuzkJnjenBgLPifUevxGPgj535cp8I6EeE4gLdMEm3lbUW4wX_GG5t6_fDAF4URfiAOkSbiIW6lKcSGD9MBVEGps88lA2REBEjT4c7XHw4Tbxci2-knuJm90zIA9KX92t96tF3VFKEA"),
1656        ((2, 9), "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJy.ZWluZGVlclNuYWNrIjoiY2Fycm90cyIsInNhbnRhSGF0Q29sb3IiOiJyZWQiLCJzbm93R2xvYmVDb2xsZWN0aW9uIjo1LCJzdG9ja2luZ1N0dWZmZXJzIjpbInlvLXlvIiwiY2FuZHkiLCJrZXljaGFpbiJdLCJ0cmVlSGVpZ2h0Ijo3fQ.EoWSlwZIMHdtd96U_FkfQ9SkbzskSvgEaRpsUeZQFJixDW57vZud_k-MK1R1LEGoJRPGttJvG_5ewdK9O46OuaGW4DHIOWIFLxSYFTJBdFMVmAWC6snqartAFr2U-LWxTwJ09WNpPBcL67YCx4HQsoGZ2mxRVNIKxR7IEfkZDhmpDkiAUbtKyn0H1EVERP1gdbzHUGpLd7wiuzkJnjenBgLPifUevxGPgj535cp8I6EeE4gLdMEm3lbUW4wX_GG5t6_fDAF4URfiAOkSbiIW6lKcSGD9MBVEGps88lA2REBEjT4c7XHw4Tbxci2-knuJm90zIA9KX92t96tF3VFKEA"),
1657        ((2, 10), "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.ImNvYWwi.cTlGrCeHzvweR-b7U1PZn3fpNk5P_C8wjTo2s93itoYdzeJwUunHTfPY9MJ3Mmif_2MDveSf7b_xID4fRhnXzEBNblIXtlfoNE1lWGPurOvB8udxxJk30qM6sG-ldK79TKzt784ok1ecyuAP94vMjKK861YUoqq5bfZdr9YwIq0chJOx0RZG0zY2OS7VVoOG-SbOssHb-eZKysCt-r8zrIwJGXoSe6H5ZYX7dN5l9CbJ6t29D89I0SkZj2TI2unBG5UueXIw6VukwREzDPTKJTdh6AbnMRwoi7GGIlayhUaFtAGPrlnS2razOmAWndtSv9rDNELJirN2AQ7iyRbqyg"),
1658    ] {
1659        let res = client.post(url).body(txt).send().await.map_err(|_| test)?;
1660        assert_status!(res, test, StatusCode::BAD_REQUEST);
1661    }
1662    // TASK 2 DONE
1663    tx.send((false, 200).into()).await.unwrap();
1664    tx.send(SubmissionUpdate::Save).await.unwrap();
1665
1666    Ok(())
1667}
1668
1669async fn validate_19(base_url: &str, tx: Sender<SubmissionUpdate>) -> ValidateResult {
1670    let client = new_client();
1671    let mut test: TaskTest;
1672    // TASK 1: CRUD
1673    test = (1, 1);
1674    let reset_url = &format!("{}/19/reset", base_url);
1675    let cite_url = &format!("{}/19/cite", base_url);
1676    let remove_url = &format!("{}/19/remove", base_url);
1677    let undo_url = &format!("{}/19/undo", base_url);
1678    let draft_url = &format!("{}/19/draft", base_url);
1679    let res = client.post(reset_url).send().await.map_err(|_| test)?;
1680    assert_status!(res, test, StatusCode::OK);
1681
1682    async fn validate_quote(
1683        res: reqwest::Response,
1684        test: (i32, i32),
1685        sent: &serde_json::Value,
1686        version: i64,
1687    ) -> Result<Uuid, TaskTest> {
1688        quote_matches(
1689            test,
1690            sent,
1691            &res.json::<serde_json::Value>().await.map_err(|_| test)?,
1692            version,
1693        )
1694        .await
1695    }
1696    async fn quote_matches(
1697        test: (i32, i32),
1698        exp: &serde_json::Value,
1699        act: &serde_json::Value,
1700        version: i64,
1701    ) -> Result<Uuid, TaskTest> {
1702        assert_eq_!(test, act.as_object().ok_or(test)?.len(), 5);
1703        assert_!(test, act.get("author") == exp.get("author"));
1704        assert_!(test, act.get("quote") == exp.get("quote"));
1705        assert_!(
1706            test,
1707            act.get("version")
1708                .is_some_and(|v| v.as_i64().is_some_and(|v| v == version))
1709        );
1710        act.get("created_at")
1711            .ok_or(test)?
1712            .as_str()
1713            .ok_or(test)?
1714            .parse::<DateTime<Utc>>()
1715            .map_err(|_| test)?;
1716        let id: Uuid = act
1717            .get("id")
1718            .ok_or(test)?
1719            .as_str()
1720            .ok_or(test)?
1721            .parse()
1722            .map_err(|_| test)?;
1723
1724        Ok(id)
1725    }
1726
1727    let quote1 = json!({"author":"Santa","quote":"Ho ho ho! Spread cheer and kindness, for that's the true magic of the season!"});
1728    let quote2 = json!({"author":"Santa's best elf","quote":"In the glow of snow and twinkling light, dreams take flight on a magical night!"});
1729    let quote3 = json!({"author":"Dasher","quote":"Whoosh and clatter, my hooves pitter-patter!"});
1730    let quote4 = json!({"author":"Polar Bear","quote":"Roar!"});
1731    let res = client
1732        .post(draft_url)
1733        .json(&quote1)
1734        .send()
1735        .await
1736        .map_err(|_| test)?;
1737    assert_status!(res, test, StatusCode::CREATED);
1738    let id = validate_quote(res, test, &quote1, 1).await?;
1739
1740    let res = client
1741        .get(format!("{}/{}", cite_url, id))
1742        .send()
1743        .await
1744        .map_err(|_| test)?;
1745    assert_status!(res, test, StatusCode::OK);
1746    validate_quote(res, test, &quote1, 1).await?;
1747
1748    let res = client
1749        .put(format!("{}/{}", undo_url, id))
1750        .json(&quote2)
1751        .send()
1752        .await
1753        .map_err(|_| test)?;
1754    assert_status!(res, test, StatusCode::OK);
1755    let id2 = validate_quote(res, test, &quote2, 2).await?;
1756    assert_eq_!(test, id, id2);
1757
1758    let res = client
1759        .delete(format!("{}/{}", remove_url, id))
1760        .send()
1761        .await
1762        .map_err(|_| test)?;
1763    assert_status!(res, test, StatusCode::OK);
1764    validate_quote(res, test, &quote2, 2).await?;
1765
1766    let res = client
1767        .get(format!("{}/{}", cite_url, id))
1768        .send()
1769        .await
1770        .map_err(|_| test)?;
1771    assert_status!(res, test, StatusCode::NOT_FOUND);
1772
1773    test = (1, 2);
1774    let res = client
1775        .post(draft_url)
1776        .json(&quote1)
1777        .send()
1778        .await
1779        .map_err(|_| test)?;
1780    assert_status!(res, test, StatusCode::CREATED);
1781    let id = validate_quote(res, test, &quote1, 1).await?;
1782    let res = client
1783        .post(draft_url)
1784        .json(&quote1)
1785        .send()
1786        .await
1787        .map_err(|_| test)?;
1788    assert_status!(res, test, StatusCode::CREATED);
1789    let id2 = validate_quote(res, test, &quote1, 1).await?;
1790    assert_neq_!(test, id, id2);
1791
1792    let res = client
1793        .put(format!("{}/{}", undo_url, id))
1794        .json(&quote2)
1795        .send()
1796        .await
1797        .map_err(|_| test)?;
1798    assert_status!(res, test, StatusCode::OK);
1799    validate_quote(res, test, &quote2, 2).await?;
1800    let res = client
1801        .get(format!("{}/{}", cite_url, id2))
1802        .send()
1803        .await
1804        .map_err(|_| test)?;
1805    assert_status!(res, test, StatusCode::OK);
1806    validate_quote(res, test, &quote1, 1).await?;
1807    let res = client
1808        .get(format!("{}/{}", cite_url, id))
1809        .send()
1810        .await
1811        .map_err(|_| test)?;
1812    assert_status!(res, test, StatusCode::OK);
1813    validate_quote(res, test, &quote2, 2).await?;
1814
1815    let res = client
1816        .put(format!("{}/{}", undo_url, id))
1817        .json(&quote3)
1818        .send()
1819        .await
1820        .map_err(|_| test)?;
1821    assert_status!(res, test, StatusCode::OK);
1822    validate_quote(res, test, &quote3, 3).await?;
1823    let res = client
1824        .put(format!("{}/{}", undo_url, id))
1825        .json(&quote1)
1826        .send()
1827        .await
1828        .map_err(|_| test)?;
1829    assert_status!(res, test, StatusCode::OK);
1830    validate_quote(res, test, &quote1, 4).await?;
1831
1832    test = (1, 3);
1833    let res = client
1834        .put(format!(
1835            "{}/{}",
1836            undo_url, "00000000-0000-0000-0000-000000000000"
1837        ))
1838        .json(&quote4)
1839        .send()
1840        .await
1841        .map_err(|_| test)?;
1842    assert_status!(res, test, StatusCode::NOT_FOUND);
1843    let res = client
1844        .delete(format!(
1845            "{}/{}",
1846            remove_url, "00000000-0000-0000-0000-000000000000"
1847        ))
1848        .send()
1849        .await
1850        .map_err(|_| test)?;
1851    assert_status!(res, test, StatusCode::NOT_FOUND);
1852    let res = client
1853        .get(format!(
1854            "{}/{}",
1855            cite_url, "00000000-0000-0000-0000-000000000000"
1856        ))
1857        .send()
1858        .await
1859        .map_err(|_| test)?;
1860    assert_status!(res, test, StatusCode::NOT_FOUND);
1861    let res = client
1862        .put(format!("{}/{}", undo_url, "1234"))
1863        .json(&quote4)
1864        .send()
1865        .await
1866        .map_err(|_| test)?;
1867    assert_status!(res, test, StatusCode::BAD_REQUEST);
1868
1869    // TASK 1 DONE
1870    tx.send((true, 0).into()).await.unwrap();
1871    tx.send(SubmissionUpdate::Save).await.unwrap();
1872
1873    // TASK 2: paginator
1874    test = (2, 1);
1875    let list_url = &format!("{}/19/list", base_url);
1876    async fn validate_quotes(
1877        res: reqwest::Response,
1878        test: (i32, i32),
1879        sent: &[(&serde_json::Value, i64)],
1880        page: i64,
1881    ) -> Result<Option<String>, TaskTest> {
1882        let json = res.json::<serde_json::Value>().await.map_err(|_| test)?;
1883        assert_!(
1884            test,
1885            json.get("page")
1886                .is_some_and(|v| v.as_i64().is_some_and(|v| v == page))
1887        );
1888        let quotes = json.get("quotes").ok_or(test)?.as_array().ok_or(test)?;
1889        for ((v, version), quote) in sent.iter().zip(quotes.iter()) {
1890            quote_matches(test, v, quote, *version).await?;
1891        }
1892        let next_token: Option<String> =
1893            serde_json::from_value(json.get("next_token").ok_or(test)?.clone())
1894                .map_err(|_| test)?;
1895        if let Some(t) = next_token.as_ref() {
1896            if t.chars().any(|c| !c.is_ascii_alphanumeric()) || t.len() != 16 {
1897                return Err(test);
1898            }
1899        }
1900        Ok(next_token)
1901    }
1902    let res = client.get(list_url).send().await.map_err(|_| test)?;
1903    assert_status!(res, test, StatusCode::OK);
1904    let n = validate_quotes(res, test, &[(&quote1, 4), (&quote1, 1)], 1).await?;
1905    assert_!(test, n.is_none());
1906
1907    let res = client
1908        .post(draft_url)
1909        .json(&quote3)
1910        .send()
1911        .await
1912        .map_err(|_| test)?;
1913    assert_status!(res, test, StatusCode::CREATED);
1914    let id3 = validate_quote(res, test, &quote3, 1).await?;
1915    let res = client
1916        .post(draft_url)
1917        .json(&quote3)
1918        .send()
1919        .await
1920        .map_err(|_| test)?;
1921    assert_status!(res, test, StatusCode::CREATED);
1922    validate_quote(res, test, &quote3, 1).await?;
1923
1924    let res = client.get(list_url).send().await.map_err(|_| test)?;
1925    assert_status!(res, test, StatusCode::OK);
1926    let n = validate_quotes(res, test, &[(&quote1, 4), (&quote1, 1), (&quote3, 1)], 1).await?;
1927    assert_!(test, n.is_some());
1928    let res = client
1929        .get(format!("{}?token={}", list_url, n.unwrap()))
1930        .send()
1931        .await
1932        .map_err(|_| test)?;
1933    assert_status!(res, test, StatusCode::OK);
1934    let n = validate_quotes(res, test, &[(&quote3, 1)], 2).await?;
1935    assert_!(test, n.is_none());
1936
1937    test = (2, 2);
1938    let res = client
1939        .delete(format!("{}/{}", remove_url, id3))
1940        .send()
1941        .await
1942        .map_err(|_| test)?;
1943    assert_status!(res, test, StatusCode::OK);
1944    validate_quote(res, test, &quote3, 1).await?;
1945    let res = client.get(list_url).send().await.map_err(|_| test)?;
1946    assert_status!(res, test, StatusCode::OK);
1947    let n = validate_quotes(res, test, &[(&quote1, 4), (&quote1, 1), (&quote3, 1)], 1).await?;
1948    assert_!(test, n.is_none());
1949
1950    test = (2, 3);
1951    let page1 = &[(&quote1, 4), (&quote1, 1), (&quote3, 1)];
1952    let page2 = &[(&quote2, 1), (&quote2, 1), (&quote3, 1)];
1953    let page3 = &[(&quote2, 1), (&quote3, 1), (&quote1, 1)];
1954    for &(q, v) in page2.iter().chain(page3.iter()) {
1955        let res = client
1956            .post(draft_url)
1957            .json(q)
1958            .send()
1959            .await
1960            .map_err(|_| test)?;
1961        assert_status!(res, test, StatusCode::CREATED);
1962        validate_quote(res, test, q, v).await?;
1963    }
1964
1965    let res = client.get(list_url).send().await.map_err(|_| test)?;
1966    assert_status!(res, test, StatusCode::OK);
1967    let n = validate_quotes(res, test, page1, 1).await?;
1968    assert_!(test, n.is_some());
1969    let res = client
1970        .get(format!("{}?token={}", list_url, n.unwrap()))
1971        .send()
1972        .await
1973        .map_err(|_| test)?;
1974    assert_status!(res, test, StatusCode::OK);
1975    let n = validate_quotes(res, test, page2, 2).await?;
1976    assert_!(test, n.is_some());
1977    let res = client
1978        .get(format!("{}?token={}", list_url, n.unwrap()))
1979        .send()
1980        .await
1981        .map_err(|_| test)?;
1982    assert_status!(res, test, StatusCode::OK);
1983    let n = validate_quotes(res, test, page3, 3).await?;
1984    assert_!(test, n.is_none());
1985
1986    test = (2, 4);
1987    let res = client
1988        .get(format!("{}?token=asd987f69as87d6q", list_url))
1989        .send()
1990        .await
1991        .map_err(|_| test)?;
1992    assert_status!(res, test, StatusCode::BAD_REQUEST);
1993
1994    test = (2, 5);
1995    let res = client.get(list_url).send().await.map_err(|_| test)?;
1996    assert_status!(res, test, StatusCode::OK);
1997    let n1 = validate_quotes(res, test, page1, 1).await?;
1998    assert_!(test, n1.is_some());
1999
2000    let res = client.get(list_url).send().await.map_err(|_| test)?;
2001    assert_status!(res, test, StatusCode::OK);
2002    let n2 = validate_quotes(res, test, page1, 1).await?;
2003    assert_!(test, n2.is_some());
2004
2005    let res = client
2006        .get(format!("{}?token={}", list_url, n1.unwrap()))
2007        .send()
2008        .await
2009        .map_err(|_| test)?;
2010    assert_status!(res, test, StatusCode::OK);
2011    let n1 = validate_quotes(res, test, page2, 2).await?;
2012    assert_!(test, n1.is_some());
2013    let res = client
2014        .get(format!("{}?token={}", list_url, n1.unwrap()))
2015        .send()
2016        .await
2017        .map_err(|_| test)?;
2018    assert_status!(res, test, StatusCode::OK);
2019    let n1 = validate_quotes(res, test, page3, 3).await?;
2020    assert_!(test, n1.is_none());
2021
2022    let res = client
2023        .get(format!("{}?token={}", list_url, n2.unwrap()))
2024        .send()
2025        .await
2026        .map_err(|_| test)?;
2027    assert_status!(res, test, StatusCode::OK);
2028    let n2 = validate_quotes(res, test, page2, 2).await?;
2029    assert_!(test, n2.is_some());
2030    let res = client
2031        .get(format!("{}?token={}", list_url, n2.unwrap()))
2032        .send()
2033        .await
2034        .map_err(|_| test)?;
2035    assert_status!(res, test, StatusCode::OK);
2036    let n2 = validate_quotes(res, test, page3, 3).await?;
2037    assert_!(test, n2.is_none());
2038
2039    // TASK 2 DONE
2040    tx.send((false, 75).into()).await.unwrap();
2041
2042    Ok(())
2043}
2044
2045async fn validate_23(base_url: &str, tx: Sender<SubmissionUpdate>) -> ValidateResult {
2046    let client = new_client();
2047    let mut test: TaskTest;
2048    // TASK 1: serve
2049    test = (1, 1);
2050    let url = &format!("{}/assets/23.html", base_url);
2051    let res = client.get(url).send().await.map_err(|_| test)?;
2052    assert_status!(res, test, StatusCode::OK);
2053    if res.text().await.map_err(|_| test)?.len() != 7163 {
2054        return Err(test);
2055    }
2056    // TASK 1 DONE
2057    tx.send((false, 0).into()).await.unwrap();
2058    tx.send(SubmissionUpdate::Save).await.unwrap();
2059
2060    let comparer = HtmlComparer::with_options(HtmlCompareOptions {
2061        ignore_whitespace: true,
2062        ignore_attributes: false,
2063        ignored_attributes: Default::default(),
2064        ignore_text: false,
2065        ignore_comments: true,
2066        ignore_sibling_order: false,
2067        ignore_style_contents: false,
2068    });
2069    macro_rules! assert_html {
2070        ($res:expr, $test:expr, $comp:expr, $expected_html:expr) => {
2071            if !$comp
2072                .compare($expected_html, &$res.text().await.map_err(|_| $test)?)
2073                .is_ok_and(|t| t)
2074            {
2075                return Err($test);
2076            }
2077        };
2078    }
2079    // TASK 2: star
2080    test = (2, 1);
2081    let url = &format!("{}/23/star", base_url);
2082    let res = client.get(url).send().await.map_err(|_| test)?;
2083    assert_status!(res, test, StatusCode::OK);
2084    assert_html!(res, test, comparer, r#"<div id="star" class="lit"></div>"#);
2085    // TASK 2 DONE
2086    tx.send((false, 0).into()).await.unwrap();
2087    tx.send(SubmissionUpdate::Save).await.unwrap();
2088
2089    // TASK 3: present
2090    test = (3, 1);
2091    let res = client
2092        .get(format!("{}/23/present/red", base_url))
2093        .send()
2094        .await
2095        .map_err(|_| test)?;
2096    assert_status!(res, test, StatusCode::OK);
2097    assert_html!(
2098        res,
2099        test,
2100        comparer,
2101        r#"<div class="present red" hx-get="/23/present/blue" hx-swap="outerHTML"><div class="ribbon"></div><div class="ribbon"></div><div class="ribbon"></div><div class="ribbon"></div></div>"#
2102    );
2103    let res = client
2104        .get(format!("{}/23/present/blue", base_url))
2105        .send()
2106        .await
2107        .map_err(|_| test)?;
2108    assert_status!(res, test, StatusCode::OK);
2109    assert_html!(
2110        res,
2111        test,
2112        comparer,
2113        r#"<div class="present blue" hx-get="/23/present/purple" hx-swap="outerHTML"><div class="ribbon"></div><div class="ribbon"></div><div class="ribbon"></div><div class="ribbon"></div></div>"#
2114    );
2115    let res = client
2116        .get(format!("{}/23/present/purple", base_url))
2117        .send()
2118        .await
2119        .map_err(|_| test)?;
2120    assert_status!(res, test, StatusCode::OK);
2121    assert_html!(
2122        res,
2123        test,
2124        comparer,
2125        r#"<div class="present purple" hx-get="/23/present/red" hx-swap="outerHTML"><div class="ribbon"></div><div class="ribbon"></div><div class="ribbon"></div><div class="ribbon"></div></div>"#
2126    );
2127    test = (3, 2);
2128    let res = client
2129        .get(format!("{}/23/present/green", base_url))
2130        .send()
2131        .await
2132        .map_err(|_| test)?;
2133    assert_status!(res, test, StatusCode::IM_A_TEAPOT);
2134    // TASK 3 DONE
2135    tx.send((false, 0).into()).await.unwrap();
2136    tx.send(SubmissionUpdate::Save).await.unwrap();
2137
2138    // TASK 4: ornament
2139    test = (4, 1);
2140    let res = client
2141        .get(format!("{}/23/ornament/on/1", base_url))
2142        .send()
2143        .await
2144        .map_err(|_| test)?;
2145    assert_status!(res, test, StatusCode::OK);
2146    assert_html!(
2147        res,
2148        test,
2149        comparer,
2150        r#"<div class="ornament on" id="ornament1" hx-trigger="load delay:2s once" hx-get="/23/ornament/off/1" hx-swap="outerHTML"></div>"#
2151    );
2152    let res = client
2153        .get(format!("{}/23/ornament/off/1", base_url))
2154        .send()
2155        .await
2156        .map_err(|_| test)?;
2157    assert_status!(res, test, StatusCode::OK);
2158    assert_html!(
2159        res,
2160        test,
2161        comparer,
2162        r#"<div class="ornament" id="ornament1" hx-trigger="load delay:2s once" hx-get="/23/ornament/on/1" hx-swap="outerHTML"></div>"#
2163    );
2164    let res = client
2165        .get(format!("{}/23/ornament/off/100", base_url))
2166        .send()
2167        .await
2168        .map_err(|_| test)?;
2169    assert_status!(res, test, StatusCode::OK);
2170    assert_html!(
2171        res,
2172        test,
2173        comparer,
2174        r#"<div class="ornament" id="ornament100" hx-trigger="load delay:2s once" hx-get="/23/ornament/on/100" hx-swap="outerHTML"></div>"#
2175    );
2176    test = (4, 2);
2177    let res = client
2178        .get(format!("{}/23/ornament/on/the_prettiest_one", base_url))
2179        .send()
2180        .await
2181        .map_err(|_| test)?;
2182    assert_status!(res, test, StatusCode::OK);
2183    assert_html!(
2184        res,
2185        test,
2186        comparer,
2187        r#"<div class="ornament on" id="ornamentthe_prettiest_one" hx-trigger="load delay:2s once" hx-get="/23/ornament/off/the_prettiest_one" hx-swap="outerHTML"></div>"#
2188    );
2189    test = (4, 3);
2190    let res = client
2191        .get(format!("{}/23/ornament/maybe-on/1", base_url))
2192        .send()
2193        .await
2194        .map_err(|_| test)?;
2195    assert_status!(res, test, StatusCode::IM_A_TEAPOT);
2196    // TASK 4 DONE
2197    tx.send((false, 0).into()).await.unwrap();
2198    tx.send(SubmissionUpdate::Save).await.unwrap();
2199
2200    // TASK 5: injection
2201    test = (5, 1);
2202    let res = client
2203        .get(format!(
2204            "{}/23/ornament/on/%22%3E%3Cscript%3Ealert%28%22Spicy%20soup%21%22%29%3C%2Fscript%3E",
2205            base_url
2206        ))
2207        .send()
2208        .await
2209        .map_err(|_| test)?;
2210    assert_status!(res, test, StatusCode::OK);
2211    assert_html!(
2212        res,
2213        test,
2214        comparer,
2215        r#"<div class="ornament on" id="ornament&quot;&gt;&lt;script&gt;alert(&quot;Spicy soup!&quot;)&lt;/script&gt;" hx-trigger="load delay:2s once" hx-get="/23/ornament/off/&quot;&gt;&lt;script&gt;alert(&quot;Spicy soup!&quot;)&lt;/script&gt;" hx-swap="outerHTML"></div>"#
2216    );
2217    // TASK 5 DONE
2218    tx.send((true, 0).into()).await.unwrap();
2219    tx.send(SubmissionUpdate::Save).await.unwrap();
2220
2221    // TASK 6: lockfile
2222    test = (6, 1);
2223    let url = &format!("{}/23/lockfile", base_url);
2224    let form = Form::new().part(
2225        "lockfile",
2226        Part::bytes(
2227            r#"[[package]]
2228name = "shuttle-runtime"
2229version = "0.49.0"
2230source = "registry+https://github.com/rust-lang/crates.io-index"
2231checksum = "337789faa0372648a8ac286b2f92a53121fe118f12e29009ac504872a5413cc6"
2232
2233[[package]]
2234name = "shuttle-service"
2235version = "0.49.0"
2236source = "registry+https://github.com/rust-lang/crates.io-index"
2237checksum = "22ba454b13e4e29b5b892a62c334360a571de5a25c936283416c94328427dd57"
2238"#
2239            .as_bytes(),
2240        )
2241        .file_name("Cargo.lock")
2242        .mime_str("application/octet-stream")
2243        .unwrap(),
2244    );
2245    let res = client
2246        .post(url)
2247        .multipart(form)
2248        .send()
2249        .await
2250        .map_err(|_| test)?;
2251    assert_status!(res, test, StatusCode::OK);
2252    assert_html!(
2253        res,
2254        test,
2255        comparer,
2256        r#"
2257<div style="background-color:#337789;top:250px;left:160px;"></div>
2258<div style="background-color:#22ba45;top:75px;left:19px;"></div>
2259"#
2260    );
2261    test = (6, 2);
2262    let form = Form::new().part(
2263        "lockfile",
2264        Part::bytes(
2265            r#"# This file is automatically @generated by Cargo.
2266# It is not intended for manual editing.
2267version = 4
2268
2269[[package]]
2270name = "addr2line"
2271version = "0.24.2"
2272source = "registry+https://github.com/rust-lang/crates.io-index"
2273checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
2274dependencies = [
2275 "gimli",
2276]
2277
2278[[package]]
2279name = "adler2"
2280version = "2.0.0"
2281source = "registry+https://github.com/rust-lang/crates.io-index"
2282checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
2283
2284[[package]]
2285name = "ahash"
2286version = "0.8.11"
2287source = "registry+https://github.com/rust-lang/crates.io-index"
2288checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
2289dependencies = [
2290 "cfg-if",
2291 "once_cell",
2292 "version_check",
2293 "zerocopy",
2294]
2295
2296[[package]]
2297name = "aho-corasick"
2298version = "1.1.3"
2299source = "registry+https://github.com/rust-lang/crates.io-index"
2300checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
2301dependencies = [
2302 "memchr",
2303]
2304
2305[[package]]
2306name = "allocator-api2"
2307version = "0.2.21"
2308source = "registry+https://github.com/rust-lang/crates.io-index"
2309checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
2310
2311[[package]]
2312name = "android-tzdata"
2313version = "0.1.1"
2314source = "registry+https://github.com/rust-lang/crates.io-index"
2315checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
2316
2317[[package]]
2318name = "android_system_properties"
2319version = "0.1.5"
2320source = "registry+https://github.com/rust-lang/crates.io-index"
2321checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
2322dependencies = [
2323 "libc",
2324]
2325
2326[[package]]
2327name = "anyhow"
2328version = "1.0.93"
2329source = "registry+https://github.com/rust-lang/crates.io-index"
2330checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
2331
2332[[package]]
2333name = "askama"
2334version = "0.12.1"
2335source = "registry+https://github.com/rust-lang/crates.io-index"
2336checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28"
2337dependencies = [
2338 "askama_derive",
2339 "askama_escape",
2340 "humansize",
2341 "num-traits",
2342 "percent-encoding",
2343]
2344
2345[[package]]
2346name = "askama_axum"
2347version = "0.4.0"
2348source = "registry+https://github.com/rust-lang/crates.io-index"
2349checksum = "a41603f7cdbf5ac4af60760f17253eb6adf6ec5b6f14a7ed830cf687d375f163"
2350dependencies = [
2351 "askama",
2352 "axum-core 0.4.5",
2353 "http 1.1.0",
2354]
2355
2356[[package]]
2357name = "askama_derive"
2358version = "0.12.5"
2359source = "registry+https://github.com/rust-lang/crates.io-index"
2360checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83"
2361dependencies = [
2362 "askama_parser",
2363 "basic-toml",
2364 "mime",
2365 "mime_guess",
2366 "proc-macro2",
2367 "quote",
2368 "serde",
2369 "syn 2.0.89",
2370]
2371
2372[[package]]
2373name = "askama_escape"
2374version = "0.10.3"
2375source = "registry+https://github.com/rust-lang/crates.io-index"
2376checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341"
2377
2378[[package]]
2379name = "askama_parser"
2380version = "0.2.1"
2381source = "registry+https://github.com/rust-lang/crates.io-index"
2382checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0"
2383dependencies = [
2384 "nom",
2385]
2386
2387[[package]]
2388name = "async-stream"
2389version = "0.3.6"
2390source = "registry+https://github.com/rust-lang/crates.io-index"
2391checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
2392dependencies = [
2393 "async-stream-impl",
2394 "futures-core",
2395 "pin-project-lite",
2396]
2397
2398[[package]]
2399name = "async-stream-impl"
2400version = "0.3.6"
2401source = "registry+https://github.com/rust-lang/crates.io-index"
2402checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
2403dependencies = [
2404 "proc-macro2",
2405 "quote",
2406 "syn 2.0.89",
2407]
2408
2409[[package]]
2410name = "async-trait"
2411version = "0.1.83"
2412source = "registry+https://github.com/rust-lang/crates.io-index"
2413checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
2414dependencies = [
2415 "proc-macro2",
2416 "quote",
2417 "syn 2.0.89",
2418]
2419
2420[[package]]
2421name = "atoi"
2422version = "2.0.0"
2423source = "registry+https://github.com/rust-lang/crates.io-index"
2424checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
2425dependencies = [
2426 "num-traits",
2427]
2428
2429[[package]]
2430name = "autocfg"
2431version = "1.4.0"
2432source = "registry+https://github.com/rust-lang/crates.io-index"
2433checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
2434
2435[[package]]
2436name = "axum"
2437version = "0.6.20"
2438source = "registry+https://github.com/rust-lang/crates.io-index"
2439checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf"
2440dependencies = [
2441 "async-trait",
2442 "axum-core 0.3.4",
2443 "bitflags 1.3.2",
2444 "bytes",
2445 "futures-util",
2446 "http 0.2.12",
2447 "http-body 0.4.6",
2448 "hyper 0.14.31",
2449 "itoa",
2450 "matchit",
2451 "memchr",
2452 "mime",
2453 "percent-encoding",
2454 "pin-project-lite",
2455 "rustversion",
2456 "serde",
2457 "sync_wrapper 0.1.2",
2458 "tower 0.4.13",
2459 "tower-layer",
2460 "tower-service",
2461]
2462
2463[[package]]
2464name = "axum"
2465version = "0.7.9"
2466source = "registry+https://github.com/rust-lang/crates.io-index"
2467checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
2468dependencies = [
2469 "async-trait",
2470 "axum-core 0.4.5",
2471 "bytes",
2472 "futures-util",
2473 "http 1.1.0",
2474 "http-body 1.0.1",
2475 "http-body-util",
2476 "hyper 1.5.1",
2477 "hyper-util",
2478 "itoa",
2479 "matchit",
2480 "memchr",
2481 "mime",
2482 "percent-encoding",
2483 "pin-project-lite",
2484 "rustversion",
2485 "serde",
2486 "serde_json",
2487 "serde_path_to_error",
2488 "serde_urlencoded",
2489 "sync_wrapper 1.0.2",
2490 "tokio",
2491 "tower 0.5.1",
2492 "tower-layer",
2493 "tower-service",
2494 "tracing",
2495]
2496
2497[[package]]
2498name = "axum-core"
2499version = "0.3.4"
2500source = "registry+https://github.com/rust-lang/crates.io-index"
2501checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c"
2502dependencies = [
2503 "async-trait",
2504 "bytes",
2505 "futures-util",
2506 "http 0.2.12",
2507 "http-body 0.4.6",
2508 "mime",
2509 "rustversion",
2510 "tower-layer",
2511 "tower-service",
2512]
2513
2514[[package]]
2515name = "axum-core"
2516version = "0.4.5"
2517source = "registry+https://github.com/rust-lang/crates.io-index"
2518checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
2519dependencies = [
2520 "async-trait",
2521 "bytes",
2522 "futures-util",
2523 "http 1.1.0",
2524 "http-body 1.0.1",
2525 "http-body-util",
2526 "mime",
2527 "pin-project-lite",
2528 "rustversion",
2529 "sync_wrapper 1.0.2",
2530 "tower-layer",
2531 "tower-service",
2532 "tracing",
2533]
2534
2535[[package]]
2536name = "axum-extra"
2537version = "0.9.6"
2538source = "registry+https://github.com/rust-lang/crates.io-index"
2539checksum = "c794b30c904f0a1c2fb7740f7df7f7972dfaa14ef6f57cb6178dc63e5dca2f04"
2540dependencies = [
2541 "axum 0.7.9",
2542 "axum-core 0.4.5",
2543 "bytes",
2544 "cookie",
2545 "fastrand",
2546 "futures-util",
2547 "http 1.1.0",
2548 "http-body 1.0.1",
2549 "http-body-util",
2550 "mime",
2551 "multer",
2552 "pin-project-lite",
2553 "serde",
2554 "tower 0.5.1",
2555 "tower-layer",
2556 "tower-service",
2557]
2558
2559[[package]]
2560name = "backtrace"
2561version = "0.3.74"
2562source = "registry+https://github.com/rust-lang/crates.io-index"
2563checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
2564dependencies = [
2565 "addr2line",
2566 "cfg-if",
2567 "libc",
2568 "miniz_oxide",
2569 "object",
2570 "rustc-demangle",
2571 "windows-targets 0.52.6",
2572]
2573
2574[[package]]
2575name = "base64"
2576version = "0.21.7"
2577source = "registry+https://github.com/rust-lang/crates.io-index"
2578checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
2579
2580[[package]]
2581name = "base64"
2582version = "0.22.1"
2583source = "registry+https://github.com/rust-lang/crates.io-index"
2584checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
2585
2586[[package]]
2587name = "base64ct"
2588version = "1.6.0"
2589source = "registry+https://github.com/rust-lang/crates.io-index"
2590checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
2591
2592[[package]]
2593name = "basic-toml"
2594version = "0.1.9"
2595source = "registry+https://github.com/rust-lang/crates.io-index"
2596checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8"
2597dependencies = [
2598 "serde",
2599]
2600
2601[[package]]
2602name = "bitflags"
2603version = "1.3.2"
2604source = "registry+https://github.com/rust-lang/crates.io-index"
2605checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
2606
2607[[package]]
2608name = "bitflags"
2609version = "2.6.0"
2610source = "registry+https://github.com/rust-lang/crates.io-index"
2611checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
2612dependencies = [
2613 "serde",
2614]
2615
2616[[package]]
2617name = "block-buffer"
2618version = "0.10.4"
2619source = "registry+https://github.com/rust-lang/crates.io-index"
2620checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
2621dependencies = [
2622 "generic-array",
2623]
2624
2625[[package]]
2626name = "bumpalo"
2627version = "3.16.0"
2628source = "registry+https://github.com/rust-lang/crates.io-index"
2629checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
2630
2631[[package]]
2632name = "byteorder"
2633version = "1.5.0"
2634source = "registry+https://github.com/rust-lang/crates.io-index"
2635checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
2636
2637[[package]]
2638name = "bytes"
2639version = "1.8.0"
2640source = "registry+https://github.com/rust-lang/crates.io-index"
2641checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da"
2642
2643[[package]]
2644name = "shuttlings-cch24"
2645version = "0.1.0"
2646dependencies = [
2647 "askama",
2648 "askama_axum",
2649 "axum 0.7.9",
2650 "axum-extra",
2651 "cargo-manifest",
2652 "chrono",
2653 "ipnet",
2654 "jsonwebtoken",
2655 "leaky-bucket",
2656 "rand",
2657 "serde",
2658 "serde_json",
2659 "serde_yml",
2660 "shuttle-axum",
2661 "shuttle-runtime",
2662 "shuttle-shared-db",
2663 "sqlx",
2664 "tokio",
2665 "toml",
2666 "tower-http",
2667 "uuid",
2668]
2669
2670[[package]]
2671name = "signal-hook"
2672version = "0.3.17"
2673source = "registry+https://github.com/rust-lang/crates.io-index"
2674checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
2675dependencies = [
2676 "libc",
2677 "signal-hook-registry",
2678]
2679"#
2680            .as_bytes(),
2681        )
2682        .file_name("Cargo.lock")
2683        .mime_str("application/octet-stream")
2684        .unwrap(),
2685    );
2686    let res = client
2687        .post(url)
2688        .multipart(form)
2689        .send()
2690        .await
2691        .map_err(|_| test)?;
2692    assert_status!(res, test, StatusCode::OK);
2693    assert_html!(
2694        res,
2695        test,
2696        comparer,
2697        r#"
2698<div style="background-color:#dfbe27;top:126px;left:86px;"></div>
2699<div style="background-color:#512761;top:224px;left:187px;"></div>
2700<div style="background-color:#e89da8;top:65px;left:168px;"></div>
2701<div style="background-color:#8e60d3;top:67px;left:13px;"></div>
2702<div style="background-color:#683d79;top:16px;left:231px;"></div>
2703<div style="background-color:#e99994;top:27px;left:35px;"></div>
2704<div style="background-color:#819e72;top:25px;left:219px;"></div>
2705<div style="background-color:#4c95c1;top:11px;left:160px;"></div>
2706<div style="background-color:#b79091;top:223px;left:24px;"></div>
2707<div style="background-color:#a41603;top:247px;left:205px;"></div>
2708<div style="background-color:#19fe8d;top:108px;left:177px;"></div>
2709<div style="background-color:#619743;top:227px;left:75px;"></div>
2710<div style="background-color:#acb116;top:28px;left:107px;"></div>
2711<div style="background-color:#0b5a71;top:166px;left:243px;"></div>
2712<div style="background-color:#c7c24d;top:225px;left:93px;"></div>
2713<div style="background-color:#721cae;top:125px;left:229px;"></div>
2714<div style="background-color:#f28d99;top:236px;left:139px;"></div>
2715<div style="background-color:#ace50b;top:173px;left:232px;"></div>
2716<div style="background-color:#3b829e;top:78px;left:50px;"></div>
2717<div style="background-color:#edca88;top:188px;left:19px;"></div>
2718<div style="background-color:#759fa5;top:119px;left:162px;"></div>
2719<div style="background-color:#09f2bd;top:97px;left:70px;"></div>
2720<div style="background-color:#c794b3;top:12px;left:144px;"></div>
2721<div style="background-color:#8d82cb;top:51px;left:44px;"></div>
2722<div style="background-color:#9d297d;top:235px;left:25px;"></div>
2723<div style="background-color:#72b325;top:79px;left:22px;"></div>
2724<div style="background-color:#8c3c1a;top:54px;left:143px;"></div>
2725<div style="background-color:#823388;top:226px;left:40px;"></div>
2726<div style="background-color:#bef38d;top:69px;left:22px;"></div>
2727<div style="background-color:#b048fb;top:99px;left:253px;"></div>
2728<div style="background-color:#3078c7;top:98px;left:155px;"></div>
2729<div style="background-color:#792967;top:22px;left:23px;"></div>
2730<div style="background-color:#1fd0f2;top:88px;left:65px;"></div>
2731<div style="background-color:#9ac015;top:12px;left:170px;"></div>
2732<div style="background-color:#862158;top:125px;left:71px;"></div>
2733"#
2734    );
2735    test = (6, 3);
2736    let form = Form::new().part(
2737        "blockfile",
2738        Part::bytes(r#"MINE DIAMONDS!!!!"#.as_bytes())
2739            .file_name("Cargo.block")
2740            .mime_str("application/octet-stream")
2741            .unwrap(),
2742    );
2743    let res = client
2744        .post(url)
2745        .multipart(form)
2746        .send()
2747        .await
2748        .map_err(|_| test)?;
2749    assert_status!(res, test, StatusCode::BAD_REQUEST);
2750    test = (6, 4);
2751    let form = Form::new();
2752    let res = client
2753        .post(url)
2754        .multipart(form)
2755        .send()
2756        .await
2757        .map_err(|_| test)?;
2758    assert_status!(res, test, StatusCode::BAD_REQUEST);
2759    test = (6, 5);
2760    let form = Form::new().part(
2761        "lockfile",
2762        Part::bytes(
2763            "[[package]]
2764checksum = \"337789faa0372648a8ac286b2f92a53121fe118f12e29009ac504872a5413cc6\"
2765\x00\x00"
2766                .as_bytes(),
2767        )
2768        .file_name("Cargo.lock")
2769        .mime_str("application/octet-stream")
2770        .unwrap(),
2771    );
2772    let res = client
2773        .post(url)
2774        .multipart(form)
2775        .send()
2776        .await
2777        .map_err(|_| test)?;
2778    assert_status!(res, test, StatusCode::BAD_REQUEST);
2779    test = (6, 5);
2780    let form = Form::new().part(
2781        "lockfile",
2782        Part::bytes(
2783            r#"[[package]]
2784checksum = "337789faa0372648a8ac286b2f92a53121fe118f12e29009ac504872a5413cc6"
2785fn jingle_bells(volume: f32) -> Result<Sound<DingDong>, MusicError> { ... }
2786"#
2787            .as_bytes(),
2788        )
2789        .file_name("Cargo.lock")
2790        .mime_str("application/octet-stream")
2791        .unwrap(),
2792    );
2793    let res = client
2794        .post(url)
2795        .multipart(form)
2796        .send()
2797        .await
2798        .map_err(|_| test)?;
2799    assert_status!(res, test, StatusCode::BAD_REQUEST);
2800    test = (6, 6);
2801    let form = Form::new().part(
2802        "lockfile",
2803        Part::bytes(
2804            r#"[[package]]
2805checksum = "337789faa0372648a8ac286b2f92a53121fe118f12e29009ac504872a5413cc6"
2806"#
2807            .as_bytes(),
2808        )
2809        .file_name("Cargo.lock")
2810        .mime_str("application/octet-stream")
2811        .unwrap(),
2812    );
2813    let res = client
2814        .post(url)
2815        .multipart(form)
2816        .send()
2817        .await
2818        .map_err(|_| test)?;
2819    assert_status!(res, test, StatusCode::OK);
2820    assert_html!(
2821        res,
2822        test,
2823        comparer,
2824        r#"
2825<div style="background-color:#337789;top:250px;left:160px;"></div>
2826"#
2827    );
2828    test = (6, 7);
2829    let form = Form::new().part(
2830        "lockfile",
2831        Part::bytes(
2832            r#"[[package]]
2833checksum = [ "cookie", "milk", "hot cocoa" ]
2834"#
2835            .as_bytes(),
2836        )
2837        .file_name("Cargo.lock")
2838        .mime_str("application/octet-stream")
2839        .unwrap(),
2840    );
2841    let res = client
2842        .post(url)
2843        .multipart(form)
2844        .send()
2845        .await
2846        .map_err(|_| test)?;
2847    assert_status!(res, test, StatusCode::BAD_REQUEST);
2848    test = (6, 8);
2849    let form = Form::new().part(
2850        "lockfile",
2851        Part::bytes(
2852            r#"[[package]]
2853checksum = "337789faa0"
2854"#
2855            .as_bytes(),
2856        )
2857        .file_name("Cargo.lock")
2858        .mime_str("application/octet-stream")
2859        .unwrap(),
2860    );
2861    let res = client
2862        .post(url)
2863        .multipart(form)
2864        .send()
2865        .await
2866        .map_err(|_| test)?;
2867    assert_status!(res, test, StatusCode::OK);
2868    assert_html!(
2869        res,
2870        test,
2871        comparer,
2872        r#"
2873<div style="background-color:#337789;top:250px;left:160px;"></div>
2874"#
2875    );
2876    test = (6, 9);
2877    let form = Form::new().part(
2878        "lockfile",
2879        Part::bytes(
2880            r#"[[package]]
2881checksum = "337789faa0"
2882"#
2883            .as_bytes(),
2884        )
2885        .file_name("Cargo.lock")
2886        .mime_str("application/octet-stream")
2887        .unwrap(),
2888    );
2889    let res = client
2890        .post(url)
2891        .multipart(form)
2892        .send()
2893        .await
2894        .map_err(|_| test)?;
2895    assert_status!(res, test, StatusCode::OK);
2896    assert_html!(
2897        res,
2898        test,
2899        comparer,
2900        r#"
2901<div style="background-color:#337789;top:250px;left:160px;"></div>
2902"#
2903    );
2904    test = (6, 10);
2905    let form = Form::new().part(
2906        "lockfile",
2907        Part::bytes(
2908            r#"[[package]]
2909checksum = "337789FAA0"
2910"#
2911            .as_bytes(),
2912        )
2913        .file_name("Cargo.lock")
2914        .mime_str("application/octet-stream")
2915        .unwrap(),
2916    );
2917    let res = client
2918        .post(url)
2919        .multipart(form)
2920        .send()
2921        .await
2922        .map_err(|_| test)?;
2923    assert_status!(res, test, StatusCode::OK);
2924    assert_html!(
2925        res,
2926        test,
2927        comparer,
2928        r#"
2929<div style="background-color:#337789;top:250px;left:160px;"></div>
2930"#
2931    );
2932    test = (6, 11);
2933    let form = Form::new().part(
2934        "lockfile",
2935        Part::bytes(
2936            r#"[[package]]
2937checksum = "3377QQFAA0"
2938"#
2939            .as_bytes(),
2940        )
2941        .file_name("Cargo.lock")
2942        .mime_str("application/octet-stream")
2943        .unwrap(),
2944    );
2945    let res = client
2946        .post(url)
2947        .multipart(form)
2948        .send()
2949        .await
2950        .map_err(|_| test)?;
2951    assert_status!(res, test, StatusCode::UNPROCESSABLE_ENTITY);
2952    test = (6, 12);
2953    let form = Form::new().part(
2954        "lockfile",
2955        Part::bytes(
2956            r#"[[package]]
2957checksum = "BEEF"
2958"#
2959            .as_bytes(),
2960        )
2961        .file_name("Cargo.lock")
2962        .mime_str("application/octet-stream")
2963        .unwrap(),
2964    );
2965    let res = client
2966        .post(url)
2967        .multipart(form)
2968        .send()
2969        .await
2970        .map_err(|_| test)?;
2971    assert_status!(res, test, StatusCode::UNPROCESSABLE_ENTITY);
2972
2973    // TASK 6 DONE
2974    tx.send((false, 100).into()).await.unwrap();
2975    tx.send(SubmissionUpdate::Save).await.unwrap();
2976
2977    Ok(())
2978}