atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
a9b016e4b248ebfb4326a7881143856a1fe77252
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git
tests/http.rsRust224 lines6.6 KB
1
use std::sync::Arc;
2
3
use axum::{
4
    Router,
5
    body::{Body, to_bytes},
6
    http::{Method, Request, StatusCode, header},
7
    response::Response,
8
};
9
use jelqd::{AppState, game::Run, router};
10
use serde_json::Value;
11
use tower::ServiceExt;
12
13
async fn send(
14
    app: &Router,
15
    method: &str,
16
    uri: &str,
17
    headers: &[(&str, &str)],
18
    body: &str,
19
) -> Response {
20
    let mut request = Request::builder()
21
        .method(Method::from_bytes(method.as_bytes()).unwrap())
22
        .uri(uri);
23
    for (name, value) in headers {
24
        request = request.header(*name, *value);
25
    }
26
    app.clone()
27
        .oneshot(request.body(Body::from(body.to_owned())).unwrap())
28
        .await
29
        .unwrap()
30
}
31
32
async fn json(response: Response) -> Value {
33
    serde_json::from_slice(&to_bytes(response.into_body(), usize::MAX).await.unwrap()).unwrap()
34
}
35
36
fn seeded(turn: u32) -> (Router, String) {
37
    let state = Arc::new(AppState::default());
38
    let mut run = Run::new("cafebabe".into(), 69, 0);
39
    run.turn = turn;
40
    let etag = run.origin_etag.clone();
41
    state.insert(run);
42
    (router(state), etag)
43
}
44
45
#[tokio::test]
46
async fn creating_a_run_returns_location_and_etag() {
47
    let app = jelqd::app();
48
    let response = send(&app, "POST", "/runs?seed=1", &[], "").await;
49
    assert_eq!(response.status(), StatusCode::CREATED);
50
    assert!(response.headers().contains_key(header::LOCATION));
51
    assert!(response.headers().contains_key(header::ETAG));
52
    assert_eq!(response.headers()[header::SERVER], "jelqd/0.6.9");
53
}
54
55
#[tokio::test]
56
async fn jelqing_cold_fails_the_precondition() {
57
    let (app, _) = seeded(0);
58
    let response = send(&app, "JELQ", "/runs/cafebabe?technique=stretch", &[], "").await;
59
    assert_eq!(response.status(), StatusCode::PRECONDITION_FAILED);
60
    assert_eq!(
61
        response.headers()[header::CONTENT_TYPE],
62
        "application/problem+json"
63
    );
64
}
65
66
#[tokio::test]
67
async fn wet_without_lube_is_unsupported_media() {
68
    let (app, _) = seeded(0);
69
    send(&app, "JELQ", "/runs/cafebabe?technique=warmup", &[], "").await;
70
    let response = send(&app, "JELQ", "/runs/cafebabe?technique=wet", &[], "").await;
71
    assert_eq!(response.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE);
72
    let images = send(
73
        &app,
74
        "JELQ",
75
        "/runs/cafebabe?technique=wet",
76
        &[("content-type", "image/jpeg")],
77
        "",
78
    )
79
    .await;
80
    assert_eq!(json(images).await["detail"], "No.");
81
}
82
83
#[tokio::test]
84
async fn unknown_methods_are_not_implemented() {
85
    let (app, _) = seeded(0);
86
    let response = send(&app, "MILK", "/runs/cafebabe", &[], "").await;
87
    assert_eq!(response.status(), StatusCode::NOT_IMPLEMENTED);
88
}
89
90
#[tokio::test]
91
async fn patch_only_accepts_the_truth() {
92
    let (app, _) = seeded(0);
93
    let patch = [("content-type", "application/json-patch+json")];
94
    let replace = send(
95
        &app,
96
        "PATCH",
97
        "/runs/cafebabe",
98
        &patch,
99
        r#"[{"op":"replace","path":"/length","value":12}]"#,
100
    )
101
    .await;
102
    assert_eq!(replace.status(), StatusCode::UNPROCESSABLE_ENTITY);
103
    let lie = send(
104
        &app,
105
        "PATCH",
106
        "/runs/cafebabe",
107
        &patch,
108
        r#"[{"op":"test","path":"/length","value":40}]"#,
109
    )
110
    .await;
111
    assert_eq!(
112
        json(lie).await["type"],
113
        "https://jelq.dev/problems/exaggeration"
114
    );
115
}
116
117
#[tokio::test]
118
async fn deleting_the_foreskin_requires_certainty() {
119
    let (app, _) = seeded(0);
120
    let response = send(&app, "DELETE", "/runs/cafebabe/foreskin", &[], "").await;
121
    assert_eq!(response.status(), StatusCode::PRECONDITION_REQUIRED);
122
    let etag = send(&app, "GET", "/runs/cafebabe/foreskin", &[], "")
123
        .await
124
        .headers()[header::ETAG]
125
        .to_str()
126
        .unwrap()
127
        .to_owned();
128
    let response = send(
129
        &app,
130
        "DELETE",
131
        "/runs/cafebabe/foreskin",
132
        &[("if-match", &etag)],
133
        "",
134
    )
135
    .await;
136
    assert_eq!(response.status(), StatusCode::NO_CONTENT);
137
    let response = send(&app, "GET", "/runs/cafebabe/foreskin", &[], "").await;
138
    assert_eq!(response.status(), StatusCode::GONE);
139
}
140
141
#[tokio::test]
142
async fn length_is_chunked_until_the_ruler() {
143
    let (app, _) = seeded(0);
144
    let lie = send(&app, "GET", "/runs/cafebabe/length", &[], "").await;
145
    assert!(!lie.headers().contains_key(header::CONTENT_LENGTH));
146
    let found = send(&app, "POST", "/runs/cafebabe/drawer", &[], "").await;
147
    assert_eq!(found.status(), StatusCode::FOUND);
148
    let truth = send(&app, "GET", "/runs/cafebabe/length", &[], "").await;
149
    let declared: usize = truth.headers()[header::CONTENT_LENGTH]
150
        .to_str()
151
        .unwrap()
152
        .parse()
153
        .unwrap();
154
    let body = to_bytes(truth.into_body(), usize::MAX).await.unwrap();
155
    assert_eq!(body.len(), declared);
156
    assert!(body.starts_with(b"8=") && body.ends_with(b"D"));
157
}
158
159
#[tokio::test]
160
async fn the_clinic_escalates_toward_honesty() {
161
    let (app, _) = seeded(20);
162
    let mut hints = Vec::new();
163
    for _ in 0..3 {
164
        let response = send(
165
            &app,
166
            "GET",
167
            "/runs/cafebabe/diagnosis",
168
            &[("authorization", "Excuse \"changing\"")],
169
            "",
170
        )
171
        .await;
172
        assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
173
        hints.push(
174
            response.headers()[header::WWW_AUTHENTICATE]
175
                .to_str()
176
                .unwrap()
177
                .to_owned(),
178
        );
179
    }
180
    assert!(hints[2].contains("Be honest."));
181
}
182
183
#[tokio::test]
184
async fn the_true_ending_is_not_modified() {
185
    let (app, etag) = seeded(20);
186
    let early = seeded(3).0;
187
    let response = send(&early, "GET", "/runs/cafebabe/diagnosis", &[], "").await;
188
    assert_eq!(response.status(), StatusCode::TOO_EARLY);
189
190
    let response = send(
191
        &app,
192
        "GET",
193
        "/runs/cafebabe/diagnosis",
194
        &[
195
            ("authorization", "Bearer honesty"),
196
            ("if-none-match", &etag),
197
        ],
198
        "",
199
    )
200
    .await;
201
    assert_eq!(response.status(), StatusCode::NOT_MODIFIED);
202
    assert_eq!(response.headers()["x-diagnosis"], "normal");
203
}
204
205
#[tokio::test]
206
async fn mom_accepts_a_known_excuse() {
207
    let state = Arc::new(AppState::default());
208
    let mut run = Run::new("cafebabe".into(), 69, 0);
209
    run.mom_present = true;
210
    state.insert(run);
211
    let app = router(state);
212
    let response = send(&app, "GET", "/runs/cafebabe", &[], "").await;
213
    assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
214
    assert_eq!(response.headers()["via"], "1.1 mom");
215
    let response = send(
216
        &app,
217
        "GET",
218
        "/runs/cafebabe",
219
        &[("authorization", "Excuse \"homework\"")],
220
        "",
221
    )
222
    .await;
223
    assert_eq!(response.status(), StatusCode::OK);
224
}