atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
a9b016e4b248ebfb4326a7881143856a1fe77252
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
};
a9b016e4b248atagen2026-10-10 08:12 UTC9
use jelqd::{AppState, game::Run, router};
a9b016e4b248atagen2026-10-10 08:12 UTC10
use serde_json::Value;
a9b016e4b248atagen2026-10-10 08:12 UTC11
use tower::ServiceExt;
a9b016e4b248atagen2026-10-10 08:12 UTC12
a9b016e4b248atagen2026-10-10 08:12 UTC13
async fn send(
a9b016e4b248atagen2026-10-10 08:12 UTC14
    app: &Router,
a9b016e4b248atagen2026-10-10 08:12 UTC15
    method: &str,
a9b016e4b248atagen2026-10-10 08:12 UTC16
    uri: &str,
a9b016e4b248atagen2026-10-10 08:12 UTC17
    headers: &[(&str, &str)],
a9b016e4b248atagen2026-10-10 08:12 UTC18
    body: &str,
a9b016e4b248atagen2026-10-10 08:12 UTC19
) -> Response {
a9b016e4b248atagen2026-10-10 08:12 UTC20
    let mut request = Request::builder()
a9b016e4b248atagen2026-10-10 08:12 UTC21
        .method(Method::from_bytes(method.as_bytes()).unwrap())
a9b016e4b248atagen2026-10-10 08:12 UTC22
        .uri(uri);
a9b016e4b248atagen2026-10-10 08:12 UTC23
    for (name, value) in headers {
a9b016e4b248atagen2026-10-10 08:12 UTC24
        request = request.header(*name, *value);
a9b016e4b248atagen2026-10-10 08:12 UTC25
    }
a9b016e4b248atagen2026-10-10 08:12 UTC26
    app.clone()
a9b016e4b248atagen2026-10-10 08:12 UTC27
        .oneshot(request.body(Body::from(body.to_owned())).unwrap())
a9b016e4b248atagen2026-10-10 08:12 UTC28
        .await
a9b016e4b248atagen2026-10-10 08:12 UTC29
        .unwrap()
a9b016e4b248atagen2026-10-10 08:12 UTC30
}
a9b016e4b248atagen2026-10-10 08:12 UTC31
a9b016e4b248atagen2026-10-10 08:12 UTC32
async fn json(response: Response) -> Value {
a9b016e4b248atagen2026-10-10 08:12 UTC33
    serde_json::from_slice(&to_bytes(response.into_body(), usize::MAX).await.unwrap()).unwrap()
a9b016e4b248atagen2026-10-10 08:12 UTC34
}
a9b016e4b248atagen2026-10-10 08:12 UTC35
a9b016e4b248atagen2026-10-10 08:12 UTC36
fn seeded(turn: u32) -> (Router, String) {
a9b016e4b248atagen2026-10-10 08:12 UTC37
    let state = Arc::new(AppState::default());
a9b016e4b248atagen2026-10-10 08:12 UTC38
    let mut run = Run::new("cafebabe".into(), 69, 0);
a9b016e4b248atagen2026-10-10 08:12 UTC39
    run.turn = turn;
a9b016e4b248atagen2026-10-10 08:12 UTC40
    let etag = run.origin_etag.clone();
a9b016e4b248atagen2026-10-10 08:12 UTC41
    state.insert(run);
a9b016e4b248atagen2026-10-10 08:12 UTC42
    (router(state), etag)
a9b016e4b248atagen2026-10-10 08:12 UTC43
}
a9b016e4b248atagen2026-10-10 08:12 UTC44
a9b016e4b248atagen2026-10-10 08:12 UTC45
#[tokio::test]
a9b016e4b248atagen2026-10-10 08:12 UTC46
async fn creating_a_run_returns_location_and_etag() {
a9b016e4b248atagen2026-10-10 08:12 UTC47
    let app = jelqd::app();
a9b016e4b248atagen2026-10-10 08:12 UTC48
    let response = send(&app, "POST", "/runs?seed=1", &[], "").await;
a9b016e4b248atagen2026-10-10 08:12 UTC49
    assert_eq!(response.status(), StatusCode::CREATED);
a9b016e4b248atagen2026-10-10 08:12 UTC50
    assert!(response.headers().contains_key(header::LOCATION));
a9b016e4b248atagen2026-10-10 08:12 UTC51
    assert!(response.headers().contains_key(header::ETAG));
a9b016e4b248atagen2026-10-10 08:12 UTC52
    assert_eq!(response.headers()[header::SERVER], "jelqd/0.6.9");
a9b016e4b248atagen2026-10-10 08:12 UTC53
}
a9b016e4b248atagen2026-10-10 08:12 UTC54
a9b016e4b248atagen2026-10-10 08:12 UTC55
#[tokio::test]
a9b016e4b248atagen2026-10-10 08:12 UTC56
async fn jelqing_cold_fails_the_precondition() {
a9b016e4b248atagen2026-10-10 08:12 UTC57
    let (app, _) = seeded(0);
a9b016e4b248atagen2026-10-10 08:12 UTC58
    let response = send(&app, "JELQ", "/runs/cafebabe?technique=stretch", &[], "").await;
a9b016e4b248atagen2026-10-10 08:12 UTC59
    assert_eq!(response.status(), StatusCode::PRECONDITION_FAILED);
a9b016e4b248atagen2026-10-10 08:12 UTC60
    assert_eq!(
a9b016e4b248atagen2026-10-10 08:12 UTC61
        response.headers()[header::CONTENT_TYPE],
a9b016e4b248atagen2026-10-10 08:12 UTC62
        "application/problem+json"
a9b016e4b248atagen2026-10-10 08:12 UTC63
    );
a9b016e4b248atagen2026-10-10 08:12 UTC64
}
a9b016e4b248atagen2026-10-10 08:12 UTC65
a9b016e4b248atagen2026-10-10 08:12 UTC66
#[tokio::test]
a9b016e4b248atagen2026-10-10 08:12 UTC67
async fn wet_without_lube_is_unsupported_media() {
a9b016e4b248atagen2026-10-10 08:12 UTC68
    let (app, _) = seeded(0);
a9b016e4b248atagen2026-10-10 08:12 UTC69
    send(&app, "JELQ", "/runs/cafebabe?technique=warmup", &[], "").await;
a9b016e4b248atagen2026-10-10 08:12 UTC70
    let response = send(&app, "JELQ", "/runs/cafebabe?technique=wet", &[], "").await;
a9b016e4b248atagen2026-10-10 08:12 UTC71
    assert_eq!(response.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE);
a9b016e4b248atagen2026-10-10 08:12 UTC72
    let images = send(
a9b016e4b248atagen2026-10-10 08:12 UTC73
        &app,
a9b016e4b248atagen2026-10-10 08:12 UTC74
        "JELQ",
a9b016e4b248atagen2026-10-10 08:12 UTC75
        "/runs/cafebabe?technique=wet",
a9b016e4b248atagen2026-10-10 08:12 UTC76
        &[("content-type", "image/jpeg")],
a9b016e4b248atagen2026-10-10 08:12 UTC77
        "",
a9b016e4b248atagen2026-10-10 08:12 UTC78
    )
a9b016e4b248atagen2026-10-10 08:12 UTC79
    .await;
a9b016e4b248atagen2026-10-10 08:12 UTC80
    assert_eq!(json(images).await["detail"], "No.");
a9b016e4b248atagen2026-10-10 08:12 UTC81
}
a9b016e4b248atagen2026-10-10 08:12 UTC82
a9b016e4b248atagen2026-10-10 08:12 UTC83
#[tokio::test]
a9b016e4b248atagen2026-10-10 08:12 UTC84
async fn unknown_methods_are_not_implemented() {
a9b016e4b248atagen2026-10-10 08:12 UTC85
    let (app, _) = seeded(0);
a9b016e4b248atagen2026-10-10 08:12 UTC86
    let response = send(&app, "MILK", "/runs/cafebabe", &[], "").await;
a9b016e4b248atagen2026-10-10 08:12 UTC87
    assert_eq!(response.status(), StatusCode::NOT_IMPLEMENTED);
a9b016e4b248atagen2026-10-10 08:12 UTC88
}
a9b016e4b248atagen2026-10-10 08:12 UTC89
a9b016e4b248atagen2026-10-10 08:12 UTC90
#[tokio::test]
a9b016e4b248atagen2026-10-10 08:12 UTC91
async fn patch_only_accepts_the_truth() {
a9b016e4b248atagen2026-10-10 08:12 UTC92
    let (app, _) = seeded(0);
a9b016e4b248atagen2026-10-10 08:12 UTC93
    let patch = [("content-type", "application/json-patch+json")];
a9b016e4b248atagen2026-10-10 08:12 UTC94
    let replace = send(
a9b016e4b248atagen2026-10-10 08:12 UTC95
        &app,
a9b016e4b248atagen2026-10-10 08:12 UTC96
        "PATCH",
a9b016e4b248atagen2026-10-10 08:12 UTC97
        "/runs/cafebabe",
a9b016e4b248atagen2026-10-10 08:12 UTC98
        &patch,
a9b016e4b248atagen2026-10-10 08:12 UTC99
        r#"[{"op":"replace","path":"/length","value":12}]"#,
a9b016e4b248atagen2026-10-10 08:12 UTC100
    )
a9b016e4b248atagen2026-10-10 08:12 UTC101
    .await;
a9b016e4b248atagen2026-10-10 08:12 UTC102
    assert_eq!(replace.status(), StatusCode::UNPROCESSABLE_ENTITY);
a9b016e4b248atagen2026-10-10 08:12 UTC103
    let lie = send(
a9b016e4b248atagen2026-10-10 08:12 UTC104
        &app,
a9b016e4b248atagen2026-10-10 08:12 UTC105
        "PATCH",
a9b016e4b248atagen2026-10-10 08:12 UTC106
        "/runs/cafebabe",
a9b016e4b248atagen2026-10-10 08:12 UTC107
        &patch,
a9b016e4b248atagen2026-10-10 08:12 UTC108
        r#"[{"op":"test","path":"/length","value":40}]"#,
a9b016e4b248atagen2026-10-10 08:12 UTC109
    )
a9b016e4b248atagen2026-10-10 08:12 UTC110
    .await;
a9b016e4b248atagen2026-10-10 08:12 UTC111
    assert_eq!(
a9b016e4b248atagen2026-10-10 08:12 UTC112
        json(lie).await["type"],
a9b016e4b248atagen2026-10-10 08:12 UTC113
        "https://jelq.dev/problems/exaggeration"
a9b016e4b248atagen2026-10-10 08:12 UTC114
    );
a9b016e4b248atagen2026-10-10 08:12 UTC115
}
a9b016e4b248atagen2026-10-10 08:12 UTC116
a9b016e4b248atagen2026-10-10 08:12 UTC117
#[tokio::test]
a9b016e4b248atagen2026-10-10 08:12 UTC118
async fn deleting_the_foreskin_requires_certainty() {
a9b016e4b248atagen2026-10-10 08:12 UTC119
    let (app, _) = seeded(0);
a9b016e4b248atagen2026-10-10 08:12 UTC120
    let response = send(&app, "DELETE", "/runs/cafebabe/foreskin", &[], "").await;
a9b016e4b248atagen2026-10-10 08:12 UTC121
    assert_eq!(response.status(), StatusCode::PRECONDITION_REQUIRED);
a9b016e4b248atagen2026-10-10 08:12 UTC122
    let etag = send(&app, "GET", "/runs/cafebabe/foreskin", &[], "")
a9b016e4b248atagen2026-10-10 08:12 UTC123
        .await
a9b016e4b248atagen2026-10-10 08:12 UTC124
        .headers()[header::ETAG]
a9b016e4b248atagen2026-10-10 08:12 UTC125
        .to_str()
a9b016e4b248atagen2026-10-10 08:12 UTC126
        .unwrap()
a9b016e4b248atagen2026-10-10 08:12 UTC127
        .to_owned();
a9b016e4b248atagen2026-10-10 08:12 UTC128
    let response = send(
a9b016e4b248atagen2026-10-10 08:12 UTC129
        &app,
a9b016e4b248atagen2026-10-10 08:12 UTC130
        "DELETE",
a9b016e4b248atagen2026-10-10 08:12 UTC131
        "/runs/cafebabe/foreskin",
a9b016e4b248atagen2026-10-10 08:12 UTC132
        &[("if-match", &etag)],
a9b016e4b248atagen2026-10-10 08:12 UTC133
        "",
a9b016e4b248atagen2026-10-10 08:12 UTC134
    )
a9b016e4b248atagen2026-10-10 08:12 UTC135
    .await;
a9b016e4b248atagen2026-10-10 08:12 UTC136
    assert_eq!(response.status(), StatusCode::NO_CONTENT);
a9b016e4b248atagen2026-10-10 08:12 UTC137
    let response = send(&app, "GET", "/runs/cafebabe/foreskin", &[], "").await;
a9b016e4b248atagen2026-10-10 08:12 UTC138
    assert_eq!(response.status(), StatusCode::GONE);
a9b016e4b248atagen2026-10-10 08:12 UTC139
}
a9b016e4b248atagen2026-10-10 08:12 UTC140
a9b016e4b248atagen2026-10-10 08:12 UTC141
#[tokio::test]
a9b016e4b248atagen2026-10-10 08:12 UTC142
async fn length_is_chunked_until_the_ruler() {
a9b016e4b248atagen2026-10-10 08:12 UTC143
    let (app, _) = seeded(0);
a9b016e4b248atagen2026-10-10 08:12 UTC144
    let lie = send(&app, "GET", "/runs/cafebabe/length", &[], "").await;
a9b016e4b248atagen2026-10-10 08:12 UTC145
    assert!(!lie.headers().contains_key(header::CONTENT_LENGTH));
a9b016e4b248atagen2026-10-10 08:12 UTC146
    let found = send(&app, "POST", "/runs/cafebabe/drawer", &[], "").await;
a9b016e4b248atagen2026-10-10 08:12 UTC147
    assert_eq!(found.status(), StatusCode::FOUND);
a9b016e4b248atagen2026-10-10 08:12 UTC148
    let truth = send(&app, "GET", "/runs/cafebabe/length", &[], "").await;
a9b016e4b248atagen2026-10-10 08:12 UTC149
    let declared: usize = truth.headers()[header::CONTENT_LENGTH]
a9b016e4b248atagen2026-10-10 08:12 UTC150
        .to_str()
a9b016e4b248atagen2026-10-10 08:12 UTC151
        .unwrap()
a9b016e4b248atagen2026-10-10 08:12 UTC152
        .parse()
a9b016e4b248atagen2026-10-10 08:12 UTC153
        .unwrap();
a9b016e4b248atagen2026-10-10 08:12 UTC154
    let body = to_bytes(truth.into_body(), usize::MAX).await.unwrap();
a9b016e4b248atagen2026-10-10 08:12 UTC155
    assert_eq!(body.len(), declared);
a9b016e4b248atagen2026-10-10 08:12 UTC156
    assert!(body.starts_with(b"8=") && body.ends_with(b"D"));
a9b016e4b248atagen2026-10-10 08:12 UTC157
}
a9b016e4b248atagen2026-10-10 08:12 UTC158
a9b016e4b248atagen2026-10-10 08:12 UTC159
#[tokio::test]
a9b016e4b248atagen2026-10-10 08:12 UTC160
async fn the_clinic_escalates_toward_honesty() {
a9b016e4b248atagen2026-10-10 08:12 UTC161
    let (app, _) = seeded(20);
a9b016e4b248atagen2026-10-10 08:12 UTC162
    let mut hints = Vec::new();
a9b016e4b248atagen2026-10-10 08:12 UTC163
    for _ in 0..3 {
a9b016e4b248atagen2026-10-10 08:12 UTC164
        let response = send(
a9b016e4b248atagen2026-10-10 08:12 UTC165
            &app,
a9b016e4b248atagen2026-10-10 08:12 UTC166
            "GET",
a9b016e4b248atagen2026-10-10 08:12 UTC167
            "/runs/cafebabe/diagnosis",
a9b016e4b248atagen2026-10-10 08:12 UTC168
            &[("authorization", "Excuse \"changing\"")],
a9b016e4b248atagen2026-10-10 08:12 UTC169
            "",
a9b016e4b248atagen2026-10-10 08:12 UTC170
        )
a9b016e4b248atagen2026-10-10 08:12 UTC171
        .await;
a9b016e4b248atagen2026-10-10 08:12 UTC172
        assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
a9b016e4b248atagen2026-10-10 08:12 UTC173
        hints.push(
a9b016e4b248atagen2026-10-10 08:12 UTC174
            response.headers()[header::WWW_AUTHENTICATE]
a9b016e4b248atagen2026-10-10 08:12 UTC175
                .to_str()
a9b016e4b248atagen2026-10-10 08:12 UTC176
                .unwrap()
a9b016e4b248atagen2026-10-10 08:12 UTC177
                .to_owned(),
a9b016e4b248atagen2026-10-10 08:12 UTC178
        );
a9b016e4b248atagen2026-10-10 08:12 UTC179
    }
a9b016e4b248atagen2026-10-10 08:12 UTC180
    assert!(hints[2].contains("Be honest."));
a9b016e4b248atagen2026-10-10 08:12 UTC181
}
a9b016e4b248atagen2026-10-10 08:12 UTC182
a9b016e4b248atagen2026-10-10 08:12 UTC183
#[tokio::test]
a9b016e4b248atagen2026-10-10 08:12 UTC184
async fn the_true_ending_is_not_modified() {
a9b016e4b248atagen2026-10-10 08:12 UTC185
    let (app, etag) = seeded(20);
a9b016e4b248atagen2026-10-10 08:12 UTC186
    let early = seeded(3).0;
a9b016e4b248atagen2026-10-10 08:12 UTC187
    let response = send(&early, "GET", "/runs/cafebabe/diagnosis", &[], "").await;
a9b016e4b248atagen2026-10-10 08:12 UTC188
    assert_eq!(response.status(), StatusCode::TOO_EARLY);
a9b016e4b248atagen2026-10-10 08:12 UTC189
a9b016e4b248atagen2026-10-10 08:12 UTC190
    let response = send(
a9b016e4b248atagen2026-10-10 08:12 UTC191
        &app,
a9b016e4b248atagen2026-10-10 08:12 UTC192
        "GET",
a9b016e4b248atagen2026-10-10 08:12 UTC193
        "/runs/cafebabe/diagnosis",
a9b016e4b248atagen2026-10-10 08:12 UTC194
        &[
a9b016e4b248atagen2026-10-10 08:12 UTC195
            ("authorization", "Bearer honesty"),
a9b016e4b248atagen2026-10-10 08:12 UTC196
            ("if-none-match", &etag),
a9b016e4b248atagen2026-10-10 08:12 UTC197
        ],
a9b016e4b248atagen2026-10-10 08:12 UTC198
        "",
a9b016e4b248atagen2026-10-10 08:12 UTC199
    )
a9b016e4b248atagen2026-10-10 08:12 UTC200
    .await;
a9b016e4b248atagen2026-10-10 08:12 UTC201
    assert_eq!(response.status(), StatusCode::NOT_MODIFIED);
a9b016e4b248atagen2026-10-10 08:12 UTC202
    assert_eq!(response.headers()["x-diagnosis"], "normal");
a9b016e4b248atagen2026-10-10 08:12 UTC203
}
a9b016e4b248atagen2026-10-10 08:12 UTC204
a9b016e4b248atagen2026-10-10 08:12 UTC205
#[tokio::test]
a9b016e4b248atagen2026-10-10 08:12 UTC206
async fn mom_accepts_a_known_excuse() {
a9b016e4b248atagen2026-10-10 08:12 UTC207
    let state = Arc::new(AppState::default());
a9b016e4b248atagen2026-10-10 08:12 UTC208
    let mut run = Run::new("cafebabe".into(), 69, 0);
a9b016e4b248atagen2026-10-10 08:12 UTC209
    run.mom_present = true;
a9b016e4b248atagen2026-10-10 08:12 UTC210
    state.insert(run);
a9b016e4b248atagen2026-10-10 08:12 UTC211
    let app = router(state);
a9b016e4b248atagen2026-10-10 08:12 UTC212
    let response = send(&app, "GET", "/runs/cafebabe", &[], "").await;
a9b016e4b248atagen2026-10-10 08:12 UTC213
    assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
a9b016e4b248atagen2026-10-10 08:12 UTC214
    assert_eq!(response.headers()["via"], "1.1 mom");
a9b016e4b248atagen2026-10-10 08:12 UTC215
    let response = send(
a9b016e4b248atagen2026-10-10 08:12 UTC216
        &app,
a9b016e4b248atagen2026-10-10 08:12 UTC217
        "GET",
a9b016e4b248atagen2026-10-10 08:12 UTC218
        "/runs/cafebabe",
a9b016e4b248atagen2026-10-10 08:12 UTC219
        &[("authorization", "Excuse \"homework\"")],
a9b016e4b248atagen2026-10-10 08:12 UTC220
        "",
a9b016e4b248atagen2026-10-10 08:12 UTC221
    )
a9b016e4b248atagen2026-10-10 08:12 UTC222
    .await;
a9b016e4b248atagen2026-10-10 08:12 UTC223
    assert_eq!(response.status(), StatusCode::OK);
a9b016e4b248atagen2026-10-10 08:12 UTC224
}