atagen

jelq

public

an http based roguelike rpg about jelqing

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

Blame: src/tests/floors.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 only_the_key_opens_the_cage() {
545ad65826ddatagen2026-10-14 11:19 UTC5
    let app = seeded_with(Floor::ChangingRoom, |run| {
545ad65826ddatagen2026-10-14 11:19 UTC6
        run.items.kit.cage = true;
545ad65826ddatagen2026-10-14 11:19 UTC7
        run.standing.cash = 30;
545ad65826ddatagen2026-10-14 11:19 UTC8
    });
8b8e83329a1datagen2026-10-12 12:01 UTC9
    send(&app, "POST", "/runs/cafebabe/shop/ice-pack", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC10
    let still = send(
8b8e83329a1datagen2026-10-12 12:01 UTC11
        &app,
8b8e83329a1datagen2026-10-12 12:01 UTC12
        "GET",
8b8e83329a1datagen2026-10-12 12:01 UTC13
        "/runs/cafebabe/inventory/chastity-cage",
8b8e83329a1datagen2026-10-12 12:01 UTC14
        &[],
8b8e83329a1datagen2026-10-12 12:01 UTC15
        "",
8b8e83329a1datagen2026-10-12 12:01 UTC16
    )
8b8e83329a1datagen2026-10-12 12:01 UTC17
    .await;
8b8e83329a1datagen2026-10-12 12:01 UTC18
    assert_eq!(still.status(), StatusCode::OK);
8b8e83329a1datagen2026-10-12 12:01 UTC19
    send(&app, "POST", "/runs/cafebabe/shop/cage-key", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC20
    let gone = send(
8b8e83329a1datagen2026-10-12 12:01 UTC21
        &app,
8b8e83329a1datagen2026-10-12 12:01 UTC22
        "GET",
8b8e83329a1datagen2026-10-12 12:01 UTC23
        "/runs/cafebabe/inventory/chastity-cage",
8b8e83329a1datagen2026-10-12 12:01 UTC24
        &[],
8b8e83329a1datagen2026-10-12 12:01 UTC25
        "",
8b8e83329a1datagen2026-10-12 12:01 UTC26
    )
8b8e83329a1datagen2026-10-12 12:01 UTC27
    .await;
8b8e83329a1datagen2026-10-12 12:01 UTC28
    assert_eq!(gone.status(), StatusCode::NOT_FOUND);
8b8e83329a1datagen2026-10-12 12:01 UTC29
}
8b8e83329a1datagen2026-10-12 12:01 UTC30
8b8e83329a1datagen2026-10-12 12:01 UTC31
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC32
async fn pump_bro_upsells() {
8b8e83329a1datagen2026-10-12 12:01 UTC33
    let closed = seeded_with(Floor::Bedroom, |_| {});
8b8e83329a1datagen2026-10-12 12:01 UTC34
    let response = send(&closed, "GET", "/runs/cafebabe/shop", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC35
    assert_eq!(response.status(), StatusCode::NOT_FOUND);
8b8e83329a1datagen2026-10-12 12:01 UTC36
8b8e83329a1datagen2026-10-12 12:01 UTC37
    let app = seeded_with(Floor::ChangingRoom, |_| {});
8b8e83329a1datagen2026-10-12 12:01 UTC38
    let pump = send(&app, "POST", "/runs/cafebabe/shop/pump", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC39
    assert_eq!(pump.status(), StatusCode::UPGRADE_REQUIRED);
8b8e83329a1datagen2026-10-12 12:01 UTC40
    assert_eq!(header(&pump, "upgrade"), "pump-pro/2.0");
8b8e83329a1datagen2026-10-12 12:01 UTC41
    let pro = send(&app, "POST", "/runs/cafebabe/shop/pump-pro", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC42
    assert_eq!(pro.status(), StatusCode::CREATED);
8b8e83329a1datagen2026-10-12 12:01 UTC43
    let broke = send(&app, "POST", "/runs/cafebabe/shop/extender", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC44
    assert_eq!(broke.status(), StatusCode::PAYMENT_REQUIRED);
8b8e83329a1datagen2026-10-12 12:01 UTC45
}
8b8e83329a1datagen2026-10-12 12:01 UTC46
8b8e83329a1datagen2026-10-12 12:01 UTC47
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC48
async fn the_elder_is_stale() {
8b8e83329a1datagen2026-10-12 12:01 UTC49
    let app = seeded_with(Floor::Forum, |run| run.body.length = 5.0);
8b8e83329a1datagen2026-10-12 12:01 UTC50
    let unverified = send(&app, "GET", "/runs/cafebabe/elder", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC51
    assert_eq!(
8b8e83329a1datagen2026-10-12 12:01 UTC52
        unverified.status(),
8b8e83329a1datagen2026-10-12 12:01 UTC53
        StatusCode::PROXY_AUTHENTICATION_REQUIRED
8b8e83329a1datagen2026-10-12 12:01 UTC54
    );
8b8e83329a1datagen2026-10-12 12:01 UTC55
    let lie = send(
8b8e83329a1datagen2026-10-12 12:01 UTC56
        &app,
8b8e83329a1datagen2026-10-12 12:01 UTC57
        "GET",
8b8e83329a1datagen2026-10-12 12:01 UTC58
        "/runs/cafebabe/elder",
8b8e83329a1datagen2026-10-12 12:01 UTC59
        &[("proxy-authorization", "Ruler 9")],
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!(lie.status(), StatusCode::BAD_REQUEST);
8b8e83329a1datagen2026-10-12 12:01 UTC64
    let csv = send(
8b8e83329a1datagen2026-10-12 12:01 UTC65
        &app,
8b8e83329a1datagen2026-10-12 12:01 UTC66
        "GET",
8b8e83329a1datagen2026-10-12 12:01 UTC67
        "/runs/cafebabe/elder",
8b8e83329a1datagen2026-10-12 12:01 UTC68
        &[("proxy-authorization", "Ruler 5.0")],
8b8e83329a1datagen2026-10-12 12:01 UTC69
        "",
8b8e83329a1datagen2026-10-12 12:01 UTC70
    )
8b8e83329a1datagen2026-10-12 12:01 UTC71
    .await;
8b8e83329a1datagen2026-10-12 12:01 UTC72
    assert_eq!(csv.status(), StatusCode::OK);
8b8e83329a1datagen2026-10-12 12:01 UTC73
    let stale = send(
8b8e83329a1datagen2026-10-12 12:01 UTC74
        &app,
8b8e83329a1datagen2026-10-12 12:01 UTC75
        "GET",
8b8e83329a1datagen2026-10-12 12:01 UTC76
        "/runs/cafebabe/elder",
8b8e83329a1datagen2026-10-12 12:01 UTC77
        &[("if-modified-since", "Mon, 15 Jun 2009 00:00:00 GMT")],
8b8e83329a1datagen2026-10-12 12:01 UTC78
        "",
8b8e83329a1datagen2026-10-12 12:01 UTC79
    )
8b8e83329a1datagen2026-10-12 12:01 UTC80
    .await;
8b8e83329a1datagen2026-10-12 12:01 UTC81
    assert_eq!(stale.status(), StatusCode::NOT_MODIFIED);
8b8e83329a1datagen2026-10-12 12:01 UTC82
}
8b8e83329a1datagen2026-10-12 12:01 UTC83
8b8e83329a1datagen2026-10-12 12:01 UTC84
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC85
async fn the_hotel_has_a_captive_portal() {
8b8e83329a1datagen2026-10-12 12:01 UTC86
    let app = seeded_with(Floor::Hotel, |_| {});
8b8e83329a1datagen2026-10-12 12:01 UTC87
    let blocked = send(&app, "GET", "/runs/cafebabe", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC88
    assert_eq!(
8b8e83329a1datagen2026-10-12 12:01 UTC89
        blocked.status(),
8b8e83329a1datagen2026-10-12 12:01 UTC90
        StatusCode::NETWORK_AUTHENTICATION_REQUIRED
8b8e83329a1datagen2026-10-12 12:01 UTC91
    );
8b8e83329a1datagen2026-10-12 12:01 UTC92
    let accepted = send(&app, "POST", "/runs/cafebabe/portal", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC93
    assert_eq!(accepted.status(), StatusCode::SEE_OTHER);
8b8e83329a1datagen2026-10-12 12:01 UTC94
    let online = send(&app, "GET", "/runs/cafebabe", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC95
    assert_eq!(online.status(), StatusCode::OK);
8b8e83329a1datagen2026-10-12 12:01 UTC96
}
8b8e83329a1datagen2026-10-12 12:01 UTC97
8b8e83329a1datagen2026-10-12 12:01 UTC98
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC99
async fn caged_drawers_are_noisy_and_eventually_give_up_the_key() {
8b8e83329a1datagen2026-10-12 12:01 UTC100
    let app = seeded_with(Floor::ChangingRoom, |run| run.items.kit.cage = true);
8b8e83329a1datagen2026-10-12 12:01 UTC101
    let mut statuses = Vec::new();
8b8e83329a1datagen2026-10-12 12:01 UTC102
    for _ in 0..30 {
8b8e83329a1datagen2026-10-12 12:01 UTC103
        let response = send(&app, "POST", "/runs/cafebabe/drawer", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC104
        statuses.push(response.status());
8b8e83329a1datagen2026-10-12 12:01 UTC105
        if response.status() == StatusCode::OK {
8b8e83329a1datagen2026-10-12 12:01 UTC106
            break;
8b8e83329a1datagen2026-10-12 12:01 UTC107
        }
8b8e83329a1datagen2026-10-12 12:01 UTC108
    }
8b8e83329a1datagen2026-10-12 12:01 UTC109
    assert_eq!(statuses.last(), Some(&StatusCode::OK), "{statuses:?}");
8b8e83329a1datagen2026-10-12 12:01 UTC110
    assert!(
8b8e83329a1datagen2026-10-12 12:01 UTC111
        statuses[..statuses.len() - 1]
8b8e83329a1datagen2026-10-12 12:01 UTC112
            .iter()
8b8e83329a1datagen2026-10-12 12:01 UTC113
            .all(|s| *s == StatusCode::NO_CONTENT)
8b8e83329a1datagen2026-10-12 12:01 UTC114
    );
8b8e83329a1datagen2026-10-12 12:01 UTC115
}
8b8e83329a1datagen2026-10-12 12:01 UTC116
8b8e83329a1datagen2026-10-12 12:01 UTC117
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC118
async fn an_open_tab_is_linked_and_jelq_still_answers_with_choices() {
8b8e83329a1datagen2026-10-12 12:01 UTC119
    let app = seeded_with(Floor::Bathroom, |run| {
8b8e83329a1datagen2026-10-12 12:01 UTC120
        run.online.story = Story::Pending;
8b8e83329a1datagen2026-10-12 12:01 UTC121
    });
8b8e83329a1datagen2026-10-12 12:01 UTC122
    let response = send(&app, "GET", "/runs/cafebabe", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC123
    let links = header(&response, "link");
8b8e83329a1datagen2026-10-12 12:01 UTC124
    assert!(links.contains("/branches/forum>; rel=\"https://jelq.dev/rels/branch\""));
8b8e83329a1datagen2026-10-12 12:01 UTC125
    assert!(links.contains("/branches/close-tab>; rel=\"https://jelq.dev/rels/branch\""));
8b8e83329a1datagen2026-10-12 12:01 UTC126
    assert!(links.contains("?technique=wet>"));
8b8e83329a1datagen2026-10-12 12:01 UTC127
    let jelq = send(&app, "JELQ", "/runs/cafebabe?technique=edge", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC128
    assert_eq!(jelq.status(), StatusCode::MULTIPLE_CHOICES);
8b8e83329a1datagen2026-10-12 12:01 UTC129
}
8b8e83329a1datagen2026-10-12 12:01 UTC130
8b8e83329a1datagen2026-10-12 12:01 UTC131
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC132
async fn either_story_choice_is_reachable_during_cooldown() {
8b8e83329a1datagen2026-10-12 12:01 UTC133
    for choice in ["forum", "close-tab"] {
8b8e83329a1datagen2026-10-12 12:01 UTC134
        let app = seeded_with(Floor::Bathroom, |run| {
8b8e83329a1datagen2026-10-12 12:01 UTC135
            run.turn = 9;
8b8e83329a1datagen2026-10-12 12:01 UTC136
            run.body.eq = 45;
8b8e83329a1datagen2026-10-12 12:01 UTC137
            run.body.stamina = 1;
8b8e83329a1datagen2026-10-12 12:01 UTC138
            run.items.stores.door_locks = 1;
8b8e83329a1datagen2026-10-12 12:01 UTC139
        });
8b8e83329a1datagen2026-10-12 12:01 UTC140
        let tenth = send(&app, "JELQ", "/runs/cafebabe?technique=edge", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC141
        assert_eq!(tenth.status(), StatusCode::OK);
8b8e83329a1datagen2026-10-12 12:01 UTC142
        let response = send(&app, "GET", "/runs/cafebabe", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC143
        assert!(!header(&response, "link").contains("?technique="));
8b8e83329a1datagen2026-10-12 12:01 UTC144
        let path = format!("/runs/cafebabe/branches/{choice}");
8b8e83329a1datagen2026-10-12 12:01 UTC145
        assert!(
8b8e83329a1datagen2026-10-12 12:01 UTC146
            header(&response, "link")
8b8e83329a1datagen2026-10-12 12:01 UTC147
                .contains(&format!("<{path}>; rel=\"https://jelq.dev/rels/branch\""))
8b8e83329a1datagen2026-10-12 12:01 UTC148
        );
8b8e83329a1datagen2026-10-12 12:01 UTC149
        let chosen = send(&app, "POST", &path, &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC150
        assert_eq!(chosen.status(), StatusCode::SEE_OTHER);
8b8e83329a1datagen2026-10-12 12:01 UTC151
        assert!(!header(&chosen, "link").contains("/branches/"));
8b8e83329a1datagen2026-10-12 12:01 UTC152
        let repeated = send(&app, "POST", &path, &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC153
        assert_eq!(repeated.status(), StatusCode::CONFLICT);
8b8e83329a1datagen2026-10-12 12:01 UTC154
    }
8b8e83329a1datagen2026-10-12 12:01 UTC155
}
8b8e83329a1datagen2026-10-12 12:01 UTC156
8b8e83329a1datagen2026-10-12 12:01 UTC157
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC158
async fn the_thread_announces_itself_at_the_branch() {
8b8e83329a1datagen2026-10-12 12:01 UTC159
    let app = seeded_with(Floor::Bathroom, |run| {
8b8e83329a1datagen2026-10-12 12:01 UTC160
        run.turn = 8;
8b8e83329a1datagen2026-10-12 12:01 UTC161
        run.items.lubes.baby_oil = 9;
8b8e83329a1datagen2026-10-12 12:01 UTC162
    });
8b8e83329a1datagen2026-10-12 12:01 UTC163
    send(
8b8e83329a1datagen2026-10-12 12:01 UTC164
        &app,
8b8e83329a1datagen2026-10-12 12:01 UTC165
        "JELQ",
8b8e83329a1datagen2026-10-12 12:01 UTC166
        "/runs/cafebabe?technique=wet",
8b8e83329a1datagen2026-10-12 12:01 UTC167
        &[("content-type", "application/baby-oil")],
8b8e83329a1datagen2026-10-12 12:01 UTC168
        "",
8b8e83329a1datagen2026-10-12 12:01 UTC169
    )
8b8e83329a1datagen2026-10-12 12:01 UTC170
    .await;
8b8e83329a1datagen2026-10-12 12:01 UTC171
    let ninth = send(&app, "GET", "/runs/cafebabe", &[], "").await;
a11497af08aaatagen2026-10-13 12:04 UTC172
    assert!(!header(&ninth, "link").contains("/cafebabe/thread>"));
8b8e83329a1datagen2026-10-12 12:01 UTC173
    let tenth = send(
8b8e83329a1datagen2026-10-12 12:01 UTC174
        &app,
8b8e83329a1datagen2026-10-12 12:01 UTC175
        "JELQ",
8b8e83329a1datagen2026-10-12 12:01 UTC176
        "/runs/cafebabe?technique=wet",
8b8e83329a1datagen2026-10-12 12:01 UTC177
        &[("content-type", "application/baby-oil")],
8b8e83329a1datagen2026-10-12 12:01 UTC178
        "",
8b8e83329a1datagen2026-10-12 12:01 UTC179
    )
8b8e83329a1datagen2026-10-12 12:01 UTC180
    .await;
8b8e83329a1datagen2026-10-12 12:01 UTC181
    assert!(
8b8e83329a1datagen2026-10-12 12:01 UTC182
        tenth
8b8e83329a1datagen2026-10-12 12:01 UTC183
            .headers()
8b8e83329a1datagen2026-10-12 12:01 UTC184
            .get_all("warning")
8b8e83329a1datagen2026-10-12 12:01 UTC185
            .iter()
8b8e83329a1datagen2026-10-12 12:01 UTC186
            .any(|w| w.to_str().unwrap().contains("a forum thread from 2009"))
8b8e83329a1datagen2026-10-12 12:01 UTC187
    );
a11497af08aaatagen2026-10-13 12:04 UTC188
    assert!(header(&tenth, "link").contains("/cafebabe/thread>"));
8b8e83329a1datagen2026-10-12 12:01 UTC189
    let announced = send(&app, "GET", "/runs/cafebabe", &[], "").await;
a11497af08aaatagen2026-10-13 12:04 UTC190
    assert!(header(&announced, "link").contains("/cafebabe/thread>"));
8b8e83329a1datagen2026-10-12 12:01 UTC191
}
8b8e83329a1datagen2026-10-12 12:01 UTC192
8b8e83329a1datagen2026-10-12 12:01 UTC193
#[tokio::test]
a11497af08aaatagen2026-10-13 12:04 UTC194
async fn reading_the_thread_teaches_uli() {
8b8e83329a1datagen2026-10-12 12:01 UTC195
    let mut taught_runs = 0;
8b8e83329a1datagen2026-10-12 12:01 UTC196
    for seed in 0..100u64 {
8b8e83329a1datagen2026-10-12 12:01 UTC197
        let app = app_with(Floor::Bedroom, seed, |run| {
8b8e83329a1datagen2026-10-12 12:01 UTC198
            run.turn = BRANCH_TURN;
8b8e83329a1datagen2026-10-12 12:01 UTC199
            run.online.story = Story::ClosedTab;
880b29a0c11eatagen2026-10-13 09:27 UTC200
            run.online.meditation_taught = true;
8b8e83329a1datagen2026-10-12 12:01 UTC201
        });
8b8e83329a1datagen2026-10-12 12:01 UTC202
        let mut taught = false;
8b8e83329a1datagen2026-10-12 12:01 UTC203
        for _ in 0..2 {
a11497af08aaatagen2026-10-13 12:04 UTC204
            let read = send(&app, "GET", "/runs/cafebabe/thread", &[], "").await;
a11497af08aaatagen2026-10-13 12:04 UTC205
            assert_eq!(read.status(), StatusCode::OK, "seed {seed}");
3ac624339c98atagen2026-10-13 12:21 UTC206
            assert!(warnings(&read).is_empty(), "a live read gives nothing away");
a11497af08aaatagen2026-10-13 12:04 UTC207
            let body = json(read).await;
a11497af08aaatagen2026-10-13 12:04 UTC208
            assert!(body["posts"].is_array(), "a read always shows the thread");
a11497af08aaatagen2026-10-13 12:04 UTC209
            if body["detail"].is_string() {
a11497af08aaatagen2026-10-13 12:04 UTC210
                taught = true;
a11497af08aaatagen2026-10-13 12:04 UTC211
                break;
8b8e83329a1datagen2026-10-12 12:01 UTC212
            }
8b8e83329a1datagen2026-10-12 12:01 UTC213
        }
8b8e83329a1datagen2026-10-12 12:01 UTC214
        let state = send(&app, "GET", "/runs/cafebabe", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC215
        assert_eq!(header(&state, "x-stamina"), "10/10", "reading is free");
8b8e83329a1datagen2026-10-12 12:01 UTC216
        if taught {
8b8e83329a1datagen2026-10-12 12:01 UTC217
            taught_runs += 1;
8b8e83329a1datagen2026-10-12 12:01 UTC218
            let jelq = send(&app, "JELQ", "/runs/cafebabe?technique=uli", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC219
            assert_ne!(jelq.status(), StatusCode::FORBIDDEN);
8b8e83329a1datagen2026-10-12 12:01 UTC220
        } else {
a11497af08aaatagen2026-10-13 12:04 UTC221
            let cached = send(&app, "GET", "/runs/cafebabe/thread", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC222
            assert_eq!(cached.status(), StatusCode::OK);
8b8e83329a1datagen2026-10-12 12:01 UTC223
            assert!(header(&cached, "warning").starts_with("112"));
8b8e83329a1datagen2026-10-12 12:01 UTC224
            assert!(header(&cached, "age").parse::<u64>().unwrap() > 0);
8b8e83329a1datagen2026-10-12 12:01 UTC225
        }
8b8e83329a1datagen2026-10-12 12:01 UTC226
    }
8b8e83329a1datagen2026-10-12 12:01 UTC227
    assert!(
8b8e83329a1datagen2026-10-12 12:01 UTC228
        (30..=58).contains(&taught_runs),
8b8e83329a1datagen2026-10-12 12:01 UTC229
        "{taught_runs}/100 learned uli in two reads"
8b8e83329a1datagen2026-10-12 12:01 UTC230
    );
8b8e83329a1datagen2026-10-12 12:01 UTC231
}
8b8e83329a1datagen2026-10-12 12:01 UTC232
8b8e83329a1datagen2026-10-12 12:01 UTC233
#[tokio::test]
56d34031c321atagen2026-10-14 09:23 UTC234
async fn the_forum_may_print_you_a_ruler() {
56d34031c321atagen2026-10-14 09:23 UTC235
    let mut printed = 0;
56d34031c321atagen2026-10-14 09:23 UTC236
    for seed in 0..100u64 {
56d34031c321atagen2026-10-14 09:23 UTC237
        let app = app_with(Floor::Forum, seed, |run| {
56d34031c321atagen2026-10-14 09:23 UTC238
            run.online.story = Story::ReadForum;
56d34031c321atagen2026-10-14 09:23 UTC239
            run.online.meditation_taught = true;
56d34031c321atagen2026-10-14 09:23 UTC240
            run.online.uli_taught = true;
56d34031c321atagen2026-10-14 09:23 UTC241
        });
56d34031c321atagen2026-10-14 09:23 UTC242
        for _ in 0..2 {
56d34031c321atagen2026-10-14 09:23 UTC243
            let read = send(&app, "GET", "/runs/cafebabe/thread", &[], "").await;
56d34031c321atagen2026-10-14 09:23 UTC244
            if read
56d34031c321atagen2026-10-14 09:23 UTC245
                .headers()
56d34031c321atagen2026-10-14 09:23 UTC246
                .get("location")
56d34031c321atagen2026-10-14 09:23 UTC247
                .is_some_and(|l| l.to_str().unwrap().ends_with("/inventory/ruler"))
56d34031c321atagen2026-10-14 09:23 UTC248
            {
56d34031c321atagen2026-10-14 09:23 UTC249
                printed += 1;
56d34031c321atagen2026-10-14 09:23 UTC250
                let state = send(&app, "GET", "/runs/cafebabe", &[], "").await;
56d34031c321atagen2026-10-14 09:23 UTC251
                assert!(!header(&state, "x-length").is_empty());
56d34031c321atagen2026-10-14 09:23 UTC252
                break;
56d34031c321atagen2026-10-14 09:23 UTC253
            }
56d34031c321atagen2026-10-14 09:23 UTC254
        }
56d34031c321atagen2026-10-14 09:23 UTC255
    }
56d34031c321atagen2026-10-14 09:23 UTC256
    assert!(
56d34031c321atagen2026-10-14 09:23 UTC257
        (40..=72).contains(&printed),
56d34031c321atagen2026-10-14 09:23 UTC258
        "{printed}/100 printed a ruler in two reads"
56d34031c321atagen2026-10-14 09:23 UTC259
    );
56d34031c321atagen2026-10-14 09:23 UTC260
56d34031c321atagen2026-10-14 09:23 UTC261
    let measured = seeded_with(Floor::Forum, |run| {
56d34031c321atagen2026-10-14 09:23 UTC262
        run.online.story = Story::ReadForum;
56d34031c321atagen2026-10-14 09:23 UTC263
        run.online.uli_taught = true;
56d34031c321atagen2026-10-14 09:23 UTC264
        run.items.kit.ruler = true;
56d34031c321atagen2026-10-14 09:23 UTC265
    });
56d34031c321atagen2026-10-14 09:23 UTC266
    for _ in 0..2 {
56d34031c321atagen2026-10-14 09:23 UTC267
        let read = send(&measured, "GET", "/runs/cafebabe/thread", &[], "").await;
56d34031c321atagen2026-10-14 09:23 UTC268
        assert!(read.headers().get("location").is_none());
56d34031c321atagen2026-10-14 09:23 UTC269
    }
56d34031c321atagen2026-10-14 09:23 UTC270
}
56d34031c321atagen2026-10-14 09:23 UTC271
56d34031c321atagen2026-10-14 09:23 UTC272
#[tokio::test]
a11497af08aaatagen2026-10-13 12:04 UTC273
async fn the_thread_cannot_be_read_before_anyone_mentions_it() {
bf5f8cb6f60fatagen2026-10-13 12:10 UTC274
    let app = seeded_with(Floor::Bedroom, |_| {});
a11497af08aaatagen2026-10-13 12:04 UTC275
    let read = send(&app, "GET", "/runs/cafebabe/thread", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC276
    assert_eq!(read.status(), StatusCode::NOT_FOUND);
8b8e83329a1datagen2026-10-12 12:01 UTC277
    let after = send(&app, "GET", "/runs/cafebabe", &[], "").await;
a11497af08aaatagen2026-10-13 12:04 UTC278
    assert!(!header(&after, "link").contains("/thread"));
8b8e83329a1datagen2026-10-12 12:01 UTC279
}
8b8e83329a1datagen2026-10-12 12:01 UTC280
8b8e83329a1datagen2026-10-12 12:01 UTC281
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC282
async fn rummaging_for_the_key_leaves_the_drawer_unsearched() {
8b8e83329a1datagen2026-10-12 12:01 UTC283
    let mut found = 0;
8b8e83329a1datagen2026-10-12 12:01 UTC284
    for seed in 0..20u64 {
8b8e83329a1datagen2026-10-12 12:01 UTC285
        let app = app_with(Floor::Hotel, seed, |run| {
8b8e83329a1datagen2026-10-12 12:01 UTC286
            run.here.wifi_accepted = true;
8b8e83329a1datagen2026-10-12 12:01 UTC287
            run.items.kit.cage = true;
8b8e83329a1datagen2026-10-12 12:01 UTC288
        });
8b8e83329a1datagen2026-10-12 12:01 UTC289
        for _ in 0..30 {
8b8e83329a1datagen2026-10-12 12:01 UTC290
            let rummage = send(&app, "POST", "/runs/cafebabe/drawer", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC291
            if rummage.status() == StatusCode::OK {
8b8e83329a1datagen2026-10-12 12:01 UTC292
                break;
8b8e83329a1datagen2026-10-12 12:01 UTC293
            }
8b8e83329a1datagen2026-10-12 12:01 UTC294
        }
8b8e83329a1datagen2026-10-12 12:01 UTC295
        let search = send(&app, "POST", "/runs/cafebabe/drawer", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC296
        if search.status() == StatusCode::FOUND {
8b8e83329a1datagen2026-10-12 12:01 UTC297
            assert!(header(&search, "location").ends_with("/inventory/do-not-disturb-sign"));
8b8e83329a1datagen2026-10-12 12:01 UTC298
            found += 1;
8b8e83329a1datagen2026-10-12 12:01 UTC299
        }
8b8e83329a1datagen2026-10-12 12:01 UTC300
    }
8b8e83329a1datagen2026-10-12 12:01 UTC301
    assert!(
8b8e83329a1datagen2026-10-12 12:01 UTC302
        found >= 18,
8b8e83329a1datagen2026-10-12 12:01 UTC303
        "only {found} of 20 hotel drawers survived the rummage"
8b8e83329a1datagen2026-10-12 12:01 UTC304
    );
8b8e83329a1datagen2026-10-12 12:01 UTC305
}
8b8e83329a1datagen2026-10-12 12:01 UTC306
8b8e83329a1datagen2026-10-12 12:01 UTC307
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC308
async fn kev_is_the_bar_in_the_changing_room() {
8b8e83329a1datagen2026-10-12 12:01 UTC309
    for (length, shamed) in [(5.0, true), (7.0, false)] {
8b8e83329a1datagen2026-10-12 12:01 UTC310
        let app = seeded_with(Floor::Bathroom, |run| {
8b8e83329a1datagen2026-10-12 12:01 UTC311
            run.body.length = length;
8b8e83329a1datagen2026-10-12 12:01 UTC312
            run.here.turns = 6;
8b8e83329a1datagen2026-10-12 12:01 UTC313
        });
8b8e83329a1datagen2026-10-12 12:01 UTC314
        let left = send(&app, "POST", "/runs/cafebabe/floor", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC315
        assert_eq!(header(&left, "x-floor"), "3");
8b8e83329a1datagen2026-10-12 12:01 UTC316
        let warning = left
8b8e83329a1datagen2026-10-12 12:01 UTC317
            .headers()
8b8e83329a1datagen2026-10-12 12:01 UTC318
            .get("warning")
8b8e83329a1datagen2026-10-12 12:01 UTC319
            .map(|w| w.to_str().unwrap().to_owned());
8b8e83329a1datagen2026-10-12 12:01 UTC320
        assert_eq!(
8b8e83329a1datagen2026-10-12 12:01 UTC321
            warning.is_some_and(|w| w.contains("Kev")),
8b8e83329a1datagen2026-10-12 12:01 UTC322
            shamed,
8b8e83329a1datagen2026-10-12 12:01 UTC323
            "length {length}"
8b8e83329a1datagen2026-10-12 12:01 UTC324
        );
8b8e83329a1datagen2026-10-12 12:01 UTC325
    }
8b8e83329a1datagen2026-10-12 12:01 UTC326
}
8b8e83329a1datagen2026-10-12 12:01 UTC327
8b8e83329a1datagen2026-10-12 12:01 UTC328
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC329
async fn pump_bro_charges_the_thin_more_for_girth() {
8b8e83329a1datagen2026-10-12 12:01 UTC330
    for (girth, ring, oil) in [(4.5, 11, 4), (5.2, 8, 4)] {
8b8e83329a1datagen2026-10-12 12:01 UTC331
        let app = seeded_with(Floor::ChangingRoom, |run| run.body.girth = girth);
8b8e83329a1datagen2026-10-12 12:01 UTC332
        let shop = json(send(&app, "GET", "/runs/cafebabe/shop", &[], "").await).await;
8b8e83329a1datagen2026-10-12 12:01 UTC333
        let price = |slug: &str| {
8b8e83329a1datagen2026-10-12 12:01 UTC334
            shop["stock"]
8b8e83329a1datagen2026-10-12 12:01 UTC335
                .as_array()
8b8e83329a1datagen2026-10-12 12:01 UTC336
                .unwrap()
8b8e83329a1datagen2026-10-12 12:01 UTC337
                .iter()
8b8e83329a1datagen2026-10-12 12:01 UTC338
                .find(|item| item["item"] == slug)
8b8e83329a1datagen2026-10-12 12:01 UTC339
                .unwrap()["price"]
8b8e83329a1datagen2026-10-12 12:01 UTC340
                .as_u64()
8b8e83329a1datagen2026-10-12 12:01 UTC341
                .unwrap()
8b8e83329a1datagen2026-10-12 12:01 UTC342
        };
8b8e83329a1datagen2026-10-12 12:01 UTC343
        assert_eq!(price("cock-ring"), ring, "girth {girth}");
8b8e83329a1datagen2026-10-12 12:01 UTC344
        assert_eq!(price("baby-oil"), oil, "girth {girth}");
8b8e83329a1datagen2026-10-12 12:01 UTC345
    }
8b8e83329a1datagen2026-10-12 12:01 UTC346
}
8b8e83329a1datagen2026-10-12 12:01 UTC347
8b8e83329a1datagen2026-10-12 12:01 UTC348
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC349
async fn the_clinic_escalates_toward_honesty() {
8b8e83329a1datagen2026-10-12 12:01 UTC350
    let Harness { app, .. } = harness(Floor::Clinic, SEED, |_| {});
8b8e83329a1datagen2026-10-12 12:01 UTC351
    let mut hints = Vec::new();
8b8e83329a1datagen2026-10-12 12:01 UTC352
    for _ in 0..3 {
8b8e83329a1datagen2026-10-12 12:01 UTC353
        let response = send(
8b8e83329a1datagen2026-10-12 12:01 UTC354
            &app,
8b8e83329a1datagen2026-10-12 12:01 UTC355
            "GET",
8b8e83329a1datagen2026-10-12 12:01 UTC356
            "/runs/cafebabe/diagnosis",
8b8e83329a1datagen2026-10-12 12:01 UTC357
            &[("authorization", "Excuse \"changing\"")],
8b8e83329a1datagen2026-10-12 12:01 UTC358
            "",
8b8e83329a1datagen2026-10-12 12:01 UTC359
        )
8b8e83329a1datagen2026-10-12 12:01 UTC360
        .await;
8b8e83329a1datagen2026-10-12 12:01 UTC361
        assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
8b8e83329a1datagen2026-10-12 12:01 UTC362
        hints.push(header(&response, "www-authenticate").to_owned());
8b8e83329a1datagen2026-10-12 12:01 UTC363
    }
8b8e83329a1datagen2026-10-12 12:01 UTC364
    assert!(hints[2].contains("Be honest."));
8b8e83329a1datagen2026-10-12 12:01 UTC365
}
8b8e83329a1datagen2026-10-12 12:01 UTC366
8b8e83329a1datagen2026-10-12 12:01 UTC367
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC368
async fn the_true_ending_is_not_modified() {
8b8e83329a1datagen2026-10-12 12:01 UTC369
    let Harness { app: early, .. } = harness(Floor::Bedroom, SEED, |_| {});
8b8e83329a1datagen2026-10-12 12:01 UTC370
    let response = send(&early, "GET", "/runs/cafebabe/diagnosis", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC371
    assert_eq!(response.status(), StatusCode::TOO_EARLY);
8b8e83329a1datagen2026-10-12 12:01 UTC372
8b8e83329a1datagen2026-10-12 12:01 UTC373
    let Harness {
8b8e83329a1datagen2026-10-12 12:01 UTC374
        app, origin: etag, ..
8b8e83329a1datagen2026-10-12 12:01 UTC375
    } = harness(Floor::Clinic, SEED, |_| {});
8b8e83329a1datagen2026-10-12 12:01 UTC376
    assert!(
8b8e83329a1datagen2026-10-12 12:01 UTC377
        !etag.contains("cafebabe"),
8b8e83329a1datagen2026-10-12 12:01 UTC378
        "the origin ETag must not be derivable from the id"
8b8e83329a1datagen2026-10-12 12:01 UTC379
    );
8b8e83329a1datagen2026-10-12 12:01 UTC380
    let response = send(
8b8e83329a1datagen2026-10-12 12:01 UTC381
        &app,
8b8e83329a1datagen2026-10-12 12:01 UTC382
        "GET",
8b8e83329a1datagen2026-10-12 12:01 UTC383
        "/runs/cafebabe/diagnosis",
8b8e83329a1datagen2026-10-12 12:01 UTC384
        &[
8b8e83329a1datagen2026-10-12 12:01 UTC385
            ("authorization", "Bearer honesty"),
8b8e83329a1datagen2026-10-12 12:01 UTC386
            ("if-none-match", &etag),
8b8e83329a1datagen2026-10-12 12:01 UTC387
        ],
8b8e83329a1datagen2026-10-12 12:01 UTC388
        "",
8b8e83329a1datagen2026-10-12 12:01 UTC389
    )
8b8e83329a1datagen2026-10-12 12:01 UTC390
    .await;
8b8e83329a1datagen2026-10-12 12:01 UTC391
    assert_eq!(response.status(), StatusCode::NOT_MODIFIED);
8b8e83329a1datagen2026-10-12 12:01 UTC392
    assert_eq!(header(&response, "x-diagnosis"), "normal");
8b8e83329a1datagen2026-10-12 12:01 UTC393
}
8b8e83329a1datagen2026-10-12 12:01 UTC394
8b8e83329a1datagen2026-10-12 12:01 UTC395
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC396
async fn the_elder_bans_three_lies_unless_the_girth_impresses() {
8b8e83329a1datagen2026-10-12 12:01 UTC397
    let forum = |girth: f64| {
8b8e83329a1datagen2026-10-12 12:01 UTC398
        seeded_with(Floor::Forum, move |run| {
8b8e83329a1datagen2026-10-12 12:01 UTC399
            run.body.length = 6.0;
8b8e83329a1datagen2026-10-12 12:01 UTC400
            run.body.girth = girth;
8b8e83329a1datagen2026-10-12 12:01 UTC401
        })
8b8e83329a1datagen2026-10-12 12:01 UTC402
    };
8b8e83329a1datagen2026-10-12 12:01 UTC403
    let fib = [("proxy-authorization", "Ruler 6.5")];
8b8e83329a1datagen2026-10-12 12:01 UTC404
    let thin = forum(4.5);
8b8e83329a1datagen2026-10-12 12:01 UTC405
    for _ in 0..2 {
8b8e83329a1datagen2026-10-12 12:01 UTC406
        let strike = send(&thin, "GET", "/runs/cafebabe/elder", &fib, "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC407
        assert_eq!(strike.status(), StatusCode::PROXY_AUTHENTICATION_REQUIRED);
8b8e83329a1datagen2026-10-12 12:01 UTC408
    }
8b8e83329a1datagen2026-10-12 12:01 UTC409
    let banned = send(&thin, "GET", "/runs/cafebabe/elder", &fib, "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC410
    assert_eq!(banned.status(), StatusCode::FORBIDDEN);
8b8e83329a1datagen2026-10-12 12:01 UTC411
    assert!(
8b8e83329a1datagen2026-10-12 12:01 UTC412
        json(banned).await["type"]
8b8e83329a1datagen2026-10-12 12:01 UTC413
            .as_str()
8b8e83329a1datagen2026-10-12 12:01 UTC414
            .unwrap()
8b8e83329a1datagen2026-10-12 12:01 UTC415
            .ends_with("banned")
8b8e83329a1datagen2026-10-12 12:01 UTC416
    );
8b8e83329a1datagen2026-10-12 12:01 UTC417
8b8e83329a1datagen2026-10-12 12:01 UTC418
    let thick = forum(5.2);
8b8e83329a1datagen2026-10-12 12:01 UTC419
    for _ in 0..4 {
8b8e83329a1datagen2026-10-12 12:01 UTC420
        let excused = send(&thick, "GET", "/runs/cafebabe/elder", &fib, "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC421
        assert_eq!(excused.status(), StatusCode::PROXY_AUTHENTICATION_REQUIRED);
8b8e83329a1datagen2026-10-12 12:01 UTC422
        assert!(header(&excused, "x-forum-note").contains("Girth is also a dimension"));
8b8e83329a1datagen2026-10-12 12:01 UTC423
    }
8b8e83329a1datagen2026-10-12 12:01 UTC424
}
8b8e83329a1datagen2026-10-12 12:01 UTC425
8b8e83329a1datagen2026-10-12 12:01 UTC426
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC427
async fn the_zip_ends_one_exit_in_two_hundred() {
8b8e83329a1datagen2026-10-12 12:01 UTC428
    let mut zipped = 0;
8b8e83329a1datagen2026-10-12 12:01 UTC429
    for seed in 0..4000u64 {
8b8e83329a1datagen2026-10-12 12:01 UTC430
        let mut run = Run::new("cafebabe".into(), "cafe".into(), seed);
8b8e83329a1datagen2026-10-12 12:01 UTC431
        run.here.turns = 6;
8b8e83329a1datagen2026-10-12 12:01 UTC432
        if run.advance().is_err() && run.ended.is_some() {
8b8e83329a1datagen2026-10-12 12:01 UTC433
            zipped += 1;
8b8e83329a1datagen2026-10-12 12:01 UTC434
        }
8b8e83329a1datagen2026-10-12 12:01 UTC435
    }
8b8e83329a1datagen2026-10-12 12:01 UTC436
    assert!(
8b8e83329a1datagen2026-10-12 12:01 UTC437
        (8..=35).contains(&zipped),
8b8e83329a1datagen2026-10-12 12:01 UTC438
        "{zipped} zips in 4000 exits, about 20 expected"
8b8e83329a1datagen2026-10-12 12:01 UTC439
    );
8b8e83329a1datagen2026-10-12 12:01 UTC440
}
8b8e83329a1datagen2026-10-12 12:01 UTC441
8b8e83329a1datagen2026-10-12 12:01 UTC442
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC443
async fn a_prescription_ends_the_run_and_the_true_ending_still_waits() {
8b8e83329a1datagen2026-10-12 12:01 UTC444
    let Harness { app, origin, .. } = harness(Floor::Clinic, SEED, |_| {});
8b8e83329a1datagen2026-10-12 12:01 UTC445
    let honest = [("authorization", "Bearer honesty")];
8b8e83329a1datagen2026-10-12 12:01 UTC446
    let prescribed = send(&app, "GET", "/runs/cafebabe/diagnosis", &honest, "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC447
    assert_eq!(prescribed.status(), StatusCode::OK);
8b8e83329a1datagen2026-10-12 12:01 UTC448
    assert!(header(&prescribed, "server-timing").contains("volume;"));
8b8e83329a1datagen2026-10-12 12:01 UTC449
    assert!(
8b8e83329a1datagen2026-10-12 12:01 UTC450
        json(prescribed).await["detail"]
8b8e83329a1datagen2026-10-12 12:01 UTC451
            .as_str()
8b8e83329a1datagen2026-10-12 12:01 UTC452
            .unwrap()
8b8e83329a1datagen2026-10-12 12:01 UTC453
            .starts_with("Congratulations")
8b8e83329a1datagen2026-10-12 12:01 UTC454
    );
8b8e83329a1datagen2026-10-12 12:01 UTC455
    let board = json(send(&app, "GET", "/leaderboard?seeded=include", &[], "").await).await;
8b8e83329a1datagen2026-10-12 12:01 UTC456
    assert_eq!(board["entries"][0]["status"], "prescribed");
8b8e83329a1datagen2026-10-12 12:01 UTC457
8b8e83329a1datagen2026-10-12 12:01 UTC458
    let conditional = [
8b8e83329a1datagen2026-10-12 12:01 UTC459
        ("authorization", "Bearer honesty"),
8b8e83329a1datagen2026-10-12 12:01 UTC460
        ("if-none-match", origin.as_str()),
8b8e83329a1datagen2026-10-12 12:01 UTC461
    ];
8b8e83329a1datagen2026-10-12 12:01 UTC462
    let cured = send(&app, "GET", "/runs/cafebabe/diagnosis", &conditional, "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC463
    assert_eq!(cured.status(), StatusCode::NOT_MODIFIED);
8b8e83329a1datagen2026-10-12 12:01 UTC464
    let view = json(send(&app, "GET", "/runs/cafebabe", &[], "").await).await;
8b8e83329a1datagen2026-10-12 12:01 UTC465
    assert_eq!(view["status"], "normal");
8b8e83329a1datagen2026-10-12 12:01 UTC466
}
8b8e83329a1datagen2026-10-12 12:01 UTC467
8b8e83329a1datagen2026-10-12 12:01 UTC468
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC469
async fn the_foreskin_window_has_hard_edges() {
8b8e83329a1datagen2026-10-12 12:01 UTC470
    for (foreskin, refused) in [
8b8e83329a1datagen2026-10-12 12:01 UTC471
        (0.79, Some(StatusCode::LENGTH_REQUIRED)),
8b8e83329a1datagen2026-10-12 12:01 UTC472
        (0.8, None),
8b8e83329a1datagen2026-10-12 12:01 UTC473
        (2.0, None),
8b8e83329a1datagen2026-10-12 12:01 UTC474
        (2.01, Some(StatusCode::PAYLOAD_TOO_LARGE)),
8b8e83329a1datagen2026-10-12 12:01 UTC475
    ] {
8b8e83329a1datagen2026-10-12 12:01 UTC476
        let app = seeded_with(Floor::Hotel, |run| {
8b8e83329a1datagen2026-10-12 12:01 UTC477
            run.here.wifi_accepted = true;
8b8e83329a1datagen2026-10-12 12:01 UTC478
            run.body.eq = 55;
8b8e83329a1datagen2026-10-12 12:01 UTC479
            run.body.foreskin = Some(foreskin);
8b8e83329a1datagen2026-10-12 12:01 UTC480
        });
8b8e83329a1datagen2026-10-12 12:01 UTC481
        let dry = send(&app, "JELQ", "/runs/cafebabe?technique=dry", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC482
        match refused {
8b8e83329a1datagen2026-10-12 12:01 UTC483
            Some(status) => assert_eq!(dry.status(), status, "foreskin {foreskin}"),
8b8e83329a1datagen2026-10-12 12:01 UTC484
            None => assert!(
8b8e83329a1datagen2026-10-12 12:01 UTC485
                ![StatusCode::LENGTH_REQUIRED, StatusCode::PAYLOAD_TOO_LARGE]
8b8e83329a1datagen2026-10-12 12:01 UTC486
                    .contains(&dry.status()),
8b8e83329a1datagen2026-10-12 12:01 UTC487
                "foreskin {foreskin} sits inside the window"
8b8e83329a1datagen2026-10-12 12:01 UTC488
            ),
8b8e83329a1datagen2026-10-12 12:01 UTC489
        }
8b8e83329a1datagen2026-10-12 12:01 UTC490
    }
8b8e83329a1datagen2026-10-12 12:01 UTC491
}
880b29a0c11eatagen2026-10-13 09:27 UTC492
880b29a0c11eatagen2026-10-13 09:27 UTC493
#[tokio::test]
880b29a0c11eatagen2026-10-13 09:27 UTC494
async fn the_forum_teaches_breathing_before_uli() {
880b29a0c11eatagen2026-10-13 09:27 UTC495
    let mut breathing = 0;
880b29a0c11eatagen2026-10-13 09:27 UTC496
    for seed in 0..100u64 {
880b29a0c11eatagen2026-10-13 09:27 UTC497
        let app = app_with(Floor::Bedroom, seed, |run| {
880b29a0c11eatagen2026-10-13 09:27 UTC498
            run.turn = BRANCH_TURN;
880b29a0c11eatagen2026-10-13 09:27 UTC499
            run.online.story = Story::Pending;
880b29a0c11eatagen2026-10-13 09:27 UTC500
        });
880b29a0c11eatagen2026-10-13 09:27 UTC501
        let listed = json(send(&app, "GET", "/runs/cafebabe/techniques", &[], "").await).await;
880b29a0c11eatagen2026-10-13 09:27 UTC502
        let meditate = listed["techniques"]
880b29a0c11eatagen2026-10-13 09:27 UTC503
            .as_array()
880b29a0c11eatagen2026-10-13 09:27 UTC504
            .unwrap()
880b29a0c11eatagen2026-10-13 09:27 UTC505
            .iter()
880b29a0c11eatagen2026-10-13 09:27 UTC506
            .find(|t| t["name"] == "meditate")
880b29a0c11eatagen2026-10-13 09:27 UTC507
            .unwrap();
880b29a0c11eatagen2026-10-13 09:27 UTC508
        assert_eq!(meditate["locked"], true);
880b29a0c11eatagen2026-10-13 09:27 UTC509
        send(&app, "POST", "/runs/cafebabe/branches/forum", &[], "").await;
880b29a0c11eatagen2026-10-13 09:27 UTC510
        for _ in 0..2 {
a11497af08aaatagen2026-10-13 12:04 UTC511
            send(&app, "GET", "/runs/cafebabe/thread", &[], "").await;
880b29a0c11eatagen2026-10-13 09:27 UTC512
        }
880b29a0c11eatagen2026-10-13 09:27 UTC513
        let meditated = send(&app, "JELQ", "/runs/cafebabe?technique=meditate", &[], "").await;
880b29a0c11eatagen2026-10-13 09:27 UTC514
        if meditated.status() == StatusCode::OK {
880b29a0c11eatagen2026-10-13 09:27 UTC515
            breathing += 1;
880b29a0c11eatagen2026-10-13 09:27 UTC516
        } else {
880b29a0c11eatagen2026-10-13 09:27 UTC517
            assert_eq!(meditated.status(), StatusCode::FORBIDDEN);
880b29a0c11eatagen2026-10-13 09:27 UTC518
            let uli = send(&app, "JELQ", "/runs/cafebabe?technique=uli", &[], "").await;
880b29a0c11eatagen2026-10-13 09:27 UTC519
            assert_eq!(
880b29a0c11eatagen2026-10-13 09:27 UTC520
                uli.status(),
880b29a0c11eatagen2026-10-13 09:27 UTC521
                StatusCode::FORBIDDEN,
880b29a0c11eatagen2026-10-13 09:27 UTC522
                "seed {seed} skipped ahead"
880b29a0c11eatagen2026-10-13 09:27 UTC523
            );
880b29a0c11eatagen2026-10-13 09:27 UTC524
        }
880b29a0c11eatagen2026-10-13 09:27 UTC525
    }
880b29a0c11eatagen2026-10-13 09:27 UTC526
    assert!(
880b29a0c11eatagen2026-10-13 09:27 UTC527
        (75..=97).contains(&breathing),
880b29a0c11eatagen2026-10-13 09:27 UTC528
        "{breathing}/100 learned to breathe on the branch floor"
880b29a0c11eatagen2026-10-13 09:27 UTC529
    );
880b29a0c11eatagen2026-10-13 09:27 UTC530
}
72cdd1267600atagen2026-10-14 11:14 UTC531
72cdd1267600atagen2026-10-14 11:14 UTC532
#[tokio::test]
72cdd1267600atagen2026-10-14 11:14 UTC533
async fn a_bought_ruler_owns_up_to_the_self_report() {
72cdd1267600atagen2026-10-14 11:14 UTC534
    let app = seeded_with(Floor::ChangingRoom, |run| {
72cdd1267600atagen2026-10-14 11:14 UTC535
        run.standing.cash = 100;
72cdd1267600atagen2026-10-14 11:14 UTC536
        run.body.inflation = 1.2;
72cdd1267600atagen2026-10-14 11:14 UTC537
    });
72cdd1267600atagen2026-10-14 11:14 UTC538
    let bought = send(&app, "POST", "/runs/cafebabe/shop/ruler", &[], "").await;
72cdd1267600atagen2026-10-14 11:14 UTC539
    assert_eq!(bought.status(), StatusCode::CREATED);
72cdd1267600atagen2026-10-14 11:14 UTC540
    assert!(bought.headers().contains_key("x-length"));
72cdd1267600atagen2026-10-14 11:14 UTC541
    let detail = json(bought).await["detail"].as_str().unwrap().to_owned();
72cdd1267600atagen2026-10-14 11:14 UTC542
    assert!(detail.contains("1.2 inches generous"), "{detail}");
72cdd1267600atagen2026-10-14 11:14 UTC543
}