atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
ddcf8b3a91dcd45287bd3610dcd17be6608c8fb3
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git
src/tests/household.rsRust490 lines14.6 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
        run.items.lubes.baby_oil = 1;
159
    });
160
    let lube = [("content-type", "application/baby-oil")];
161
    let caught = send(
162
        &control,
163
        "JELQ",
164
        "/runs/cafebabe?technique=wet&intensity=5",
165
        &lube,
166
        "",
167
    )
168
    .await;
169
    assert_eq!(caught.status(), StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS);
170
171
    let app = seeded_with(Floor::ChangingRoom, |run| {
172
        run.standing.suspicion = SUSPICION_LIMIT;
173
        run.items.lubes.baby_oil = 0;
174
    });
175
    let refused = send(
176
        &app,
177
        "JELQ",
178
        "/runs/cafebabe?technique=wet&intensity=5",
179
        &lube,
180
        "",
181
    )
182
    .await;
183
    assert_eq!(refused.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE);
184
    let view = send(&app, "GET", "/runs/cafebabe", &[], "").await;
185
    assert_eq!(header(&view, "x-suspicion"), "5/5");
186
    assert_eq!(header(&view, "x-shame"), "0");
187
}
188
189
#[tokio::test]
190
async fn respectable_girth_waves_off_the_gym_staff() {
191
    let staff = |girth: f64| {
192
        seeded_with(Floor::ChangingRoom, move |run| {
193
            run.body.eq = 45;
194
            run.body.girth = girth;
195
            run.standing.suspicion = SUSPICION_LIMIT;
196
        })
197
    };
198
    let thin = send(
199
        &staff(4.5),
200
        "JELQ",
201
        "/runs/cafebabe?technique=edge",
202
        &[],
203
        "",
204
    )
205
    .await;
206
    assert_eq!(thin.status(), StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS);
207
    assert_eq!(header(&thin, "x-shame"), "5");
208
209
    let thick = send(
210
        &staff(5.2),
211
        "JELQ",
212
        "/runs/cafebabe?technique=edge",
213
        &[],
214
        "",
215
    )
216
    .await;
217
    assert_eq!(thick.status(), StatusCode::OK);
218
    assert!(
219
        warnings(&thick)
220
            .iter()
221
            .any(|w| w.contains("the gym staff have seen worse"))
222
    );
223
}
224
225
#[tokio::test]
226
async fn the_kitchen_is_only_on_the_home_floors() {
227
    let absent = seeded_with(Floor::Bathroom, |_| {});
228
    let no_kitchen = send(&absent, "POST", "/runs/cafebabe/kitchen", &[], "").await;
229
    assert_eq!(no_kitchen.status(), StatusCode::NOT_FOUND);
230
    let view = send(&absent, "GET", "/runs/cafebabe", &[], "").await;
231
    assert!(!header(&view, "link").contains("/kitchen"));
232
233
    let app = seeded_with(Floor::Bedroom, |_| {});
234
    let view = send(&app, "GET", "/runs/cafebabe", &[], "").await;
235
    assert!(header(&view, "link").contains("/kitchen"));
236
    let raid = send(&app, "POST", "/runs/cafebabe/kitchen", &[], "").await;
237
    assert_ne!(raid.status(), StatusCode::NOT_FOUND);
238
}
239
240
#[tokio::test]
241
async fn any_other_action_takes_the_melon_off_the_table() {
242
    for (method, uri) in [
243
        ("JELQ", "/runs/cafebabe?technique=warmup"),
244
        ("POST", "/runs/cafebabe/drawer"),
245
    ] {
246
        let app = seeded_with(Floor::Bedroom, |run| {
247
            run.body.eq = 95;
248
            run.here.kitchen.melon_offered = true;
249
        });
250
        send(&app, method, uri, &[], "").await;
251
        let melon = send(&app, "POST", "/runs/cafebabe/kitchen/watermelon", &[], "").await;
252
        assert_eq!(melon.status(), StatusCode::CONFLICT, "{method} {uri}");
253
    }
254
}
255
256
#[tokio::test]
257
async fn the_truth_works_under_any_scheme_and_costs_ten() {
258
    let app = seeded_with(Floor::Bedroom, mum_at_the_door);
259
    let truth = [("authorization", "Whatever honesty")];
260
    let confessed = send(&app, "GET", "/runs/cafebabe", &truth, "").await;
261
    assert_eq!(confessed.status(), StatusCode::OK);
262
    assert_eq!(header(&confessed, "x-shame"), "10");
263
}
264
265
#[tokio::test]
266
async fn mum_refuses_basic_and_stale_nonces() {
267
    let bedroom = seeded_with(Floor::Bedroom, mum_at_the_door);
268
    let basic = send(
269
        &bedroom,
270
        "GET",
271
        "/runs/cafebabe",
272
        &[("authorization", "Basic aG9tZXdvcms=")],
273
        "",
274
    )
275
    .await;
276
    assert_eq!(basic.status(), StatusCode::UNAUTHORIZED);
277
    assert!(
278
        json(basic).await["type"]
279
            .as_str()
280
            .unwrap()
281
            .ends_with("/basic")
282
    );
283
284
    let nans = seeded_with(Floor::Nans, mum_at_the_door);
285
    let stale = [(
286
        "authorization",
287
        "Digest username=\"on a call\", nonce=\"yesterday\"",
288
    )];
289
    let refused = send(&nans, "GET", "/runs/cafebabe", &stale, "").await;
290
    assert_eq!(refused.status(), StatusCode::UNAUTHORIZED);
291
    assert!(
292
        json(refused).await["type"]
293
            .as_str()
294
            .unwrap()
295
            .ends_with("stale-nonce")
296
    );
297
}
298
299
#[tokio::test]
300
async fn american_players_can_say_studying() {
301
    let app = seeded_with(Floor::Bedroom, mum_at_the_door);
302
    let excuse = [
303
        ("accept-language", "en-US"),
304
        ("authorization", "Excuse \"Studying\""),
305
    ];
306
    let response = send(&app, "GET", "/runs/cafebabe", &excuse, "").await;
307
    assert_eq!(response.status(), StatusCode::OK);
308
}
309
310
#[tokio::test]
311
async fn raids_get_noisier_and_the_household_notices() {
312
    let told = |suspicion: u8| {
313
        (0..400u64)
314
            .filter(|&seed| {
315
                let mut run = Run::new("cafebabe".into(), "cafe".into(), seed);
316
                run.body.eq = 40;
317
                run.standing.suspicion = suspicion;
318
                run.raid_kitchen().is_err() && run.here.kitchen.closed
319
            })
320
            .count()
321
    };
322
    let quiet = told(0);
323
    let noisy = told(4);
324
    assert!(
325
        (60..=150).contains(&quiet),
326
        "a quiet house told {quiet} of 400 raids, about 100 expected"
327
    );
328
    assert!(
329
        (240..=340).contains(&noisy),
330
        "a noisy house told {noisy} of 400 raids, about 300 expected"
331
    );
332
333
    let mut run = Run::new("cafebabe".into(), "cafe".into(), SEED);
334
    run.body.eq = 40;
335
    let _ = run.raid_kitchen();
336
    assert_eq!(run.standing.suspicion, 1, "every raid makes a little noise");
337
}
338
339
#[tokio::test]
340
async fn one_telling_closes_the_kitchen_and_its_link() {
341
    let mut told = None;
342
    for seed in 0..20 {
343
        let app = app_with(Floor::Bedroom, seed, |run| run.standing.suspicion = 4);
344
        let raid = send(&app, "POST", "/runs/cafebabe/kitchen", &[], "").await;
345
        if raid.status() == StatusCode::GONE {
346
            told = Some((app, raid));
347
            break;
348
        }
349
        assert!(
350
            warnings(&raid).iter().all(|w| !w.contains("Stop raiding")),
351
            "there is no warning before the telling"
352
        );
353
    }
354
    let (app, told) = told.expect("a noisy house tells within twenty houses");
355
    assert!(
356
        warnings(&told)
357
            .iter()
358
            .any(|w| w.contains("Stop raiding the kitchen."))
359
    );
360
    assert_eq!(json(told).await["detail"], "The kitchen is off limits.");
361
    let view = send(&app, "GET", "/runs/cafebabe", &[], "").await;
362
    assert!(!header(&view, "link").contains("/kitchen"));
363
}
364
365
#[tokio::test]
366
async fn an_excuse_without_a_scheme_is_explained_for_free() {
367
    let app = seeded_with(Floor::Bedroom, mum_at_the_door);
368
    for said in ["homework", "on a call", "homework", "homework"] {
369
        let response = send(
370
            &app,
371
            "GET",
372
            "/runs/cafebabe",
373
            &[("authorization", said)],
374
            "",
375
        )
376
        .await;
377
        assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
378
        assert!(
379
            json(response).await["detail"]
380
                .as_str()
381
                .unwrap()
382
                .contains("Excuse \"homework\"")
383
        );
384
    }
385
    let view = send(&app, "GET", "/runs/cafebabe", &[], "").await;
386
    assert_eq!(header(&view, "x-shame"), "0");
387
    let excused = send(
388
        &app,
389
        "GET",
390
        "/runs/cafebabe",
391
        &[("authorization", "Excuse \"homework\"")],
392
        "",
393
    )
394
    .await;
395
    assert_eq!(excused.status(), StatusCode::OK);
396
}
397
398
#[tokio::test]
399
async fn mum_leaves_the_way_her_errand_suggests() {
400
    let app = seeded_with(Floor::Bedroom, mum_at_the_door);
401
    let pockets = send(&app, "GET", "/runs/cafebabe/inventory", &[], "").await;
402
    assert_eq!(
403
        header(&pockets, "www-authenticate"),
404
        "Excuse realm=\"household\""
405
    );
406
    let excused = send(
407
        &app,
408
        "GET",
409
        "/runs/cafebabe",
410
        &[("authorization", "Excuse \"homework\"")],
411
        "",
412
    )
413
    .await;
414
    assert!(warnings(&excused).iter().any(|w| w.contains("the pipes")));
415
    assert!(excused.headers().get("www-authenticate").is_none());
416
}
417
418
#[tokio::test]
419
async fn a_stale_nonce_is_explained_for_free() {
420
    let app = seeded_with(Floor::Nans, mum_at_the_door);
421
    for _ in 0..4 {
422
        let stale = send(
423
            &app,
424
            "GET",
425
            "/runs/cafebabe",
426
            &[(
427
                "authorization",
428
                "Digest username=\"helping Nan\", nonce=\"old\"",
429
            )],
430
            "",
431
        )
432
        .await;
433
        assert_eq!(stale.status(), StatusCode::UNAUTHORIZED);
434
        assert!(header(&stale, "www-authenticate").contains("nonce=\"abc\""));
435
    }
436
    let current = send(
437
        &app,
438
        "GET",
439
        "/runs/cafebabe",
440
        &[(
441
            "authorization",
442
            "Digest username=\"helping Nan\", nonce=\"abc\"",
443
        )],
444
        "",
445
    )
446
    .await;
447
    assert_eq!(current.status(), StatusCode::OK);
448
}
449
450
#[tokio::test]
451
async fn mum_announces_herself_but_never_returns_on_the_turn_she_left() {
452
    let mut arrivals = 0;
453
    for seed in 0..200u64 {
454
        let app = app_with(Floor::Bedroom, seed, |run| {
455
            mum_at_the_door(run);
456
            run.body.eq = 60;
457
            run.here.brother_told = true;
458
            run.standing.suspicion = 4;
459
        });
460
        let excused = send(
461
            &app,
462
            "JELQ",
463
            "/runs/cafebabe?technique=stretch&intensity=10",
464
            &[("authorization", "Excuse \"homework\"")],
465
            "",
466
        )
467
        .await;
468
        assert!(
469
            excused.headers().get("www-authenticate").is_none(),
470
            "seed {seed} fetched Mum straight back"
471
        );
472
        let again = send(
473
            &app,
474
            "JELQ",
475
            "/runs/cafebabe?technique=stretch&intensity=10",
476
            &[],
477
            "",
478
        )
479
        .await;
480
        if again.headers().get("www-authenticate").is_some() {
481
            arrivals += 1;
482
            assert!(
483
                warnings(&again)
484
                    .iter()
485
                    .any(|w| w.contains("Mum is at the door. She"))
486
            );
487
        }
488
    }
489
    assert!(arrivals > 0, "Mum never came back at all");
490
}