atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
35d571ccadc4c1643547ed0a6e9d3e1e99fc184f
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git

Blame: src/tests/household.rs

8b8e83329a1datagen2026-10-12 12:01 UTC1
use super::*;
8b8e83329a1datagen2026-10-12 12:01 UTC2
8b8e83329a1datagen2026-10-12 12:01 UTC3
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC4
async fn mum_accepts_a_known_excuse() {
8b8e83329a1datagen2026-10-12 12:01 UTC5
    let app = seeded_with(Floor::Bedroom, mum_at_the_door);
8b8e83329a1datagen2026-10-12 12:01 UTC6
    let response = send(&app, "GET", "/runs/cafebabe", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC7
    assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
8b8e83329a1datagen2026-10-12 12:01 UTC8
    assert_eq!(header(&response, "via"), "1.1 mum");
8b8e83329a1datagen2026-10-12 12:01 UTC9
    let response = send(
8b8e83329a1datagen2026-10-12 12:01 UTC10
        &app,
8b8e83329a1datagen2026-10-12 12:01 UTC11
        "GET",
8b8e83329a1datagen2026-10-12 12:01 UTC12
        "/runs/cafebabe",
8b8e83329a1datagen2026-10-12 12:01 UTC13
        &[("authorization", "Excuse \"homework\"")],
8b8e83329a1datagen2026-10-12 12:01 UTC14
        "",
8b8e83329a1datagen2026-10-12 12:01 UTC15
    )
8b8e83329a1datagen2026-10-12 12:01 UTC16
    .await;
8b8e83329a1datagen2026-10-12 12:01 UTC17
    assert_eq!(response.status(), StatusCode::OK);
8b8e83329a1datagen2026-10-12 12:01 UTC18
}
8b8e83329a1datagen2026-10-12 12:01 UTC19
8b8e83329a1datagen2026-10-12 12:01 UTC20
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC21
async fn mum_runs_out_of_patience() {
8b8e83329a1datagen2026-10-12 12:01 UTC22
    let app = seeded_with(Floor::Bedroom, mum_at_the_door);
8b8e83329a1datagen2026-10-12 12:01 UTC23
    let lie = [("authorization", "Excuse \"it is for science\"")];
8b8e83329a1datagen2026-10-12 12:01 UTC24
    for _ in 0..2 {
8b8e83329a1datagen2026-10-12 12:01 UTC25
        let response = send(&app, "GET", "/runs/cafebabe", &lie, "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC26
        assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
8b8e83329a1datagen2026-10-12 12:01 UTC27
    }
8b8e83329a1datagen2026-10-12 12:01 UTC28
    let caught = send(&app, "GET", "/runs/cafebabe", &lie, "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC29
    assert_eq!(caught.status(), StatusCode::GONE);
8b8e83329a1datagen2026-10-12 12:01 UTC30
    assert_eq!(
8b8e83329a1datagen2026-10-12 12:01 UTC31
        json(caught).await["type"],
8b8e83329a1datagen2026-10-12 12:01 UTC32
        "https://jelq.dev/problems/caught"
8b8e83329a1datagen2026-10-12 12:01 UTC33
    );
8b8e83329a1datagen2026-10-12 12:01 UTC34
}
8b8e83329a1datagen2026-10-12 12:01 UTC35
8b8e83329a1datagen2026-10-12 12:01 UTC36
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC37
async fn mum_remembers_excuses_by_christmas() {
8b8e83329a1datagen2026-10-12 12:01 UTC38
    let app = seeded_with(Floor::Nans, |run| {
8b8e83329a1datagen2026-10-12 12:01 UTC39
        mum_at_the_door(run);
8b8e83329a1datagen2026-10-12 12:01 UTC40
        run.household.heard.push(Excuse::Homework);
8b8e83329a1datagen2026-10-12 12:01 UTC41
    });
8b8e83329a1datagen2026-10-12 12:01 UTC42
    let basic = send(
8b8e83329a1datagen2026-10-12 12:01 UTC43
        &app,
8b8e83329a1datagen2026-10-12 12:01 UTC44
        "GET",
8b8e83329a1datagen2026-10-12 12:01 UTC45
        "/runs/cafebabe",
8b8e83329a1datagen2026-10-12 12:01 UTC46
        &[("authorization", "Excuse \"changing\"")],
8b8e83329a1datagen2026-10-12 12:01 UTC47
        "",
8b8e83329a1datagen2026-10-12 12:01 UTC48
    )
8b8e83329a1datagen2026-10-12 12:01 UTC49
    .await;
8b8e83329a1datagen2026-10-12 12:01 UTC50
    assert_eq!(basic.status(), StatusCode::UNAUTHORIZED);
8b8e83329a1datagen2026-10-12 12:01 UTC51
    assert!(header(&basic, "www-authenticate").starts_with("Digest"));
8b8e83329a1datagen2026-10-12 12:01 UTC52
    let replay = send(
8b8e83329a1datagen2026-10-12 12:01 UTC53
        &app,
8b8e83329a1datagen2026-10-12 12:01 UTC54
        "GET",
8b8e83329a1datagen2026-10-12 12:01 UTC55
        "/runs/cafebabe",
8b8e83329a1datagen2026-10-12 12:01 UTC56
        &[(
8b8e83329a1datagen2026-10-12 12:01 UTC57
            "authorization",
8b8e83329a1datagen2026-10-12 12:01 UTC58
            "Digest username=\"homework\", nonce=\"abc\"",
8b8e83329a1datagen2026-10-12 12:01 UTC59
        )],
8b8e83329a1datagen2026-10-12 12:01 UTC60
        "",
8b8e83329a1datagen2026-10-12 12:01 UTC61
    )
8b8e83329a1datagen2026-10-12 12:01 UTC62
    .await;
8b8e83329a1datagen2026-10-12 12:01 UTC63
    assert_eq!(
8b8e83329a1datagen2026-10-12 12:01 UTC64
        json(replay).await["type"],
8b8e83329a1datagen2026-10-12 12:01 UTC65
        "https://jelq.dev/problems/excuse-replayed"
8b8e83329a1datagen2026-10-12 12:01 UTC66
    );
8b8e83329a1datagen2026-10-12 12:01 UTC67
    let fresh = send(
8b8e83329a1datagen2026-10-12 12:01 UTC68
        &app,
8b8e83329a1datagen2026-10-12 12:01 UTC69
        "GET",
8b8e83329a1datagen2026-10-12 12:01 UTC70
        "/runs/cafebabe",
8b8e83329a1datagen2026-10-12 12:01 UTC71
        &[(
8b8e83329a1datagen2026-10-12 12:01 UTC72
            "authorization",
8b8e83329a1datagen2026-10-12 12:01 UTC73
            "Digest username=\"wrapping presents\", nonce=\"abc\"",
8b8e83329a1datagen2026-10-12 12:01 UTC74
        )],
8b8e83329a1datagen2026-10-12 12:01 UTC75
        "",
8b8e83329a1datagen2026-10-12 12:01 UTC76
    )
8b8e83329a1datagen2026-10-12 12:01 UTC77
    .await;
8b8e83329a1datagen2026-10-12 12:01 UTC78
    assert_eq!(fresh.status(), StatusCode::OK);
8b8e83329a1datagen2026-10-12 12:01 UTC79
}
8b8e83329a1datagen2026-10-12 12:01 UTC80
8b8e83329a1datagen2026-10-12 12:01 UTC81
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC82
async fn a_pulled_muscle_is_a_true_excuse() {
8b8e83329a1datagen2026-10-12 12:01 UTC83
    let app = seeded_with(Floor::Nans, |run| {
8b8e83329a1datagen2026-10-12 12:01 UTC84
        mum_at_the_door(run);
8b8e83329a1datagen2026-10-12 12:01 UTC85
        run.body.conditions.pulled_muscle = true;
8b8e83329a1datagen2026-10-12 12:01 UTC86
        run.household.heard.push(Excuse::PulledAMuscle);
8b8e83329a1datagen2026-10-12 12:01 UTC87
    });
8b8e83329a1datagen2026-10-12 12:01 UTC88
    let response = send(
8b8e83329a1datagen2026-10-12 12:01 UTC89
        &app,
8b8e83329a1datagen2026-10-12 12:01 UTC90
        "GET",
8b8e83329a1datagen2026-10-12 12:01 UTC91
        "/runs/cafebabe",
8b8e83329a1datagen2026-10-12 12:01 UTC92
        &[(
8b8e83329a1datagen2026-10-12 12:01 UTC93
            "authorization",
8b8e83329a1datagen2026-10-12 12:01 UTC94
            "Digest username=\"pulled a muscle\", nonce=\"abc\"",
8b8e83329a1datagen2026-10-12 12:01 UTC95
        )],
8b8e83329a1datagen2026-10-12 12:01 UTC96
        "",
8b8e83329a1datagen2026-10-12 12:01 UTC97
    )
8b8e83329a1datagen2026-10-12 12:01 UTC98
    .await;
8b8e83329a1datagen2026-10-12 12:01 UTC99
    assert_eq!(response.status(), StatusCode::OK);
8b8e83329a1datagen2026-10-12 12:01 UTC100
}
8b8e83329a1datagen2026-10-12 12:01 UTC101
8b8e83329a1datagen2026-10-12 12:01 UTC102
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC103
async fn ignoring_mum_has_consequences() {
8b8e83329a1datagen2026-10-12 12:01 UTC104
    let app = seeded_with(Floor::Bedroom, |run| {
8b8e83329a1datagen2026-10-12 12:01 UTC105
        mum_at_the_door(run);
8b8e83329a1datagen2026-10-12 12:01 UTC106
        run.here.turns = 6;
8b8e83329a1datagen2026-10-12 12:01 UTC107
    });
8b8e83329a1datagen2026-10-12 12:01 UTC108
    let knock = send(&app, "GET", "/runs/cafebabe", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC109
    assert_eq!(
8b8e83329a1datagen2026-10-12 12:01 UTC110
        json(knock).await["type"],
8b8e83329a1datagen2026-10-12 12:01 UTC111
        "https://jelq.dev/problems/someone-at-the-door"
8b8e83329a1datagen2026-10-12 12:01 UTC112
    );
8b8e83329a1datagen2026-10-12 12:01 UTC113
    let leave = send(&app, "POST", "/runs/cafebabe/floor", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC114
    assert_eq!(
8b8e83329a1datagen2026-10-12 12:01 UTC115
        leave.status(),
8b8e83329a1datagen2026-10-12 12:01 UTC116
        StatusCode::UNAUTHORIZED,
8b8e83329a1datagen2026-10-12 12:01 UTC117
        "no leaving with Mum at the door"
8b8e83329a1datagen2026-10-12 12:01 UTC118
    );
8b8e83329a1datagen2026-10-12 12:01 UTC119
    send(&app, "GET", "/runs/cafebabe", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC120
    let opened = send(&app, "GET", "/runs/cafebabe", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC121
    assert_eq!(opened.status(), StatusCode::GONE);
8b8e83329a1datagen2026-10-12 12:01 UTC122
    assert_eq!(
8b8e83329a1datagen2026-10-12 12:01 UTC123
        json(opened).await["type"],
8b8e83329a1datagen2026-10-12 12:01 UTC124
        "https://jelq.dev/problems/opened"
8b8e83329a1datagen2026-10-12 12:01 UTC125
    );
8b8e83329a1datagen2026-10-12 12:01 UTC126
}
8b8e83329a1datagen2026-10-12 12:01 UTC127
8b8e83329a1datagen2026-10-12 12:01 UTC128
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC129
async fn mom_speaks_american() {
8b8e83329a1datagen2026-10-12 12:01 UTC130
    let app = seeded_with(Floor::Nans, mum_at_the_door);
8b8e83329a1datagen2026-10-12 12:01 UTC131
    let us = [("accept-language", "en-US")];
8b8e83329a1datagen2026-10-12 12:01 UTC132
    let knock = send(&app, "GET", "/runs/cafebabe", &us, "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC133
    assert_eq!(header(&knock, "content-language"), "en-US");
8b8e83329a1datagen2026-10-12 12:01 UTC134
    assert_eq!(header(&knock, "via"), "1.1 mom");
8b8e83329a1datagen2026-10-12 12:01 UTC135
    assert!(
8b8e83329a1datagen2026-10-12 12:01 UTC136
        json(knock).await["detail"]
8b8e83329a1datagen2026-10-12 12:01 UTC137
            .as_str()
8b8e83329a1datagen2026-10-12 12:01 UTC138
            .unwrap()
8b8e83329a1datagen2026-10-12 12:01 UTC139
            .contains("Someone is at the door.")
8b8e83329a1datagen2026-10-12 12:01 UTC140
    );
8b8e83329a1datagen2026-10-12 12:01 UTC141
    let excuse = [
8b8e83329a1datagen2026-10-12 12:01 UTC142
        ("accept-language", "en-US"),
8b8e83329a1datagen2026-10-12 12:01 UTC143
        (
8b8e83329a1datagen2026-10-12 12:01 UTC144
            "authorization",
8b8e83329a1datagen2026-10-12 12:01 UTC145
            "Digest username=\"carving the turkey\", nonce=\"abc\"",
8b8e83329a1datagen2026-10-12 12:01 UTC146
        ),
8b8e83329a1datagen2026-10-12 12:01 UTC147
    ];
8b8e83329a1datagen2026-10-12 12:01 UTC148
    let answered = send(&app, "GET", "/runs/cafebabe", &excuse, "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC149
    assert_eq!(answered.status(), StatusCode::OK);
8b8e83329a1datagen2026-10-12 12:01 UTC150
    let floor = json(send(&app, "GET", "/runs/cafebabe/floor", &us, "").await).await;
8b8e83329a1datagen2026-10-12 12:01 UTC151
    assert_eq!(floor["name"], "Grandma's house for Thanksgiving");
8b8e83329a1datagen2026-10-12 12:01 UTC152
}
8b8e83329a1datagen2026-10-12 12:01 UTC153
8b8e83329a1datagen2026-10-12 12:01 UTC154
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC155
async fn an_empty_bottle_is_declined_before_the_enemy_stirs() {
8b8e83329a1datagen2026-10-12 12:01 UTC156
    let control = seeded_with(Floor::ChangingRoom, |run| {
8b8e83329a1datagen2026-10-12 12:01 UTC157
        run.standing.suspicion = SUSPICION_LIMIT;
9adb509e6685atagen2026-10-14 08:54 UTC158
        run.items.lubes.baby_oil = 1;
8b8e83329a1datagen2026-10-12 12:01 UTC159
    });
8b8e83329a1datagen2026-10-12 12:01 UTC160
    let lube = [("content-type", "application/baby-oil")];
8b8e83329a1datagen2026-10-12 12:01 UTC161
    let caught = send(
8b8e83329a1datagen2026-10-12 12:01 UTC162
        &control,
8b8e83329a1datagen2026-10-12 12:01 UTC163
        "JELQ",
8b8e83329a1datagen2026-10-12 12:01 UTC164
        "/runs/cafebabe?technique=wet&intensity=5",
8b8e83329a1datagen2026-10-12 12:01 UTC165
        &lube,
8b8e83329a1datagen2026-10-12 12:01 UTC166
        "",
8b8e83329a1datagen2026-10-12 12:01 UTC167
    )
8b8e83329a1datagen2026-10-12 12:01 UTC168
    .await;
8b8e83329a1datagen2026-10-12 12:01 UTC169
    assert_eq!(caught.status(), StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS);
8b8e83329a1datagen2026-10-12 12:01 UTC170
8b8e83329a1datagen2026-10-12 12:01 UTC171
    let app = seeded_with(Floor::ChangingRoom, |run| {
8b8e83329a1datagen2026-10-12 12:01 UTC172
        run.standing.suspicion = SUSPICION_LIMIT;
8b8e83329a1datagen2026-10-12 12:01 UTC173
        run.items.lubes.baby_oil = 0;
8b8e83329a1datagen2026-10-12 12:01 UTC174
    });
8b8e83329a1datagen2026-10-12 12:01 UTC175
    let refused = send(
8b8e83329a1datagen2026-10-12 12:01 UTC176
        &app,
8b8e83329a1datagen2026-10-12 12:01 UTC177
        "JELQ",
8b8e83329a1datagen2026-10-12 12:01 UTC178
        "/runs/cafebabe?technique=wet&intensity=5",
8b8e83329a1datagen2026-10-12 12:01 UTC179
        &lube,
8b8e83329a1datagen2026-10-12 12:01 UTC180
        "",
8b8e83329a1datagen2026-10-12 12:01 UTC181
    )
8b8e83329a1datagen2026-10-12 12:01 UTC182
    .await;
8b8e83329a1datagen2026-10-12 12:01 UTC183
    assert_eq!(refused.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE);
8b8e83329a1datagen2026-10-12 12:01 UTC184
    let view = send(&app, "GET", "/runs/cafebabe", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC185
    assert_eq!(header(&view, "x-suspicion"), "5/5");
8b8e83329a1datagen2026-10-12 12:01 UTC186
    assert_eq!(header(&view, "x-shame"), "0");
8b8e83329a1datagen2026-10-12 12:01 UTC187
}
8b8e83329a1datagen2026-10-12 12:01 UTC188
8b8e83329a1datagen2026-10-12 12:01 UTC189
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC190
async fn respectable_girth_waves_off_the_gym_staff() {
8b8e83329a1datagen2026-10-12 12:01 UTC191
    let staff = |girth: f64| {
8b8e83329a1datagen2026-10-12 12:01 UTC192
        seeded_with(Floor::ChangingRoom, move |run| {
8b8e83329a1datagen2026-10-12 12:01 UTC193
            run.body.eq = 45;
8b8e83329a1datagen2026-10-12 12:01 UTC194
            run.body.girth = girth;
8b8e83329a1datagen2026-10-12 12:01 UTC195
            run.standing.suspicion = SUSPICION_LIMIT;
8b8e83329a1datagen2026-10-12 12:01 UTC196
        })
8b8e83329a1datagen2026-10-12 12:01 UTC197
    };
8b8e83329a1datagen2026-10-12 12:01 UTC198
    let thin = send(
8b8e83329a1datagen2026-10-12 12:01 UTC199
        &staff(4.5),
8b8e83329a1datagen2026-10-12 12:01 UTC200
        "JELQ",
8b8e83329a1datagen2026-10-12 12:01 UTC201
        "/runs/cafebabe?technique=edge",
8b8e83329a1datagen2026-10-12 12:01 UTC202
        &[],
8b8e83329a1datagen2026-10-12 12:01 UTC203
        "",
8b8e83329a1datagen2026-10-12 12:01 UTC204
    )
8b8e83329a1datagen2026-10-12 12:01 UTC205
    .await;
8b8e83329a1datagen2026-10-12 12:01 UTC206
    assert_eq!(thin.status(), StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS);
8b8e83329a1datagen2026-10-12 12:01 UTC207
    assert_eq!(header(&thin, "x-shame"), "5");
8b8e83329a1datagen2026-10-12 12:01 UTC208
8b8e83329a1datagen2026-10-12 12:01 UTC209
    let thick = send(
8b8e83329a1datagen2026-10-12 12:01 UTC210
        &staff(5.2),
8b8e83329a1datagen2026-10-12 12:01 UTC211
        "JELQ",
8b8e83329a1datagen2026-10-12 12:01 UTC212
        "/runs/cafebabe?technique=edge",
8b8e83329a1datagen2026-10-12 12:01 UTC213
        &[],
8b8e83329a1datagen2026-10-12 12:01 UTC214
        "",
8b8e83329a1datagen2026-10-12 12:01 UTC215
    )
8b8e83329a1datagen2026-10-12 12:01 UTC216
    .await;
8b8e83329a1datagen2026-10-12 12:01 UTC217
    assert_eq!(thick.status(), StatusCode::OK);
8b8e83329a1datagen2026-10-12 12:01 UTC218
    assert!(
8b8e83329a1datagen2026-10-12 12:01 UTC219
        warnings(&thick)
8b8e83329a1datagen2026-10-12 12:01 UTC220
            .iter()
8b8e83329a1datagen2026-10-12 12:01 UTC221
            .any(|w| w.contains("the gym staff have seen worse"))
8b8e83329a1datagen2026-10-12 12:01 UTC222
    );
8b8e83329a1datagen2026-10-12 12:01 UTC223
}
8b8e83329a1datagen2026-10-12 12:01 UTC224
8b8e83329a1datagen2026-10-12 12:01 UTC225
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC226
async fn the_kitchen_is_only_on_the_home_floors() {
8b8e83329a1datagen2026-10-12 12:01 UTC227
    let absent = seeded_with(Floor::Bathroom, |_| {});
8b8e83329a1datagen2026-10-12 12:01 UTC228
    let no_kitchen = send(&absent, "POST", "/runs/cafebabe/kitchen", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC229
    assert_eq!(no_kitchen.status(), StatusCode::NOT_FOUND);
8b8e83329a1datagen2026-10-12 12:01 UTC230
    let view = send(&absent, "GET", "/runs/cafebabe", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC231
    assert!(!header(&view, "link").contains("/kitchen"));
8b8e83329a1datagen2026-10-12 12:01 UTC232
8b8e83329a1datagen2026-10-12 12:01 UTC233
    let app = seeded_with(Floor::Bedroom, |_| {});
8b8e83329a1datagen2026-10-12 12:01 UTC234
    let view = send(&app, "GET", "/runs/cafebabe", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC235
    assert!(header(&view, "link").contains("/kitchen"));
8b8e83329a1datagen2026-10-12 12:01 UTC236
    let raid = send(&app, "POST", "/runs/cafebabe/kitchen", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC237
    assert_ne!(raid.status(), StatusCode::NOT_FOUND);
8b8e83329a1datagen2026-10-12 12:01 UTC238
}
8b8e83329a1datagen2026-10-12 12:01 UTC239
8b8e83329a1datagen2026-10-12 12:01 UTC240
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC241
async fn any_other_action_takes_the_melon_off_the_table() {
8b8e83329a1datagen2026-10-12 12:01 UTC242
    for (method, uri) in [
8b8e83329a1datagen2026-10-12 12:01 UTC243
        ("JELQ", "/runs/cafebabe?technique=warmup"),
8b8e83329a1datagen2026-10-12 12:01 UTC244
        ("POST", "/runs/cafebabe/drawer"),
8b8e83329a1datagen2026-10-12 12:01 UTC245
    ] {
8b8e83329a1datagen2026-10-12 12:01 UTC246
        let app = seeded_with(Floor::Bedroom, |run| {
8b8e83329a1datagen2026-10-12 12:01 UTC247
            run.body.eq = 95;
8b8e83329a1datagen2026-10-12 12:01 UTC248
            run.here.kitchen.melon_offered = true;
8b8e83329a1datagen2026-10-12 12:01 UTC249
        });
8b8e83329a1datagen2026-10-12 12:01 UTC250
        send(&app, method, uri, &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC251
        let melon = send(&app, "POST", "/runs/cafebabe/kitchen/watermelon", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC252
        assert_eq!(melon.status(), StatusCode::CONFLICT, "{method} {uri}");
8b8e83329a1datagen2026-10-12 12:01 UTC253
    }
8b8e83329a1datagen2026-10-12 12:01 UTC254
}
8b8e83329a1datagen2026-10-12 12:01 UTC255
8b8e83329a1datagen2026-10-12 12:01 UTC256
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC257
async fn the_truth_works_under_any_scheme_and_costs_ten() {
8b8e83329a1datagen2026-10-12 12:01 UTC258
    let app = seeded_with(Floor::Bedroom, mum_at_the_door);
8b8e83329a1datagen2026-10-12 12:01 UTC259
    let truth = [("authorization", "Whatever honesty")];
8b8e83329a1datagen2026-10-12 12:01 UTC260
    let confessed = send(&app, "GET", "/runs/cafebabe", &truth, "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC261
    assert_eq!(confessed.status(), StatusCode::OK);
8b8e83329a1datagen2026-10-12 12:01 UTC262
    assert_eq!(header(&confessed, "x-shame"), "10");
8b8e83329a1datagen2026-10-12 12:01 UTC263
}
8b8e83329a1datagen2026-10-12 12:01 UTC264
8b8e83329a1datagen2026-10-12 12:01 UTC265
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC266
async fn mum_refuses_basic_and_stale_nonces() {
8b8e83329a1datagen2026-10-12 12:01 UTC267
    let bedroom = seeded_with(Floor::Bedroom, mum_at_the_door);
8b8e83329a1datagen2026-10-12 12:01 UTC268
    let basic = send(
8b8e83329a1datagen2026-10-12 12:01 UTC269
        &bedroom,
8b8e83329a1datagen2026-10-12 12:01 UTC270
        "GET",
8b8e83329a1datagen2026-10-12 12:01 UTC271
        "/runs/cafebabe",
8b8e83329a1datagen2026-10-12 12:01 UTC272
        &[("authorization", "Basic aG9tZXdvcms=")],
8b8e83329a1datagen2026-10-12 12:01 UTC273
        "",
8b8e83329a1datagen2026-10-12 12:01 UTC274
    )
8b8e83329a1datagen2026-10-12 12:01 UTC275
    .await;
8b8e83329a1datagen2026-10-12 12:01 UTC276
    assert_eq!(basic.status(), StatusCode::UNAUTHORIZED);
8b8e83329a1datagen2026-10-12 12:01 UTC277
    assert!(
8b8e83329a1datagen2026-10-12 12:01 UTC278
        json(basic).await["type"]
8b8e83329a1datagen2026-10-12 12:01 UTC279
            .as_str()
8b8e83329a1datagen2026-10-12 12:01 UTC280
            .unwrap()
8b8e83329a1datagen2026-10-12 12:01 UTC281
            .ends_with("/basic")
8b8e83329a1datagen2026-10-12 12:01 UTC282
    );
8b8e83329a1datagen2026-10-12 12:01 UTC283
8b8e83329a1datagen2026-10-12 12:01 UTC284
    let nans = seeded_with(Floor::Nans, mum_at_the_door);
8b8e83329a1datagen2026-10-12 12:01 UTC285
    let stale = [(
8b8e83329a1datagen2026-10-12 12:01 UTC286
        "authorization",
8b8e83329a1datagen2026-10-12 12:01 UTC287
        "Digest username=\"on a call\", nonce=\"yesterday\"",
8b8e83329a1datagen2026-10-12 12:01 UTC288
    )];
8b8e83329a1datagen2026-10-12 12:01 UTC289
    let refused = send(&nans, "GET", "/runs/cafebabe", &stale, "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC290
    assert_eq!(refused.status(), StatusCode::UNAUTHORIZED);
8b8e83329a1datagen2026-10-12 12:01 UTC291
    assert!(
8b8e83329a1datagen2026-10-12 12:01 UTC292
        json(refused).await["type"]
8b8e83329a1datagen2026-10-12 12:01 UTC293
            .as_str()
8b8e83329a1datagen2026-10-12 12:01 UTC294
            .unwrap()
8b8e83329a1datagen2026-10-12 12:01 UTC295
            .ends_with("stale-nonce")
8b8e83329a1datagen2026-10-12 12:01 UTC296
    );
8b8e83329a1datagen2026-10-12 12:01 UTC297
}
8b8e83329a1datagen2026-10-12 12:01 UTC298
8b8e83329a1datagen2026-10-12 12:01 UTC299
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC300
async fn american_players_can_say_studying() {
8b8e83329a1datagen2026-10-12 12:01 UTC301
    let app = seeded_with(Floor::Bedroom, mum_at_the_door);
8b8e83329a1datagen2026-10-12 12:01 UTC302
    let excuse = [
8b8e83329a1datagen2026-10-12 12:01 UTC303
        ("accept-language", "en-US"),
8b8e83329a1datagen2026-10-12 12:01 UTC304
        ("authorization", "Excuse \"Studying\""),
8b8e83329a1datagen2026-10-12 12:01 UTC305
    ];
8b8e83329a1datagen2026-10-12 12:01 UTC306
    let response = send(&app, "GET", "/runs/cafebabe", &excuse, "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC307
    assert_eq!(response.status(), StatusCode::OK);
8b8e83329a1datagen2026-10-12 12:01 UTC308
}
8b8e83329a1datagen2026-10-12 12:01 UTC309
8b8e83329a1datagen2026-10-12 12:01 UTC310
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC311
async fn raids_get_noisier_and_the_household_notices() {
8b8e83329a1datagen2026-10-12 12:01 UTC312
    let told = |suspicion: u8| {
8b8e83329a1datagen2026-10-12 12:01 UTC313
        (0..400u64)
8b8e83329a1datagen2026-10-12 12:01 UTC314
            .filter(|&seed| {
8b8e83329a1datagen2026-10-12 12:01 UTC315
                let mut run = Run::new("cafebabe".into(), "cafe".into(), seed);
8b8e83329a1datagen2026-10-12 12:01 UTC316
                run.body.eq = 40;
8b8e83329a1datagen2026-10-12 12:01 UTC317
                run.standing.suspicion = suspicion;
f16ab17c8ff6atagen2026-10-13 11:14 UTC318
                run.raid_kitchen().is_err() && run.here.kitchen.closed
8b8e83329a1datagen2026-10-12 12:01 UTC319
            })
8b8e83329a1datagen2026-10-12 12:01 UTC320
            .count()
8b8e83329a1datagen2026-10-12 12:01 UTC321
    };
8b8e83329a1datagen2026-10-12 12:01 UTC322
    let quiet = told(0);
8b8e83329a1datagen2026-10-12 12:01 UTC323
    let noisy = told(4);
8b8e83329a1datagen2026-10-12 12:01 UTC324
    assert!(
8b8e83329a1datagen2026-10-12 12:01 UTC325
        (60..=150).contains(&quiet),
8b8e83329a1datagen2026-10-12 12:01 UTC326
        "a quiet house told {quiet} of 400 raids, about 100 expected"
8b8e83329a1datagen2026-10-12 12:01 UTC327
    );
8b8e83329a1datagen2026-10-12 12:01 UTC328
    assert!(
8b8e83329a1datagen2026-10-12 12:01 UTC329
        (240..=340).contains(&noisy),
8b8e83329a1datagen2026-10-12 12:01 UTC330
        "a noisy house told {noisy} of 400 raids, about 300 expected"
8b8e83329a1datagen2026-10-12 12:01 UTC331
    );
8b8e83329a1datagen2026-10-12 12:01 UTC332
8b8e83329a1datagen2026-10-12 12:01 UTC333
    let mut run = Run::new("cafebabe".into(), "cafe".into(), SEED);
8b8e83329a1datagen2026-10-12 12:01 UTC334
    run.body.eq = 40;
f16ab17c8ff6atagen2026-10-13 11:14 UTC335
    let _ = run.raid_kitchen();
8b8e83329a1datagen2026-10-12 12:01 UTC336
    assert_eq!(run.standing.suspicion, 1, "every raid makes a little noise");
8b8e83329a1datagen2026-10-12 12:01 UTC337
}
f16ab17c8ff6atagen2026-10-13 11:14 UTC338
f16ab17c8ff6atagen2026-10-13 11:14 UTC339
#[tokio::test]
f16ab17c8ff6atagen2026-10-13 11:14 UTC340
async fn one_telling_closes_the_kitchen_and_its_link() {
f16ab17c8ff6atagen2026-10-13 11:14 UTC341
    let mut told = None;
9adb509e6685atagen2026-10-14 08:54 UTC342
    for seed in 0..20 {
9adb509e6685atagen2026-10-14 08:54 UTC343
        let app = app_with(Floor::Bedroom, seed, |run| run.standing.suspicion = 4);
f16ab17c8ff6atagen2026-10-13 11:14 UTC344
        let raid = send(&app, "POST", "/runs/cafebabe/kitchen", &[], "").await;
f16ab17c8ff6atagen2026-10-13 11:14 UTC345
        if raid.status() == StatusCode::GONE {
9adb509e6685atagen2026-10-14 08:54 UTC346
            told = Some((app, raid));
f16ab17c8ff6atagen2026-10-13 11:14 UTC347
            break;
f16ab17c8ff6atagen2026-10-13 11:14 UTC348
        }
f16ab17c8ff6atagen2026-10-13 11:14 UTC349
        assert!(
f16ab17c8ff6atagen2026-10-13 11:14 UTC350
            warnings(&raid).iter().all(|w| !w.contains("Stop raiding")),
f16ab17c8ff6atagen2026-10-13 11:14 UTC351
            "there is no warning before the telling"
f16ab17c8ff6atagen2026-10-13 11:14 UTC352
        );
f16ab17c8ff6atagen2026-10-13 11:14 UTC353
    }
9adb509e6685atagen2026-10-14 08:54 UTC354
    let (app, told) = told.expect("a noisy house tells within twenty houses");
f16ab17c8ff6atagen2026-10-13 11:14 UTC355
    assert!(
aef6f6498ea2atagen2026-10-13 11:23 UTC356
        warnings(&told)
aef6f6498ea2atagen2026-10-13 11:23 UTC357
            .iter()
aef6f6498ea2atagen2026-10-13 11:23 UTC358
            .any(|w| w.contains("Stop raiding the kitchen."))
f16ab17c8ff6atagen2026-10-13 11:14 UTC359
    );
aef6f6498ea2atagen2026-10-13 11:23 UTC360
    assert_eq!(json(told).await["detail"], "The kitchen is off limits.");
f16ab17c8ff6atagen2026-10-13 11:14 UTC361
    let view = send(&app, "GET", "/runs/cafebabe", &[], "").await;
f16ab17c8ff6atagen2026-10-13 11:14 UTC362
    assert!(!header(&view, "link").contains("/kitchen"));
f16ab17c8ff6atagen2026-10-13 11:14 UTC363
}
f16ab17c8ff6atagen2026-10-13 11:14 UTC364
f16ab17c8ff6atagen2026-10-13 11:14 UTC365
#[tokio::test]
f16ab17c8ff6atagen2026-10-13 11:14 UTC366
async fn an_excuse_without_a_scheme_is_explained_for_free() {
f16ab17c8ff6atagen2026-10-13 11:14 UTC367
    let app = seeded_with(Floor::Bedroom, mum_at_the_door);
f16ab17c8ff6atagen2026-10-13 11:14 UTC368
    for said in ["homework", "on a call", "homework", "homework"] {
f16ab17c8ff6atagen2026-10-13 11:14 UTC369
        let response = send(
f16ab17c8ff6atagen2026-10-13 11:14 UTC370
            &app,
f16ab17c8ff6atagen2026-10-13 11:14 UTC371
            "GET",
f16ab17c8ff6atagen2026-10-13 11:14 UTC372
            "/runs/cafebabe",
f16ab17c8ff6atagen2026-10-13 11:14 UTC373
            &[("authorization", said)],
f16ab17c8ff6atagen2026-10-13 11:14 UTC374
            "",
f16ab17c8ff6atagen2026-10-13 11:14 UTC375
        )
f16ab17c8ff6atagen2026-10-13 11:14 UTC376
        .await;
f16ab17c8ff6atagen2026-10-13 11:14 UTC377
        assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
f16ab17c8ff6atagen2026-10-13 11:14 UTC378
        assert!(
f16ab17c8ff6atagen2026-10-13 11:14 UTC379
            json(response).await["detail"]
f16ab17c8ff6atagen2026-10-13 11:14 UTC380
                .as_str()
f16ab17c8ff6atagen2026-10-13 11:14 UTC381
                .unwrap()
f16ab17c8ff6atagen2026-10-13 11:14 UTC382
                .contains("Excuse \"homework\"")
f16ab17c8ff6atagen2026-10-13 11:14 UTC383
        );
f16ab17c8ff6atagen2026-10-13 11:14 UTC384
    }
f16ab17c8ff6atagen2026-10-13 11:14 UTC385
    let view = send(&app, "GET", "/runs/cafebabe", &[], "").await;
f16ab17c8ff6atagen2026-10-13 11:14 UTC386
    assert_eq!(header(&view, "x-shame"), "0");
f16ab17c8ff6atagen2026-10-13 11:14 UTC387
    let excused = send(
f16ab17c8ff6atagen2026-10-13 11:14 UTC388
        &app,
f16ab17c8ff6atagen2026-10-13 11:14 UTC389
        "GET",
f16ab17c8ff6atagen2026-10-13 11:14 UTC390
        "/runs/cafebabe",
f16ab17c8ff6atagen2026-10-13 11:14 UTC391
        &[("authorization", "Excuse \"homework\"")],
f16ab17c8ff6atagen2026-10-13 11:14 UTC392
        "",
f16ab17c8ff6atagen2026-10-13 11:14 UTC393
    )
f16ab17c8ff6atagen2026-10-13 11:14 UTC394
    .await;
f16ab17c8ff6atagen2026-10-13 11:14 UTC395
    assert_eq!(excused.status(), StatusCode::OK);
f16ab17c8ff6atagen2026-10-13 11:14 UTC396
}
886159fd0fe7atagen2026-10-13 12:48 UTC397
886159fd0fe7atagen2026-10-13 12:48 UTC398
#[tokio::test]
886159fd0fe7atagen2026-10-13 12:48 UTC399
async fn mum_leaves_the_way_her_errand_suggests() {
886159fd0fe7atagen2026-10-13 12:48 UTC400
    let app = seeded_with(Floor::Bedroom, mum_at_the_door);
886159fd0fe7atagen2026-10-13 12:48 UTC401
    let pockets = send(&app, "GET", "/runs/cafebabe/inventory", &[], "").await;
886159fd0fe7atagen2026-10-13 12:48 UTC402
    assert_eq!(
886159fd0fe7atagen2026-10-13 12:48 UTC403
        header(&pockets, "www-authenticate"),
886159fd0fe7atagen2026-10-13 12:48 UTC404
        "Excuse realm=\"household\""
886159fd0fe7atagen2026-10-13 12:48 UTC405
    );
886159fd0fe7atagen2026-10-13 12:48 UTC406
    let excused = send(
886159fd0fe7atagen2026-10-13 12:48 UTC407
        &app,
886159fd0fe7atagen2026-10-13 12:48 UTC408
        "GET",
886159fd0fe7atagen2026-10-13 12:48 UTC409
        "/runs/cafebabe",
886159fd0fe7atagen2026-10-13 12:48 UTC410
        &[("authorization", "Excuse \"homework\"")],
886159fd0fe7atagen2026-10-13 12:48 UTC411
        "",
886159fd0fe7atagen2026-10-13 12:48 UTC412
    )
886159fd0fe7atagen2026-10-13 12:48 UTC413
    .await;
886159fd0fe7atagen2026-10-13 12:48 UTC414
    assert!(warnings(&excused).iter().any(|w| w.contains("the pipes")));
886159fd0fe7atagen2026-10-13 12:48 UTC415
    assert!(excused.headers().get("www-authenticate").is_none());
886159fd0fe7atagen2026-10-13 12:48 UTC416
}
886159fd0fe7atagen2026-10-13 12:48 UTC417
886159fd0fe7atagen2026-10-13 12:48 UTC418
#[tokio::test]
886159fd0fe7atagen2026-10-13 12:48 UTC419
async fn a_stale_nonce_is_explained_for_free() {
886159fd0fe7atagen2026-10-13 12:48 UTC420
    let app = seeded_with(Floor::Nans, mum_at_the_door);
886159fd0fe7atagen2026-10-13 12:48 UTC421
    for _ in 0..4 {
886159fd0fe7atagen2026-10-13 12:48 UTC422
        let stale = send(
886159fd0fe7atagen2026-10-13 12:48 UTC423
            &app,
886159fd0fe7atagen2026-10-13 12:48 UTC424
            "GET",
886159fd0fe7atagen2026-10-13 12:48 UTC425
            "/runs/cafebabe",
886159fd0fe7atagen2026-10-13 12:48 UTC426
            &[(
886159fd0fe7atagen2026-10-13 12:48 UTC427
                "authorization",
886159fd0fe7atagen2026-10-13 12:48 UTC428
                "Digest username=\"helping Nan\", nonce=\"old\"",
886159fd0fe7atagen2026-10-13 12:48 UTC429
            )],
886159fd0fe7atagen2026-10-13 12:48 UTC430
            "",
886159fd0fe7atagen2026-10-13 12:48 UTC431
        )
886159fd0fe7atagen2026-10-13 12:48 UTC432
        .await;
886159fd0fe7atagen2026-10-13 12:48 UTC433
        assert_eq!(stale.status(), StatusCode::UNAUTHORIZED);
886159fd0fe7atagen2026-10-13 12:48 UTC434
        assert!(header(&stale, "www-authenticate").contains("nonce=\"abc\""));
886159fd0fe7atagen2026-10-13 12:48 UTC435
    }
886159fd0fe7atagen2026-10-13 12:48 UTC436
    let current = send(
886159fd0fe7atagen2026-10-13 12:48 UTC437
        &app,
886159fd0fe7atagen2026-10-13 12:48 UTC438
        "GET",
886159fd0fe7atagen2026-10-13 12:48 UTC439
        "/runs/cafebabe",
886159fd0fe7atagen2026-10-13 12:48 UTC440
        &[(
886159fd0fe7atagen2026-10-13 12:48 UTC441
            "authorization",
886159fd0fe7atagen2026-10-13 12:48 UTC442
            "Digest username=\"helping Nan\", nonce=\"abc\"",
886159fd0fe7atagen2026-10-13 12:48 UTC443
        )],
886159fd0fe7atagen2026-10-13 12:48 UTC444
        "",
886159fd0fe7atagen2026-10-13 12:48 UTC445
    )
886159fd0fe7atagen2026-10-13 12:48 UTC446
    .await;
886159fd0fe7atagen2026-10-13 12:48 UTC447
    assert_eq!(current.status(), StatusCode::OK);
886159fd0fe7atagen2026-10-13 12:48 UTC448
}
2176d9a94bd9atagen2026-10-14 11:03 UTC449
2176d9a94bd9atagen2026-10-14 11:03 UTC450
#[tokio::test]
2176d9a94bd9atagen2026-10-14 11:03 UTC451
async fn mum_announces_herself_but_never_returns_on_the_turn_she_left() {
2176d9a94bd9atagen2026-10-14 11:03 UTC452
    let mut arrivals = 0;
2176d9a94bd9atagen2026-10-14 11:03 UTC453
    for seed in 0..200u64 {
2176d9a94bd9atagen2026-10-14 11:03 UTC454
        let app = app_with(Floor::Bedroom, seed, |run| {
2176d9a94bd9atagen2026-10-14 11:03 UTC455
            mum_at_the_door(run);
2176d9a94bd9atagen2026-10-14 11:03 UTC456
            run.body.eq = 60;
2176d9a94bd9atagen2026-10-14 11:03 UTC457
            run.here.brother_told = true;
2176d9a94bd9atagen2026-10-14 11:03 UTC458
            run.standing.suspicion = 4;
2176d9a94bd9atagen2026-10-14 11:03 UTC459
        });
2176d9a94bd9atagen2026-10-14 11:03 UTC460
        let excused = send(
2176d9a94bd9atagen2026-10-14 11:03 UTC461
            &app,
2176d9a94bd9atagen2026-10-14 11:03 UTC462
            "JELQ",
2176d9a94bd9atagen2026-10-14 11:03 UTC463
            "/runs/cafebabe?technique=stretch&intensity=10",
2176d9a94bd9atagen2026-10-14 11:03 UTC464
            &[("authorization", "Excuse \"homework\"")],
2176d9a94bd9atagen2026-10-14 11:03 UTC465
            "",
2176d9a94bd9atagen2026-10-14 11:03 UTC466
        )
2176d9a94bd9atagen2026-10-14 11:03 UTC467
        .await;
2176d9a94bd9atagen2026-10-14 11:03 UTC468
        assert!(
2176d9a94bd9atagen2026-10-14 11:03 UTC469
            excused.headers().get("www-authenticate").is_none(),
2176d9a94bd9atagen2026-10-14 11:03 UTC470
            "seed {seed} fetched Mum straight back"
2176d9a94bd9atagen2026-10-14 11:03 UTC471
        );
2176d9a94bd9atagen2026-10-14 11:03 UTC472
        let again = send(
2176d9a94bd9atagen2026-10-14 11:03 UTC473
            &app,
2176d9a94bd9atagen2026-10-14 11:03 UTC474
            "JELQ",
2176d9a94bd9atagen2026-10-14 11:03 UTC475
            "/runs/cafebabe?technique=stretch&intensity=10",
2176d9a94bd9atagen2026-10-14 11:03 UTC476
            &[],
2176d9a94bd9atagen2026-10-14 11:03 UTC477
            "",
2176d9a94bd9atagen2026-10-14 11:03 UTC478
        )
2176d9a94bd9atagen2026-10-14 11:03 UTC479
        .await;
2176d9a94bd9atagen2026-10-14 11:03 UTC480
        if again.headers().get("www-authenticate").is_some() {
2176d9a94bd9atagen2026-10-14 11:03 UTC481
            arrivals += 1;
2176d9a94bd9atagen2026-10-14 11:03 UTC482
            assert!(
2176d9a94bd9atagen2026-10-14 11:03 UTC483
                warnings(&again)
2176d9a94bd9atagen2026-10-14 11:03 UTC484
                    .iter()
2176d9a94bd9atagen2026-10-14 11:03 UTC485
                    .any(|w| w.contains("Mum is at the door. She"))
2176d9a94bd9atagen2026-10-14 11:03 UTC486
            );
2176d9a94bd9atagen2026-10-14 11:03 UTC487
        }
2176d9a94bd9atagen2026-10-14 11:03 UTC488
    }
2176d9a94bd9atagen2026-10-14 11:03 UTC489
    assert!(arrivals > 0, "Mum never came back at all");
2176d9a94bd9atagen2026-10-14 11:03 UTC490
}