atagen

jelq

public

an http based roguelike rpg about jelqing

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

Blame: tests/http.rs

a9b016e4b248atagen2026-10-10 08:12 UTC1
use std::sync::Arc;
a9b016e4b248atagen2026-10-10 08:12 UTC2
a9b016e4b248atagen2026-10-10 08:12 UTC3
use axum::{
a9b016e4b248atagen2026-10-10 08:12 UTC4
    Router,
a9b016e4b248atagen2026-10-10 08:12 UTC5
    body::{Body, to_bytes},
a9b016e4b248atagen2026-10-10 08:12 UTC6
    http::{Method, Request, StatusCode, header},
a9b016e4b248atagen2026-10-10 08:12 UTC7
    response::Response,
a9b016e4b248atagen2026-10-10 08:12 UTC8
};
dfa38cf4cd4batagen2026-10-10 09:17 UTC9
use jelqd::{
dfa38cf4cd4batagen2026-10-10 09:17 UTC10
    AppState,
dfa38cf4cd4batagen2026-10-10 09:17 UTC11
    game::{Floor, Run},
dfa38cf4cd4batagen2026-10-10 09:17 UTC12
    router,
dfa38cf4cd4batagen2026-10-10 09:17 UTC13
};
a9b016e4b248atagen2026-10-10 08:12 UTC14
use serde_json::Value;
a9b016e4b248atagen2026-10-10 08:12 UTC15
use tower::ServiceExt;
a9b016e4b248atagen2026-10-10 08:12 UTC16
a9b016e4b248atagen2026-10-10 08:12 UTC17
async fn send(
a9b016e4b248atagen2026-10-10 08:12 UTC18
    app: &Router,
a9b016e4b248atagen2026-10-10 08:12 UTC19
    method: &str,
a9b016e4b248atagen2026-10-10 08:12 UTC20
    uri: &str,
a9b016e4b248atagen2026-10-10 08:12 UTC21
    headers: &[(&str, &str)],
a9b016e4b248atagen2026-10-10 08:12 UTC22
    body: &str,
a9b016e4b248atagen2026-10-10 08:12 UTC23
) -> Response {
a9b016e4b248atagen2026-10-10 08:12 UTC24
    let mut request = Request::builder()
a9b016e4b248atagen2026-10-10 08:12 UTC25
        .method(Method::from_bytes(method.as_bytes()).unwrap())
a9b016e4b248atagen2026-10-10 08:12 UTC26
        .uri(uri);
a9b016e4b248atagen2026-10-10 08:12 UTC27
    for (name, value) in headers {
a9b016e4b248atagen2026-10-10 08:12 UTC28
        request = request.header(*name, *value);
a9b016e4b248atagen2026-10-10 08:12 UTC29
    }
a9b016e4b248atagen2026-10-10 08:12 UTC30
    app.clone()
a9b016e4b248atagen2026-10-10 08:12 UTC31
        .oneshot(request.body(Body::from(body.to_owned())).unwrap())
a9b016e4b248atagen2026-10-10 08:12 UTC32
        .await
a9b016e4b248atagen2026-10-10 08:12 UTC33
        .unwrap()
a9b016e4b248atagen2026-10-10 08:12 UTC34
}
a9b016e4b248atagen2026-10-10 08:12 UTC35
a9b016e4b248atagen2026-10-10 08:12 UTC36
async fn json(response: Response) -> Value {
a9b016e4b248atagen2026-10-10 08:12 UTC37
    serde_json::from_slice(&to_bytes(response.into_body(), usize::MAX).await.unwrap()).unwrap()
a9b016e4b248atagen2026-10-10 08:12 UTC38
}
a9b016e4b248atagen2026-10-10 08:12 UTC39
dfa38cf4cd4batagen2026-10-10 09:17 UTC40
fn seeded(floor: Floor) -> (Router, String) {
a9b016e4b248atagen2026-10-10 08:12 UTC41
    let state = Arc::new(AppState::default());
a9b016e4b248atagen2026-10-10 08:12 UTC42
    let mut run = Run::new("cafebabe".into(), 69, 0);
dfa38cf4cd4batagen2026-10-10 09:17 UTC43
    run.floor = floor;
a9b016e4b248atagen2026-10-10 08:12 UTC44
    let etag = run.origin_etag.clone();
a9b016e4b248atagen2026-10-10 08:12 UTC45
    state.insert(run);
a9b016e4b248atagen2026-10-10 08:12 UTC46
    (router(state), etag)
a9b016e4b248atagen2026-10-10 08:12 UTC47
}
a9b016e4b248atagen2026-10-10 08:12 UTC48
a9b016e4b248atagen2026-10-10 08:12 UTC49
#[tokio::test]
a9b016e4b248atagen2026-10-10 08:12 UTC50
async fn creating_a_run_returns_location_and_etag() {
a9b016e4b248atagen2026-10-10 08:12 UTC51
    let app = jelqd::app();
a9b016e4b248atagen2026-10-10 08:12 UTC52
    let response = send(&app, "POST", "/runs?seed=1", &[], "").await;
a9b016e4b248atagen2026-10-10 08:12 UTC53
    assert_eq!(response.status(), StatusCode::CREATED);
a9b016e4b248atagen2026-10-10 08:12 UTC54
    assert!(response.headers().contains_key(header::LOCATION));
a9b016e4b248atagen2026-10-10 08:12 UTC55
    assert!(response.headers().contains_key(header::ETAG));
a9b016e4b248atagen2026-10-10 08:12 UTC56
    assert_eq!(response.headers()[header::SERVER], "jelqd/0.6.9");
a9b016e4b248atagen2026-10-10 08:12 UTC57
}
a9b016e4b248atagen2026-10-10 08:12 UTC58
a9b016e4b248atagen2026-10-10 08:12 UTC59
#[tokio::test]
a9b016e4b248atagen2026-10-10 08:12 UTC60
async fn jelqing_cold_fails_the_precondition() {
dfa38cf4cd4batagen2026-10-10 09:17 UTC61
    let (app, _) = seeded(Floor::Bedroom);
a9b016e4b248atagen2026-10-10 08:12 UTC62
    let response = send(&app, "JELQ", "/runs/cafebabe?technique=stretch", &[], "").await;
a9b016e4b248atagen2026-10-10 08:12 UTC63
    assert_eq!(response.status(), StatusCode::PRECONDITION_FAILED);
a9b016e4b248atagen2026-10-10 08:12 UTC64
    assert_eq!(
a9b016e4b248atagen2026-10-10 08:12 UTC65
        response.headers()[header::CONTENT_TYPE],
a9b016e4b248atagen2026-10-10 08:12 UTC66
        "application/problem+json"
a9b016e4b248atagen2026-10-10 08:12 UTC67
    );
a9b016e4b248atagen2026-10-10 08:12 UTC68
}
a9b016e4b248atagen2026-10-10 08:12 UTC69
a9b016e4b248atagen2026-10-10 08:12 UTC70
#[tokio::test]
a9b016e4b248atagen2026-10-10 08:12 UTC71
async fn wet_without_lube_is_unsupported_media() {
dfa38cf4cd4batagen2026-10-10 09:17 UTC72
    let (app, _) = seeded(Floor::Bedroom);
a9b016e4b248atagen2026-10-10 08:12 UTC73
    send(&app, "JELQ", "/runs/cafebabe?technique=warmup", &[], "").await;
a9b016e4b248atagen2026-10-10 08:12 UTC74
    let response = send(&app, "JELQ", "/runs/cafebabe?technique=wet", &[], "").await;
a9b016e4b248atagen2026-10-10 08:12 UTC75
    assert_eq!(response.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE);
a9b016e4b248atagen2026-10-10 08:12 UTC76
    let images = send(
a9b016e4b248atagen2026-10-10 08:12 UTC77
        &app,
a9b016e4b248atagen2026-10-10 08:12 UTC78
        "JELQ",
a9b016e4b248atagen2026-10-10 08:12 UTC79
        "/runs/cafebabe?technique=wet",
a9b016e4b248atagen2026-10-10 08:12 UTC80
        &[("content-type", "image/jpeg")],
a9b016e4b248atagen2026-10-10 08:12 UTC81
        "",
a9b016e4b248atagen2026-10-10 08:12 UTC82
    )
a9b016e4b248atagen2026-10-10 08:12 UTC83
    .await;
a9b016e4b248atagen2026-10-10 08:12 UTC84
    assert_eq!(json(images).await["detail"], "No.");
a9b016e4b248atagen2026-10-10 08:12 UTC85
}
a9b016e4b248atagen2026-10-10 08:12 UTC86
a9b016e4b248atagen2026-10-10 08:12 UTC87
#[tokio::test]
a9b016e4b248atagen2026-10-10 08:12 UTC88
async fn unknown_methods_are_not_implemented() {
dfa38cf4cd4batagen2026-10-10 09:17 UTC89
    let (app, _) = seeded(Floor::Bedroom);
a9b016e4b248atagen2026-10-10 08:12 UTC90
    let response = send(&app, "MILK", "/runs/cafebabe", &[], "").await;
a9b016e4b248atagen2026-10-10 08:12 UTC91
    assert_eq!(response.status(), StatusCode::NOT_IMPLEMENTED);
a9b016e4b248atagen2026-10-10 08:12 UTC92
}
a9b016e4b248atagen2026-10-10 08:12 UTC93
a9b016e4b248atagen2026-10-10 08:12 UTC94
#[tokio::test]
a9b016e4b248atagen2026-10-10 08:12 UTC95
async fn patch_only_accepts_the_truth() {
dfa38cf4cd4batagen2026-10-10 09:17 UTC96
    let (app, _) = seeded(Floor::Bedroom);
a9b016e4b248atagen2026-10-10 08:12 UTC97
    let patch = [("content-type", "application/json-patch+json")];
a9b016e4b248atagen2026-10-10 08:12 UTC98
    let replace = send(
a9b016e4b248atagen2026-10-10 08:12 UTC99
        &app,
a9b016e4b248atagen2026-10-10 08:12 UTC100
        "PATCH",
a9b016e4b248atagen2026-10-10 08:12 UTC101
        "/runs/cafebabe",
a9b016e4b248atagen2026-10-10 08:12 UTC102
        &patch,
a9b016e4b248atagen2026-10-10 08:12 UTC103
        r#"[{"op":"replace","path":"/length","value":12}]"#,
a9b016e4b248atagen2026-10-10 08:12 UTC104
    )
a9b016e4b248atagen2026-10-10 08:12 UTC105
    .await;
a9b016e4b248atagen2026-10-10 08:12 UTC106
    assert_eq!(replace.status(), StatusCode::UNPROCESSABLE_ENTITY);
a9b016e4b248atagen2026-10-10 08:12 UTC107
    let lie = send(
a9b016e4b248atagen2026-10-10 08:12 UTC108
        &app,
a9b016e4b248atagen2026-10-10 08:12 UTC109
        "PATCH",
a9b016e4b248atagen2026-10-10 08:12 UTC110
        "/runs/cafebabe",
a9b016e4b248atagen2026-10-10 08:12 UTC111
        &patch,
a9b016e4b248atagen2026-10-10 08:12 UTC112
        r#"[{"op":"test","path":"/length","value":40}]"#,
a9b016e4b248atagen2026-10-10 08:12 UTC113
    )
a9b016e4b248atagen2026-10-10 08:12 UTC114
    .await;
a9b016e4b248atagen2026-10-10 08:12 UTC115
    assert_eq!(
a9b016e4b248atagen2026-10-10 08:12 UTC116
        json(lie).await["type"],
a9b016e4b248atagen2026-10-10 08:12 UTC117
        "https://jelq.dev/problems/exaggeration"
a9b016e4b248atagen2026-10-10 08:12 UTC118
    );
a9b016e4b248atagen2026-10-10 08:12 UTC119
}
a9b016e4b248atagen2026-10-10 08:12 UTC120
a9b016e4b248atagen2026-10-10 08:12 UTC121
#[tokio::test]
a9b016e4b248atagen2026-10-10 08:12 UTC122
async fn deleting_the_foreskin_requires_certainty() {
dfa38cf4cd4batagen2026-10-10 09:17 UTC123
    let (app, _) = seeded(Floor::Bedroom);
a9b016e4b248atagen2026-10-10 08:12 UTC124
    let response = send(&app, "DELETE", "/runs/cafebabe/foreskin", &[], "").await;
a9b016e4b248atagen2026-10-10 08:12 UTC125
    assert_eq!(response.status(), StatusCode::PRECONDITION_REQUIRED);
a9b016e4b248atagen2026-10-10 08:12 UTC126
    let etag = send(&app, "GET", "/runs/cafebabe/foreskin", &[], "")
a9b016e4b248atagen2026-10-10 08:12 UTC127
        .await
a9b016e4b248atagen2026-10-10 08:12 UTC128
        .headers()[header::ETAG]
a9b016e4b248atagen2026-10-10 08:12 UTC129
        .to_str()
a9b016e4b248atagen2026-10-10 08:12 UTC130
        .unwrap()
a9b016e4b248atagen2026-10-10 08:12 UTC131
        .to_owned();
a9b016e4b248atagen2026-10-10 08:12 UTC132
    let response = send(
a9b016e4b248atagen2026-10-10 08:12 UTC133
        &app,
a9b016e4b248atagen2026-10-10 08:12 UTC134
        "DELETE",
a9b016e4b248atagen2026-10-10 08:12 UTC135
        "/runs/cafebabe/foreskin",
a9b016e4b248atagen2026-10-10 08:12 UTC136
        &[("if-match", &etag)],
a9b016e4b248atagen2026-10-10 08:12 UTC137
        "",
a9b016e4b248atagen2026-10-10 08:12 UTC138
    )
a9b016e4b248atagen2026-10-10 08:12 UTC139
    .await;
a9b016e4b248atagen2026-10-10 08:12 UTC140
    assert_eq!(response.status(), StatusCode::NO_CONTENT);
a9b016e4b248atagen2026-10-10 08:12 UTC141
    let response = send(&app, "GET", "/runs/cafebabe/foreskin", &[], "").await;
a9b016e4b248atagen2026-10-10 08:12 UTC142
    assert_eq!(response.status(), StatusCode::GONE);
a9b016e4b248atagen2026-10-10 08:12 UTC143
}
a9b016e4b248atagen2026-10-10 08:12 UTC144
a9b016e4b248atagen2026-10-10 08:12 UTC145
#[tokio::test]
a9b016e4b248atagen2026-10-10 08:12 UTC146
async fn length_is_chunked_until_the_ruler() {
dfa38cf4cd4batagen2026-10-10 09:17 UTC147
    let (app, _) = seeded(Floor::Bedroom);
a9b016e4b248atagen2026-10-10 08:12 UTC148
    let lie = send(&app, "GET", "/runs/cafebabe/length", &[], "").await;
a9b016e4b248atagen2026-10-10 08:12 UTC149
    assert!(!lie.headers().contains_key(header::CONTENT_LENGTH));
a9b016e4b248atagen2026-10-10 08:12 UTC150
    let found = send(&app, "POST", "/runs/cafebabe/drawer", &[], "").await;
a9b016e4b248atagen2026-10-10 08:12 UTC151
    assert_eq!(found.status(), StatusCode::FOUND);
a9b016e4b248atagen2026-10-10 08:12 UTC152
    let truth = send(&app, "GET", "/runs/cafebabe/length", &[], "").await;
a9b016e4b248atagen2026-10-10 08:12 UTC153
    let declared: usize = truth.headers()[header::CONTENT_LENGTH]
a9b016e4b248atagen2026-10-10 08:12 UTC154
        .to_str()
a9b016e4b248atagen2026-10-10 08:12 UTC155
        .unwrap()
a9b016e4b248atagen2026-10-10 08:12 UTC156
        .parse()
a9b016e4b248atagen2026-10-10 08:12 UTC157
        .unwrap();
a9b016e4b248atagen2026-10-10 08:12 UTC158
    let body = to_bytes(truth.into_body(), usize::MAX).await.unwrap();
a9b016e4b248atagen2026-10-10 08:12 UTC159
    assert_eq!(body.len(), declared);
a9b016e4b248atagen2026-10-10 08:12 UTC160
    assert!(body.starts_with(b"8=") && body.ends_with(b"D"));
a9b016e4b248atagen2026-10-10 08:12 UTC161
}
a9b016e4b248atagen2026-10-10 08:12 UTC162
a9b016e4b248atagen2026-10-10 08:12 UTC163
#[tokio::test]
a9b016e4b248atagen2026-10-10 08:12 UTC164
async fn the_clinic_escalates_toward_honesty() {
dfa38cf4cd4batagen2026-10-10 09:17 UTC165
    let (app, _) = seeded(Floor::Clinic);
a9b016e4b248atagen2026-10-10 08:12 UTC166
    let mut hints = Vec::new();
a9b016e4b248atagen2026-10-10 08:12 UTC167
    for _ in 0..3 {
a9b016e4b248atagen2026-10-10 08:12 UTC168
        let response = send(
a9b016e4b248atagen2026-10-10 08:12 UTC169
            &app,
a9b016e4b248atagen2026-10-10 08:12 UTC170
            "GET",
a9b016e4b248atagen2026-10-10 08:12 UTC171
            "/runs/cafebabe/diagnosis",
a9b016e4b248atagen2026-10-10 08:12 UTC172
            &[("authorization", "Excuse \"changing\"")],
a9b016e4b248atagen2026-10-10 08:12 UTC173
            "",
a9b016e4b248atagen2026-10-10 08:12 UTC174
        )
a9b016e4b248atagen2026-10-10 08:12 UTC175
        .await;
a9b016e4b248atagen2026-10-10 08:12 UTC176
        assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
a9b016e4b248atagen2026-10-10 08:12 UTC177
        hints.push(
a9b016e4b248atagen2026-10-10 08:12 UTC178
            response.headers()[header::WWW_AUTHENTICATE]
a9b016e4b248atagen2026-10-10 08:12 UTC179
                .to_str()
a9b016e4b248atagen2026-10-10 08:12 UTC180
                .unwrap()
a9b016e4b248atagen2026-10-10 08:12 UTC181
                .to_owned(),
a9b016e4b248atagen2026-10-10 08:12 UTC182
        );
a9b016e4b248atagen2026-10-10 08:12 UTC183
    }
a9b016e4b248atagen2026-10-10 08:12 UTC184
    assert!(hints[2].contains("Be honest."));
a9b016e4b248atagen2026-10-10 08:12 UTC185
}
a9b016e4b248atagen2026-10-10 08:12 UTC186
a9b016e4b248atagen2026-10-10 08:12 UTC187
#[tokio::test]
a9b016e4b248atagen2026-10-10 08:12 UTC188
async fn the_true_ending_is_not_modified() {
dfa38cf4cd4batagen2026-10-10 09:17 UTC189
    let (app, etag) = seeded(Floor::Clinic);
dfa38cf4cd4batagen2026-10-10 09:17 UTC190
    let early = seeded(Floor::Bedroom).0;
a9b016e4b248atagen2026-10-10 08:12 UTC191
    let response = send(&early, "GET", "/runs/cafebabe/diagnosis", &[], "").await;
a9b016e4b248atagen2026-10-10 08:12 UTC192
    assert_eq!(response.status(), StatusCode::TOO_EARLY);
a9b016e4b248atagen2026-10-10 08:12 UTC193
a9b016e4b248atagen2026-10-10 08:12 UTC194
    let response = send(
a9b016e4b248atagen2026-10-10 08:12 UTC195
        &app,
a9b016e4b248atagen2026-10-10 08:12 UTC196
        "GET",
a9b016e4b248atagen2026-10-10 08:12 UTC197
        "/runs/cafebabe/diagnosis",
a9b016e4b248atagen2026-10-10 08:12 UTC198
        &[
a9b016e4b248atagen2026-10-10 08:12 UTC199
            ("authorization", "Bearer honesty"),
a9b016e4b248atagen2026-10-10 08:12 UTC200
            ("if-none-match", &etag),
a9b016e4b248atagen2026-10-10 08:12 UTC201
        ],
a9b016e4b248atagen2026-10-10 08:12 UTC202
        "",
a9b016e4b248atagen2026-10-10 08:12 UTC203
    )
a9b016e4b248atagen2026-10-10 08:12 UTC204
    .await;
a9b016e4b248atagen2026-10-10 08:12 UTC205
    assert_eq!(response.status(), StatusCode::NOT_MODIFIED);
a9b016e4b248atagen2026-10-10 08:12 UTC206
    assert_eq!(response.headers()["x-diagnosis"], "normal");
a9b016e4b248atagen2026-10-10 08:12 UTC207
}
a9b016e4b248atagen2026-10-10 08:12 UTC208
a9b016e4b248atagen2026-10-10 08:12 UTC209
#[tokio::test]
a9b016e4b248atagen2026-10-10 08:12 UTC210
async fn mom_accepts_a_known_excuse() {
a9b016e4b248atagen2026-10-10 08:12 UTC211
    let state = Arc::new(AppState::default());
a9b016e4b248atagen2026-10-10 08:12 UTC212
    let mut run = Run::new("cafebabe".into(), 69, 0);
a9b016e4b248atagen2026-10-10 08:12 UTC213
    run.mom_present = true;
a9b016e4b248atagen2026-10-10 08:12 UTC214
    state.insert(run);
a9b016e4b248atagen2026-10-10 08:12 UTC215
    let app = router(state);
a9b016e4b248atagen2026-10-10 08:12 UTC216
    let response = send(&app, "GET", "/runs/cafebabe", &[], "").await;
a9b016e4b248atagen2026-10-10 08:12 UTC217
    assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
a9b016e4b248atagen2026-10-10 08:12 UTC218
    assert_eq!(response.headers()["via"], "1.1 mom");
a9b016e4b248atagen2026-10-10 08:12 UTC219
    let response = send(
a9b016e4b248atagen2026-10-10 08:12 UTC220
        &app,
a9b016e4b248atagen2026-10-10 08:12 UTC221
        "GET",
a9b016e4b248atagen2026-10-10 08:12 UTC222
        "/runs/cafebabe",
a9b016e4b248atagen2026-10-10 08:12 UTC223
        &[("authorization", "Excuse \"homework\"")],
a9b016e4b248atagen2026-10-10 08:12 UTC224
        "",
a9b016e4b248atagen2026-10-10 08:12 UTC225
    )
a9b016e4b248atagen2026-10-10 08:12 UTC226
    .await;
a9b016e4b248atagen2026-10-10 08:12 UTC227
    assert_eq!(response.status(), StatusCode::OK);
a9b016e4b248atagen2026-10-10 08:12 UTC228
}
dfa38cf4cd4batagen2026-10-10 09:17 UTC229
dfa38cf4cd4batagen2026-10-10 09:17 UTC230
fn seeded_with(floor: Floor, setup: impl FnOnce(&mut Run)) -> Router {
dfa38cf4cd4batagen2026-10-10 09:17 UTC231
    let state = Arc::new(AppState::default());
dfa38cf4cd4batagen2026-10-10 09:17 UTC232
    let mut run = Run::new("cafebabe".into(), 69, 0);
dfa38cf4cd4batagen2026-10-10 09:17 UTC233
    run.floor = floor;
dfa38cf4cd4batagen2026-10-10 09:17 UTC234
    setup(&mut run);
dfa38cf4cd4batagen2026-10-10 09:17 UTC235
    state.insert(run);
dfa38cf4cd4batagen2026-10-10 09:17 UTC236
    router(state)
dfa38cf4cd4batagen2026-10-10 09:17 UTC237
}
dfa38cf4cd4batagen2026-10-10 09:17 UTC238
dfa38cf4cd4batagen2026-10-10 09:17 UTC239
#[tokio::test]
dfa38cf4cd4batagen2026-10-10 09:17 UTC240
async fn floors_end_when_they_end() {
dfa38cf4cd4batagen2026-10-10 09:17 UTC241
    let app = seeded_with(Floor::Bedroom, |_| {});
dfa38cf4cd4batagen2026-10-10 09:17 UTC242
    let early = send(&app, "POST", "/runs/cafebabe/floor", &[], "").await;
dfa38cf4cd4batagen2026-10-10 09:17 UTC243
    assert_eq!(early.status(), StatusCode::TOO_EARLY);
dfa38cf4cd4batagen2026-10-10 09:17 UTC244
    let app = seeded_with(Floor::Bedroom, |run| run.floor_turns = 6);
dfa38cf4cd4batagen2026-10-10 09:17 UTC245
    let next = send(&app, "POST", "/runs/cafebabe/floor", &[], "").await;
dfa38cf4cd4batagen2026-10-10 09:17 UTC246
    assert_eq!(next.status(), StatusCode::SEE_OTHER);
dfa38cf4cd4batagen2026-10-10 09:17 UTC247
    assert_eq!(next.headers()["x-floor"], "2");
dfa38cf4cd4batagen2026-10-10 09:17 UTC248
}
dfa38cf4cd4batagen2026-10-10 09:17 UTC249
dfa38cf4cd4batagen2026-10-10 09:17 UTC250
#[tokio::test]
dfa38cf4cd4batagen2026-10-10 09:17 UTC251
async fn pump_bro_upsells() {
dfa38cf4cd4batagen2026-10-10 09:17 UTC252
    let closed = seeded_with(Floor::Bedroom, |_| {});
dfa38cf4cd4batagen2026-10-10 09:17 UTC253
    let response = send(&closed, "GET", "/runs/cafebabe/shop", &[], "").await;
dfa38cf4cd4batagen2026-10-10 09:17 UTC254
    assert_eq!(response.status(), StatusCode::NOT_FOUND);
dfa38cf4cd4batagen2026-10-10 09:17 UTC255
dfa38cf4cd4batagen2026-10-10 09:17 UTC256
    let app = seeded_with(Floor::LockerRoom, |_| {});
dfa38cf4cd4batagen2026-10-10 09:17 UTC257
    let pump = send(&app, "POST", "/runs/cafebabe/shop/pump", &[], "").await;
dfa38cf4cd4batagen2026-10-10 09:17 UTC258
    assert_eq!(pump.status(), StatusCode::UPGRADE_REQUIRED);
dfa38cf4cd4batagen2026-10-10 09:17 UTC259
    assert_eq!(pump.headers()[header::UPGRADE], "pump-pro/2.0");
dfa38cf4cd4batagen2026-10-10 09:17 UTC260
    let pro = send(&app, "POST", "/runs/cafebabe/shop/pump-pro", &[], "").await;
dfa38cf4cd4batagen2026-10-10 09:17 UTC261
    assert_eq!(pro.status(), StatusCode::CREATED);
dfa38cf4cd4batagen2026-10-10 09:17 UTC262
    let broke = send(&app, "POST", "/runs/cafebabe/shop/extender", &[], "").await;
dfa38cf4cd4batagen2026-10-10 09:17 UTC263
    assert_eq!(broke.status(), StatusCode::PAYMENT_REQUIRED);
dfa38cf4cd4batagen2026-10-10 09:17 UTC264
}
dfa38cf4cd4batagen2026-10-10 09:17 UTC265
dfa38cf4cd4batagen2026-10-10 09:17 UTC266
#[tokio::test]
dfa38cf4cd4batagen2026-10-10 09:17 UTC267
async fn combos_are_multi_status_xml() {
dfa38cf4cd4batagen2026-10-10 09:17 UTC268
    let app = seeded_with(Floor::Bedroom, |run| run.foreskin = Some(1.0));
dfa38cf4cd4batagen2026-10-10 09:17 UTC269
    send(&app, "JELQ", "/runs/cafebabe?technique=warmup", &[], "").await;
dfa38cf4cd4batagen2026-10-10 09:17 UTC270
    let response = send(
dfa38cf4cd4batagen2026-10-10 09:17 UTC271
        &app,
dfa38cf4cd4batagen2026-10-10 09:17 UTC272
        "JELQ",
dfa38cf4cd4batagen2026-10-10 09:17 UTC273
        "/runs/cafebabe?technique=wet,stretch",
dfa38cf4cd4batagen2026-10-10 09:17 UTC274
        &[("content-type", "application/baby-oil")],
dfa38cf4cd4batagen2026-10-10 09:17 UTC275
        "",
dfa38cf4cd4batagen2026-10-10 09:17 UTC276
    )
dfa38cf4cd4batagen2026-10-10 09:17 UTC277
    .await;
dfa38cf4cd4batagen2026-10-10 09:17 UTC278
    assert_eq!(response.status(), StatusCode::MULTI_STATUS);
dfa38cf4cd4batagen2026-10-10 09:17 UTC279
    let body = to_bytes(response.into_body(), usize::MAX).await.unwrap();
dfa38cf4cd4batagen2026-10-10 09:17 UTC280
    assert!(String::from_utf8_lossy(&body).contains("<D:multistatus"));
dfa38cf4cd4batagen2026-10-10 09:17 UTC281
}
dfa38cf4cd4batagen2026-10-10 09:17 UTC282
dfa38cf4cd4batagen2026-10-10 09:17 UTC283
#[tokio::test]
dfa38cf4cd4batagen2026-10-10 09:17 UTC284
async fn the_elder_is_stale() {
dfa38cf4cd4batagen2026-10-10 09:17 UTC285
    let app = seeded_with(Floor::Forum, |run| run.length = 5.0);
dfa38cf4cd4batagen2026-10-10 09:17 UTC286
    let unverified = send(&app, "GET", "/runs/cafebabe/elder", &[], "").await;
dfa38cf4cd4batagen2026-10-10 09:17 UTC287
    assert_eq!(
dfa38cf4cd4batagen2026-10-10 09:17 UTC288
        unverified.status(),
dfa38cf4cd4batagen2026-10-10 09:17 UTC289
        StatusCode::PROXY_AUTHENTICATION_REQUIRED
dfa38cf4cd4batagen2026-10-10 09:17 UTC290
    );
dfa38cf4cd4batagen2026-10-10 09:17 UTC291
    let lie = send(
dfa38cf4cd4batagen2026-10-10 09:17 UTC292
        &app,
dfa38cf4cd4batagen2026-10-10 09:17 UTC293
        "GET",
dfa38cf4cd4batagen2026-10-10 09:17 UTC294
        "/runs/cafebabe/elder",
dfa38cf4cd4batagen2026-10-10 09:17 UTC295
        &[("proxy-authorization", "Ruler 9")],
dfa38cf4cd4batagen2026-10-10 09:17 UTC296
        "",
dfa38cf4cd4batagen2026-10-10 09:17 UTC297
    )
dfa38cf4cd4batagen2026-10-10 09:17 UTC298
    .await;
dfa38cf4cd4batagen2026-10-10 09:17 UTC299
    assert_eq!(lie.status(), StatusCode::BAD_REQUEST);
dfa38cf4cd4batagen2026-10-10 09:17 UTC300
    let csv = send(
dfa38cf4cd4batagen2026-10-10 09:17 UTC301
        &app,
dfa38cf4cd4batagen2026-10-10 09:17 UTC302
        "GET",
dfa38cf4cd4batagen2026-10-10 09:17 UTC303
        "/runs/cafebabe/elder",
dfa38cf4cd4batagen2026-10-10 09:17 UTC304
        &[("proxy-authorization", "Ruler 5.0")],
dfa38cf4cd4batagen2026-10-10 09:17 UTC305
        "",
dfa38cf4cd4batagen2026-10-10 09:17 UTC306
    )
dfa38cf4cd4batagen2026-10-10 09:17 UTC307
    .await;
dfa38cf4cd4batagen2026-10-10 09:17 UTC308
    assert_eq!(csv.status(), StatusCode::OK);
dfa38cf4cd4batagen2026-10-10 09:17 UTC309
    let stale = send(
dfa38cf4cd4batagen2026-10-10 09:17 UTC310
        &app,
dfa38cf4cd4batagen2026-10-10 09:17 UTC311
        "GET",
dfa38cf4cd4batagen2026-10-10 09:17 UTC312
        "/runs/cafebabe/elder",
dfa38cf4cd4batagen2026-10-10 09:17 UTC313
        &[("if-modified-since", "Mon, 15 Jun 2009 00:00:00 GMT")],
dfa38cf4cd4batagen2026-10-10 09:17 UTC314
        "",
dfa38cf4cd4batagen2026-10-10 09:17 UTC315
    )
dfa38cf4cd4batagen2026-10-10 09:17 UTC316
    .await;
dfa38cf4cd4batagen2026-10-10 09:17 UTC317
    assert_eq!(stale.status(), StatusCode::NOT_MODIFIED);
dfa38cf4cd4batagen2026-10-10 09:17 UTC318
    let exit = send(&app, "POST", "/runs/cafebabe/floor", &[], "").await;
dfa38cf4cd4batagen2026-10-10 09:17 UTC319
    assert_eq!(exit.status(), StatusCode::SEE_OTHER);
dfa38cf4cd4batagen2026-10-10 09:17 UTC320
}
dfa38cf4cd4batagen2026-10-10 09:17 UTC321
dfa38cf4cd4batagen2026-10-10 09:17 UTC322
#[tokio::test]
dfa38cf4cd4batagen2026-10-10 09:17 UTC323
async fn the_hotel_has_a_captive_portal() {
dfa38cf4cd4batagen2026-10-10 09:17 UTC324
    let app = seeded_with(Floor::Hotel, |_| {});
dfa38cf4cd4batagen2026-10-10 09:17 UTC325
    let blocked = send(&app, "GET", "/runs/cafebabe", &[], "").await;
dfa38cf4cd4batagen2026-10-10 09:17 UTC326
    assert_eq!(
dfa38cf4cd4batagen2026-10-10 09:17 UTC327
        blocked.status(),
dfa38cf4cd4batagen2026-10-10 09:17 UTC328
        StatusCode::NETWORK_AUTHENTICATION_REQUIRED
dfa38cf4cd4batagen2026-10-10 09:17 UTC329
    );
dfa38cf4cd4batagen2026-10-10 09:17 UTC330
    let accepted = send(&app, "POST", "/runs/cafebabe/portal", &[], "").await;
dfa38cf4cd4batagen2026-10-10 09:17 UTC331
    assert_eq!(accepted.status(), StatusCode::SEE_OTHER);
dfa38cf4cd4batagen2026-10-10 09:17 UTC332
    let online = send(&app, "GET", "/runs/cafebabe", &[], "").await;
dfa38cf4cd4batagen2026-10-10 09:17 UTC333
    assert_eq!(online.status(), StatusCode::OK);
dfa38cf4cd4batagen2026-10-10 09:17 UTC334
}
dfa38cf4cd4batagen2026-10-10 09:17 UTC335
dfa38cf4cd4batagen2026-10-10 09:17 UTC336
#[tokio::test]
dfa38cf4cd4batagen2026-10-10 09:17 UTC337
async fn mom_remembers_excuses_by_thanksgiving() {
dfa38cf4cd4batagen2026-10-10 09:17 UTC338
    let app = seeded_with(Floor::Grandmas, |run| {
dfa38cf4cd4batagen2026-10-10 09:17 UTC339
        run.mom_present = true;
dfa38cf4cd4batagen2026-10-10 09:17 UTC340
        run.nonce = "abc".into();
dfa38cf4cd4batagen2026-10-10 09:17 UTC341
        run.heard.push("homework".into());
dfa38cf4cd4batagen2026-10-10 09:17 UTC342
    });
dfa38cf4cd4batagen2026-10-10 09:17 UTC343
    let basic = send(
dfa38cf4cd4batagen2026-10-10 09:17 UTC344
        &app,
dfa38cf4cd4batagen2026-10-10 09:17 UTC345
        "GET",
dfa38cf4cd4batagen2026-10-10 09:17 UTC346
        "/runs/cafebabe",
dfa38cf4cd4batagen2026-10-10 09:17 UTC347
        &[("authorization", "Excuse \"changing\"")],
dfa38cf4cd4batagen2026-10-10 09:17 UTC348
        "",
dfa38cf4cd4batagen2026-10-10 09:17 UTC349
    )
dfa38cf4cd4batagen2026-10-10 09:17 UTC350
    .await;
dfa38cf4cd4batagen2026-10-10 09:17 UTC351
    assert_eq!(basic.status(), StatusCode::UNAUTHORIZED);
dfa38cf4cd4batagen2026-10-10 09:17 UTC352
    assert!(
dfa38cf4cd4batagen2026-10-10 09:17 UTC353
        basic.headers()[header::WWW_AUTHENTICATE]
dfa38cf4cd4batagen2026-10-10 09:17 UTC354
            .to_str()
dfa38cf4cd4batagen2026-10-10 09:17 UTC355
            .unwrap()
dfa38cf4cd4batagen2026-10-10 09:17 UTC356
            .starts_with("Digest")
dfa38cf4cd4batagen2026-10-10 09:17 UTC357
    );
dfa38cf4cd4batagen2026-10-10 09:17 UTC358
    let replay = send(
dfa38cf4cd4batagen2026-10-10 09:17 UTC359
        &app,
dfa38cf4cd4batagen2026-10-10 09:17 UTC360
        "GET",
dfa38cf4cd4batagen2026-10-10 09:17 UTC361
        "/runs/cafebabe",
dfa38cf4cd4batagen2026-10-10 09:17 UTC362
        &[(
dfa38cf4cd4batagen2026-10-10 09:17 UTC363
            "authorization",
dfa38cf4cd4batagen2026-10-10 09:17 UTC364
            "Digest username=\"homework\", nonce=\"abc\"",
dfa38cf4cd4batagen2026-10-10 09:17 UTC365
        )],
dfa38cf4cd4batagen2026-10-10 09:17 UTC366
        "",
dfa38cf4cd4batagen2026-10-10 09:17 UTC367
    )
dfa38cf4cd4batagen2026-10-10 09:17 UTC368
    .await;
dfa38cf4cd4batagen2026-10-10 09:17 UTC369
    assert_eq!(
dfa38cf4cd4batagen2026-10-10 09:17 UTC370
        json(replay).await["type"],
dfa38cf4cd4batagen2026-10-10 09:17 UTC371
        "https://jelq.dev/problems/excuse-replayed"
dfa38cf4cd4batagen2026-10-10 09:17 UTC372
    );
dfa38cf4cd4batagen2026-10-10 09:17 UTC373
    let fresh = send(
dfa38cf4cd4batagen2026-10-10 09:17 UTC374
        &app,
dfa38cf4cd4batagen2026-10-10 09:17 UTC375
        "GET",
dfa38cf4cd4batagen2026-10-10 09:17 UTC376
        "/runs/cafebabe",
dfa38cf4cd4batagen2026-10-10 09:17 UTC377
        &[(
dfa38cf4cd4batagen2026-10-10 09:17 UTC378
            "authorization",
dfa38cf4cd4batagen2026-10-10 09:17 UTC379
            "Digest username=\"praying\", nonce=\"abc\"",
dfa38cf4cd4batagen2026-10-10 09:17 UTC380
        )],
dfa38cf4cd4batagen2026-10-10 09:17 UTC381
        "",
dfa38cf4cd4batagen2026-10-10 09:17 UTC382
    )
dfa38cf4cd4batagen2026-10-10 09:17 UTC383
    .await;
dfa38cf4cd4batagen2026-10-10 09:17 UTC384
    assert_eq!(fresh.status(), StatusCode::OK);
dfa38cf4cd4batagen2026-10-10 09:17 UTC385
}
dfa38cf4cd4batagen2026-10-10 09:17 UTC386
dfa38cf4cd4batagen2026-10-10 09:17 UTC387
#[tokio::test]
dfa38cf4cd4batagen2026-10-10 09:17 UTC388
async fn length_supports_inch_ranges() {
dfa38cf4cd4batagen2026-10-10 09:17 UTC389
    let app = seeded_with(Floor::Bedroom, |_| {});
dfa38cf4cd4batagen2026-10-10 09:17 UTC390
    let response = send(
dfa38cf4cd4batagen2026-10-10 09:17 UTC391
        &app,
dfa38cf4cd4batagen2026-10-10 09:17 UTC392
        "GET",
dfa38cf4cd4batagen2026-10-10 09:17 UTC393
        "/runs/cafebabe/length",
dfa38cf4cd4batagen2026-10-10 09:17 UTC394
        &[("range", "inches=0-1")],
dfa38cf4cd4batagen2026-10-10 09:17 UTC395
        "",
dfa38cf4cd4batagen2026-10-10 09:17 UTC396
    )
dfa38cf4cd4batagen2026-10-10 09:17 UTC397
    .await;
dfa38cf4cd4batagen2026-10-10 09:17 UTC398
    assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
dfa38cf4cd4batagen2026-10-10 09:17 UTC399
    assert!(
dfa38cf4cd4batagen2026-10-10 09:17 UTC400
        response.headers()[header::CONTENT_RANGE]
dfa38cf4cd4batagen2026-10-10 09:17 UTC401
            .to_str()
dfa38cf4cd4batagen2026-10-10 09:17 UTC402
            .unwrap()
dfa38cf4cd4batagen2026-10-10 09:17 UTC403
            .starts_with("inches 0.00-1.00/")
dfa38cf4cd4batagen2026-10-10 09:17 UTC404
    );
dfa38cf4cd4batagen2026-10-10 09:17 UTC405
    let body = to_bytes(response.into_body(), usize::MAX).await.unwrap();
dfa38cf4cd4batagen2026-10-10 09:17 UTC406
    assert_eq!(body.len(), 25);
dfa38cf4cd4batagen2026-10-10 09:17 UTC407
    let absurd = send(
dfa38cf4cd4batagen2026-10-10 09:17 UTC408
        &app,
dfa38cf4cd4batagen2026-10-10 09:17 UTC409
        "GET",
dfa38cf4cd4batagen2026-10-10 09:17 UTC410
        "/runs/cafebabe/length",
dfa38cf4cd4batagen2026-10-10 09:17 UTC411
        &[("range", "inches=40-50")],
dfa38cf4cd4batagen2026-10-10 09:17 UTC412
        "",
dfa38cf4cd4batagen2026-10-10 09:17 UTC413
    )
dfa38cf4cd4batagen2026-10-10 09:17 UTC414
    .await;
dfa38cf4cd4batagen2026-10-10 09:17 UTC415
    assert_eq!(absurd.status(), StatusCode::RANGE_NOT_SATISFIABLE);
dfa38cf4cd4batagen2026-10-10 09:17 UTC416
}
dfa38cf4cd4batagen2026-10-10 09:17 UTC417
dfa38cf4cd4batagen2026-10-10 09:17 UTC418
#[tokio::test]
dfa38cf4cd4batagen2026-10-10 09:17 UTC419
async fn http_1_0_gets_the_pdf_treatment() {
dfa38cf4cd4batagen2026-10-10 09:17 UTC420
    let request = Request::builder()
dfa38cf4cd4batagen2026-10-10 09:17 UTC421
        .uri("/graveyard")
dfa38cf4cd4batagen2026-10-10 09:17 UTC422
        .version(axum::http::Version::HTTP_10)
dfa38cf4cd4batagen2026-10-10 09:17 UTC423
        .body(Body::empty())
dfa38cf4cd4batagen2026-10-10 09:17 UTC424
        .unwrap();
dfa38cf4cd4batagen2026-10-10 09:17 UTC425
    let response = jelqd::app().oneshot(request).await.unwrap();
dfa38cf4cd4batagen2026-10-10 09:17 UTC426
    assert_eq!(response.status(), StatusCode::HTTP_VERSION_NOT_SUPPORTED);
dfa38cf4cd4batagen2026-10-10 09:17 UTC427
}