atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
2d9074e0b4de549e73159752d1805e3c2399cbb1
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git
src/tests/household.rsRust395 lines11.9 KB
1
use super::*;
2
3
#[tokio::test]
4
async fn mum_accepts_a_known_excuse() {
5
    let app = seeded_with(Floor::Bedroom, mum_at_the_door);
6
    let response = send(&app, "GET", "/runs/cafebabe", &[], "").await;
7
    assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
8
    assert_eq!(header(&response, "via"), "1.1 mum");
9
    let response = send(
10
        &app,
11
        "GET",
12
        "/runs/cafebabe",
13
        &[("authorization", "Excuse \"homework\"")],
14
        "",
15
    )
16
    .await;
17
    assert_eq!(response.status(), StatusCode::OK);
18
}
19
20
#[tokio::test]
21
async fn mum_runs_out_of_patience() {
22
    let app = seeded_with(Floor::Bedroom, mum_at_the_door);
23
    let lie = [("authorization", "Excuse \"it is for science\"")];
24
    for _ in 0..2 {
25
        let response = send(&app, "GET", "/runs/cafebabe", &lie, "").await;
26
        assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
27
    }
28
    let caught = send(&app, "GET", "/runs/cafebabe", &lie, "").await;
29
    assert_eq!(caught.status(), StatusCode::GONE);
30
    assert_eq!(
31
        json(caught).await["type"],
32
        "https://jelq.dev/problems/caught"
33
    );
34
}
35
36
#[tokio::test]
37
async fn mum_remembers_excuses_by_christmas() {
38
    let app = seeded_with(Floor::Nans, |run| {
39
        mum_at_the_door(run);
40
        run.household.heard.push(Excuse::Homework);
41
    });
42
    let basic = send(
43
        &app,
44
        "GET",
45
        "/runs/cafebabe",
46
        &[("authorization", "Excuse \"changing\"")],
47
        "",
48
    )
49
    .await;
50
    assert_eq!(basic.status(), StatusCode::UNAUTHORIZED);
51
    assert!(header(&basic, "www-authenticate").starts_with("Digest"));
52
    let replay = send(
53
        &app,
54
        "GET",
55
        "/runs/cafebabe",
56
        &[(
57
            "authorization",
58
            "Digest username=\"homework\", nonce=\"abc\"",
59
        )],
60
        "",
61
    )
62
    .await;
63
    assert_eq!(
64
        json(replay).await["type"],
65
        "https://jelq.dev/problems/excuse-replayed"
66
    );
67
    let fresh = send(
68
        &app,
69
        "GET",
70
        "/runs/cafebabe",
71
        &[(
72
            "authorization",
73
            "Digest username=\"wrapping presents\", nonce=\"abc\"",
74
        )],
75
        "",
76
    )
77
    .await;
78
    assert_eq!(fresh.status(), StatusCode::OK);
79
}
80
81
#[tokio::test]
82
async fn a_pulled_muscle_is_a_true_excuse() {
83
    let app = seeded_with(Floor::Nans, |run| {
84
        mum_at_the_door(run);
85
        run.body.conditions.pulled_muscle = true;
86
        run.household.heard.push(Excuse::PulledAMuscle);
87
    });
88
    let response = send(
89
        &app,
90
        "GET",
91
        "/runs/cafebabe",
92
        &[(
93
            "authorization",
94
            "Digest username=\"pulled a muscle\", nonce=\"abc\"",
95
        )],
96
        "",
97
    )
98
    .await;
99
    assert_eq!(response.status(), StatusCode::OK);
100
}
101
102
#[tokio::test]
103
async fn ignoring_mum_has_consequences() {
104
    let app = seeded_with(Floor::Bedroom, |run| {
105
        mum_at_the_door(run);
106
        run.here.turns = 6;
107
    });
108
    let knock = send(&app, "GET", "/runs/cafebabe", &[], "").await;
109
    assert_eq!(
110
        json(knock).await["type"],
111
        "https://jelq.dev/problems/someone-at-the-door"
112
    );
113
    let leave = send(&app, "POST", "/runs/cafebabe/floor", &[], "").await;
114
    assert_eq!(
115
        leave.status(),
116
        StatusCode::UNAUTHORIZED,
117
        "no leaving with Mum at the door"
118
    );
119
    send(&app, "GET", "/runs/cafebabe", &[], "").await;
120
    let opened = send(&app, "GET", "/runs/cafebabe", &[], "").await;
121
    assert_eq!(opened.status(), StatusCode::GONE);
122
    assert_eq!(
123
        json(opened).await["type"],
124
        "https://jelq.dev/problems/opened"
125
    );
126
}
127
128
#[tokio::test]
129
async fn mom_speaks_american() {
130
    let app = seeded_with(Floor::Nans, mum_at_the_door);
131
    let us = [("accept-language", "en-US")];
132
    let knock = send(&app, "GET", "/runs/cafebabe", &us, "").await;
133
    assert_eq!(header(&knock, "content-language"), "en-US");
134
    assert_eq!(header(&knock, "via"), "1.1 mom");
135
    assert!(
136
        json(knock).await["detail"]
137
            .as_str()
138
            .unwrap()
139
            .contains("Someone is at the door.")
140
    );
141
    let excuse = [
142
        ("accept-language", "en-US"),
143
        (
144
            "authorization",
145
            "Digest username=\"carving the turkey\", nonce=\"abc\"",
146
        ),
147
    ];
148
    let answered = send(&app, "GET", "/runs/cafebabe", &excuse, "").await;
149
    assert_eq!(answered.status(), StatusCode::OK);
150
    let floor = json(send(&app, "GET", "/runs/cafebabe/floor", &us, "").await).await;
151
    assert_eq!(floor["name"], "Grandma's house for Thanksgiving");
152
}
153
154
#[tokio::test]
155
async fn an_empty_bottle_is_declined_before_the_enemy_stirs() {
156
    let control = seeded_with(Floor::ChangingRoom, |run| {
157
        run.standing.suspicion = SUSPICION_LIMIT;
158
    });
159
    let lube = [("content-type", "application/baby-oil")];
160
    let caught = send(
161
        &control,
162
        "JELQ",
163
        "/runs/cafebabe?technique=wet&intensity=5",
164
        &lube,
165
        "",
166
    )
167
    .await;
168
    assert_eq!(caught.status(), StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS);
169
170
    let app = seeded_with(Floor::ChangingRoom, |run| {
171
        run.standing.suspicion = SUSPICION_LIMIT;
172
        run.items.lubes.baby_oil = 0;
173
    });
174
    let refused = send(
175
        &app,
176
        "JELQ",
177
        "/runs/cafebabe?technique=wet&intensity=5",
178
        &lube,
179
        "",
180
    )
181
    .await;
182
    assert_eq!(refused.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE);
183
    let view = send(&app, "GET", "/runs/cafebabe", &[], "").await;
184
    assert_eq!(header(&view, "x-suspicion"), "5/5");
185
    assert_eq!(header(&view, "x-shame"), "0");
186
}
187
188
#[tokio::test]
189
async fn respectable_girth_waves_off_the_gym_staff() {
190
    let staff = |girth: f64| {
191
        seeded_with(Floor::ChangingRoom, move |run| {
192
            run.body.eq = 45;
193
            run.body.girth = girth;
194
            run.standing.suspicion = SUSPICION_LIMIT;
195
        })
196
    };
197
    let thin = send(
198
        &staff(4.5),
199
        "JELQ",
200
        "/runs/cafebabe?technique=edge",
201
        &[],
202
        "",
203
    )
204
    .await;
205
    assert_eq!(thin.status(), StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS);
206
    assert_eq!(header(&thin, "x-shame"), "5");
207
208
    let thick = send(
209
        &staff(5.2),
210
        "JELQ",
211
        "/runs/cafebabe?technique=edge",
212
        &[],
213
        "",
214
    )
215
    .await;
216
    assert_eq!(thick.status(), StatusCode::OK);
217
    assert!(
218
        warnings(&thick)
219
            .iter()
220
            .any(|w| w.contains("the gym staff have seen worse"))
221
    );
222
}
223
224
#[tokio::test]
225
async fn the_kitchen_is_only_on_the_home_floors() {
226
    let absent = seeded_with(Floor::Bathroom, |_| {});
227
    let no_kitchen = send(&absent, "POST", "/runs/cafebabe/kitchen", &[], "").await;
228
    assert_eq!(no_kitchen.status(), StatusCode::NOT_FOUND);
229
    let view = send(&absent, "GET", "/runs/cafebabe", &[], "").await;
230
    assert!(!header(&view, "link").contains("/kitchen"));
231
232
    let app = seeded_with(Floor::Bedroom, |_| {});
233
    let view = send(&app, "GET", "/runs/cafebabe", &[], "").await;
234
    assert!(header(&view, "link").contains("/kitchen"));
235
    let raid = send(&app, "POST", "/runs/cafebabe/kitchen", &[], "").await;
236
    assert_ne!(raid.status(), StatusCode::NOT_FOUND);
237
}
238
239
#[tokio::test]
240
async fn any_other_action_takes_the_melon_off_the_table() {
241
    for (method, uri) in [
242
        ("JELQ", "/runs/cafebabe?technique=warmup"),
243
        ("POST", "/runs/cafebabe/drawer"),
244
    ] {
245
        let app = seeded_with(Floor::Bedroom, |run| {
246
            run.body.eq = 95;
247
            run.here.kitchen.melon_offered = true;
248
        });
249
        send(&app, method, uri, &[], "").await;
250
        let melon = send(&app, "POST", "/runs/cafebabe/kitchen/watermelon", &[], "").await;
251
        assert_eq!(melon.status(), StatusCode::CONFLICT, "{method} {uri}");
252
    }
253
}
254
255
#[tokio::test]
256
async fn the_truth_works_under_any_scheme_and_costs_ten() {
257
    let app = seeded_with(Floor::Bedroom, mum_at_the_door);
258
    let truth = [("authorization", "Whatever honesty")];
259
    let confessed = send(&app, "GET", "/runs/cafebabe", &truth, "").await;
260
    assert_eq!(confessed.status(), StatusCode::OK);
261
    assert_eq!(header(&confessed, "x-shame"), "10");
262
}
263
264
#[tokio::test]
265
async fn mum_refuses_basic_and_stale_nonces() {
266
    let bedroom = seeded_with(Floor::Bedroom, mum_at_the_door);
267
    let basic = send(
268
        &bedroom,
269
        "GET",
270
        "/runs/cafebabe",
271
        &[("authorization", "Basic aG9tZXdvcms=")],
272
        "",
273
    )
274
    .await;
275
    assert_eq!(basic.status(), StatusCode::UNAUTHORIZED);
276
    assert!(
277
        json(basic).await["type"]
278
            .as_str()
279
            .unwrap()
280
            .ends_with("/basic")
281
    );
282
283
    let nans = seeded_with(Floor::Nans, mum_at_the_door);
284
    let stale = [(
285
        "authorization",
286
        "Digest username=\"on a call\", nonce=\"yesterday\"",
287
    )];
288
    let refused = send(&nans, "GET", "/runs/cafebabe", &stale, "").await;
289
    assert_eq!(refused.status(), StatusCode::UNAUTHORIZED);
290
    assert!(
291
        json(refused).await["type"]
292
            .as_str()
293
            .unwrap()
294
            .ends_with("stale-nonce")
295
    );
296
}
297
298
#[tokio::test]
299
async fn american_players_can_say_studying() {
300
    let app = seeded_with(Floor::Bedroom, mum_at_the_door);
301
    let excuse = [
302
        ("accept-language", "en-US"),
303
        ("authorization", "Excuse \"Studying\""),
304
    ];
305
    let response = send(&app, "GET", "/runs/cafebabe", &excuse, "").await;
306
    assert_eq!(response.status(), StatusCode::OK);
307
}
308
309
#[tokio::test]
310
async fn raids_get_noisier_and_the_household_notices() {
311
    let told = |suspicion: u8| {
312
        (0..400u64)
313
            .filter(|&seed| {
314
                let mut run = Run::new("cafebabe".into(), "cafe".into(), seed);
315
                run.body.eq = 40;
316
                run.standing.suspicion = suspicion;
317
                run.raid_kitchen().is_err() && run.here.kitchen.closed
318
            })
319
            .count()
320
    };
321
    let quiet = told(0);
322
    let noisy = told(4);
323
    assert!(
324
        (60..=150).contains(&quiet),
325
        "a quiet house told {quiet} of 400 raids, about 100 expected"
326
    );
327
    assert!(
328
        (240..=340).contains(&noisy),
329
        "a noisy house told {noisy} of 400 raids, about 300 expected"
330
    );
331
332
    let mut run = Run::new("cafebabe".into(), "cafe".into(), SEED);
333
    run.body.eq = 40;
334
    let _ = run.raid_kitchen();
335
    assert_eq!(run.standing.suspicion, 1, "every raid makes a little noise");
336
}
337
338
#[tokio::test]
339
async fn one_telling_closes_the_kitchen_and_its_link() {
340
    let app = seeded_with(Floor::Bedroom, |run| run.standing.suspicion = 4);
341
    let mut told = None;
342
    for _ in 0..20 {
343
        let raid = send(&app, "POST", "/runs/cafebabe/kitchen", &[], "").await;
344
        if raid.status() == StatusCode::GONE {
345
            told = Some(raid);
346
            break;
347
        }
348
        assert!(
349
            warnings(&raid).iter().all(|w| !w.contains("Stop raiding")),
350
            "there is no warning before the telling"
351
        );
352
    }
353
    let told = told.expect("a noisy house tells within twenty raids");
354
    assert!(
355
        warnings(&told)
356
            .iter()
357
            .any(|w| w.contains("Stop raiding the kitchen."))
358
    );
359
    assert_eq!(json(told).await["detail"], "The kitchen is off limits.");
360
    let view = send(&app, "GET", "/runs/cafebabe", &[], "").await;
361
    assert!(!header(&view, "link").contains("/kitchen"));
362
}
363
364
#[tokio::test]
365
async fn an_excuse_without_a_scheme_is_explained_for_free() {
366
    let app = seeded_with(Floor::Bedroom, mum_at_the_door);
367
    for said in ["homework", "on a call", "homework", "homework"] {
368
        let response = send(
369
            &app,
370
            "GET",
371
            "/runs/cafebabe",
372
            &[("authorization", said)],
373
            "",
374
        )
375
        .await;
376
        assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
377
        assert!(
378
            json(response).await["detail"]
379
                .as_str()
380
                .unwrap()
381
                .contains("Excuse \"homework\"")
382
        );
383
    }
384
    let view = send(&app, "GET", "/runs/cafebabe", &[], "").await;
385
    assert_eq!(header(&view, "x-shame"), "0");
386
    let excused = send(
387
        &app,
388
        "GET",
389
        "/runs/cafebabe",
390
        &[("authorization", "Excuse \"homework\"")],
391
        "",
392
    )
393
    .await;
394
    assert_eq!(excused.status(), StatusCode::OK);
395
}