atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
56d34031c32175c328592f822e27127d1e792ab2
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git
src/tests/body.rsRust493 lines15.5 KB
1
use super::*;
2
3
#[tokio::test]
4
async fn deleting_the_foreskin_requires_certainty() {
5
    let Harness { app, .. } = harness(Floor::Bedroom, SEED, |_| {});
6
    let response = send(&app, "DELETE", "/runs/cafebabe/foreskin", &[], "").await;
7
    assert_eq!(response.status(), StatusCode::PRECONDITION_REQUIRED);
8
    let look = send(&app, "GET", "/runs/cafebabe/foreskin", &[], "").await;
9
    assert_eq!(look.status(), StatusCode::OK);
10
    let etag = header(&look, "etag").to_owned();
11
    assert!(text(look).await.contains('~'));
12
    let response = send(
13
        &app,
14
        "DELETE",
15
        "/runs/cafebabe/foreskin",
16
        &[("if-match", &etag)],
17
        "",
18
    )
19
    .await;
20
    assert_eq!(response.status(), StatusCode::NO_CONTENT);
21
    let response = send(&app, "GET", "/runs/cafebabe/foreskin", &[], "").await;
22
    assert_eq!(response.status(), StatusCode::GONE);
23
}
24
25
#[tokio::test]
26
async fn length_supports_inch_ranges() {
27
    let Harness { app, .. } = harness(Floor::Bedroom, SEED, |_| {});
28
    let response = send(
29
        &app,
30
        "GET",
31
        "/runs/cafebabe/length",
32
        &[("range", "inches=0-1")],
33
        "",
34
    )
35
    .await;
36
    assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
37
    assert!(header(&response, "content-range").starts_with("inches 0.00-1.00/"));
38
    assert_eq!(text(response).await.len(), 25);
39
    let absurd = send(
40
        &app,
41
        "GET",
42
        "/runs/cafebabe/length",
43
        &[("range", "inches=40-50")],
44
        "",
45
    )
46
    .await;
47
    assert_eq!(absurd.status(), StatusCode::RANGE_NOT_SATISFIABLE);
48
    let malformed = send(
49
        &app,
50
        "GET",
51
        "/runs/cafebabe/length",
52
        &[("range", "inches=6.17")],
53
        "",
54
    )
55
    .await;
56
    assert_eq!(malformed.status(), StatusCode::OK);
57
    let suffix = send(
58
        &app,
59
        "GET",
60
        "/runs/cafebabe/length",
61
        &[("range", "inches=-1")],
62
        "",
63
    )
64
    .await;
65
    assert_eq!(suffix.status(), StatusCode::PARTIAL_CONTENT);
66
    assert!(text(suffix).await.ends_with('D'));
67
    for (range, status) in [
68
        ("inches=1-", StatusCode::PARTIAL_CONTENT),
69
        ("inches=40-", StatusCode::RANGE_NOT_SATISFIABLE),
70
        ("inches=-0", StatusCode::RANGE_NOT_SATISFIABLE),
71
        ("inches=2-1", StatusCode::OK),
72
        ("inches=NaN-2", StatusCode::OK),
73
        ("inches=0-inf", StatusCode::OK),
74
    ] {
75
        let response = send(
76
            &app,
77
            "GET",
78
            "/runs/cafebabe/length",
79
            &[("range", range)],
80
            "",
81
        )
82
        .await;
83
        assert_eq!(response.status(), status, "{range}");
84
    }
85
}
86
87
#[tokio::test]
88
async fn the_displayed_length_is_accepted_as_honest() {
89
    let app = seeded_with(Floor::Forum, |run| run.body.length = 5.2496);
90
    let response = send(
91
        &app,
92
        "GET",
93
        "/runs/cafebabe/elder",
94
        &[("proxy-authorization", "Ruler 5.25")],
95
        "",
96
    )
97
    .await;
98
    assert_eq!(response.status(), StatusCode::OK);
99
}
100
101
#[tokio::test]
102
async fn dry_jelqs_build_friction_and_real_lube_does_not() {
103
    let app = seeded_with(Floor::ChangingRoom, |run| {
104
        run.body.eq = 60;
105
        run.items.lubes.baby_oil = 1;
106
        run.body.foreskin = Some(0.9);
107
    });
108
    let dry = send(&app, "JELQ", "/runs/cafebabe?technique=dry", &[], "").await;
109
    assert_eq!(header(&dry, "x-friction"), "2/10");
110
    let lube = [("content-type", "application/baby-oil")];
111
    let wet = send(
112
        &app,
113
        "JELQ",
114
        "/runs/cafebabe?technique=wet&intensity=5",
115
        &lube,
116
        "",
117
    )
118
    .await;
119
    assert_eq!(header(&wet, "x-friction"), "2/10");
120
    let spit = [("content-type", "text/plain; charset=spit")];
121
    let glisten = send(
122
        &app,
123
        "JELQ",
124
        "/runs/cafebabe?technique=wet&intensity=5",
125
        &spit,
126
        "",
127
    )
128
    .await;
129
    assert_eq!(header(&glisten, "x-friction"), "3/10");
130
}
131
132
#[tokio::test]
133
async fn switching_grips_makes_the_friction_cheaper() {
134
    let app = seeded_with(Floor::ChangingRoom, |run| {
135
        run.body.eq = 60;
136
        run.body.foreskin = Some(0.9);
137
    });
138
    let dry = send(&app, "JELQ", "/runs/cafebabe?technique=dry", &[], "").await;
139
    assert_eq!(header(&dry, "x-friction"), "2/10");
140
    send(
141
        &app,
142
        "GET",
143
        "/runs/cafebabe/grip",
144
        &[("upgrade", "underhand"), ("connection", "upgrade")],
145
        "",
146
    )
147
    .await;
148
    let switched = send(&app, "JELQ", "/runs/cafebabe?technique=dry", &[], "").await;
149
    assert_eq!(header(&switched, "x-friction"), "3/10");
150
    let settled = send(&app, "JELQ", "/runs/cafebabe?technique=dry", &[], "").await;
151
    assert_eq!(header(&settled, "x-friction"), "5/10");
152
}
153
154
#[tokio::test]
155
async fn holding_and_meditating_shed_friction() {
156
    let app = seeded_with(Floor::Bedroom, |run| {
157
        run.body.eq = 80;
158
        run.body.friction = 5;
159
    });
160
    let held = send(&app, "HOLD", "/runs/cafebabe", &[], "").await;
161
    assert_eq!(held.status(), StatusCode::NO_CONTENT);
162
    let view = send(&app, "GET", "/runs/cafebabe", &[], "").await;
163
    assert_eq!(header(&view, "x-friction"), "3/10");
164
165
    let taught = seeded_with(Floor::Bedroom, |run| {
166
        run.body.friction = 5;
167
        run.online.meditation_taught = true;
168
    });
169
    let calmed = send(
170
        &taught,
171
        "JELQ",
172
        "/runs/cafebabe?technique=meditate",
173
        &[],
174
        "",
175
    )
176
    .await;
177
    assert_eq!(calmed.status(), StatusCode::OK);
178
    let view = send(&taught, "GET", "/runs/cafebabe", &[], "").await;
179
    assert_eq!(header(&view, "x-friction"), "3/10");
180
}
181
182
#[tokio::test]
183
async fn a_friction_burn_halves_the_gains() {
184
    let lube = [("content-type", "application/baby-oil")];
185
    let path = "/runs/cafebabe?technique=wet&intensity=5";
186
    for seed in 0..60u64 {
187
        let steady = app_with(Floor::Bedroom, seed, |run| {
188
            run.body.eq = 60;
189
            run.items.lubes.baby_oil = 9;
190
        });
191
        let hurt_app = app_with(Floor::Bedroom, seed, |run| {
192
            run.body.eq = 60;
193
            run.items.lubes.baby_oil = 9;
194
            run.body.conditions.friction_burn = true;
195
        });
196
        let calm = send(&steady, "JELQ", path, &lube, "").await;
197
        let hurt = send(&hurt_app, "JELQ", path, &lube, "").await;
198
        if calm.status() != StatusCode::OK || hurt.status() != StatusCode::OK {
199
            continue;
200
        }
201
        let calm_gain = json(calm).await["length"].as_f64().unwrap();
202
        let hurt_gain = json(hurt).await["length"].as_f64().unwrap();
203
        assert!(
204
            (hurt_gain * 2.0 - calm_gain).abs() < 1e-9,
205
            "seed {seed} burned {hurt_gain} against {calm_gain}"
206
        );
207
        return;
208
    }
209
    panic!("no seed in 60 produced two clean rolls to compare");
210
}
211
212
#[tokio::test]
213
async fn the_burn_warns_and_the_ice_treats_it() {
214
    let app = seeded_with(Floor::Bedroom, |run| {
215
        run.body.eq = 60;
216
        run.items.lubes.baby_oil = 9;
217
        run.body.friction = 7;
218
        run.body.conditions.friction_burn = true;
219
    });
220
    let lube = [("content-type", "application/baby-oil")];
221
    let jelq = send(
222
        &app,
223
        "JELQ",
224
        "/runs/cafebabe?technique=wet&intensity=5",
225
        &lube,
226
        "",
227
    )
228
    .await;
229
    let warnings: Vec<_> = jelq
230
        .headers()
231
        .get_all("warning")
232
        .iter()
233
        .filter_map(|w| w.to_str().ok())
234
        .collect();
235
    assert!(warnings.contains(&"199 - \"friction burn\""));
236
    send(&app, "JELQ", "/runs/cafebabe?technique=ice", &[], "").await;
237
    let view = send(&app, "GET", "/runs/cafebabe", &[], "").await;
238
    let warnings: Vec<_> = view
239
        .headers()
240
        .get_all("warning")
241
        .iter()
242
        .filter_map(|w| w.to_str().ok())
243
        .collect();
244
    assert!(!warnings.contains(&"199 - \"friction burn\""));
245
}
246
247
#[tokio::test]
248
async fn friction_rises_with_vigour_and_edging_chafes() {
249
    let spit = [("content-type", "text/plain; charset=spit")];
250
    for (intensity, friction) in [(5, "1/10"), (7, "2/10"), (10, "3/10")] {
251
        let app = seeded_with(Floor::Hotel, |run| {
252
            run.here.wifi_accepted = true;
253
            run.body.eq = 55;
254
            run.body.foreskin = Some(0.5);
255
        });
256
        let uri = format!("/runs/cafebabe?technique=wet&intensity={intensity}");
257
        let jelq = send(&app, "JELQ", &uri, &spit, "").await;
258
        assert_eq!(
259
            header(&jelq, "x-friction"),
260
            friction,
261
            "intensity {intensity}"
262
        );
263
    }
264
    let app = seeded_with(Floor::Hotel, |run| {
265
        run.here.wifi_accepted = true;
266
        run.body.eq = 55;
267
        run.body.foreskin = Some(0.5);
268
    });
269
    let edge = send(&app, "JELQ", "/runs/cafebabe?technique=edge", &[], "").await;
270
    assert_eq!(header(&edge, "x-friction"), "2/10");
271
272
    let oiled = seeded_with(Floor::Hotel, |run| {
273
        run.here.wifi_accepted = true;
274
        run.body.eq = 55;
275
        run.body.foreskin = Some(0.5);
276
        run.items.lubes.baby_oil = 1;
277
    });
278
    let oil = [("content-type", "application/baby-oil")];
279
    let edge = send(&oiled, "JELQ", "/runs/cafebabe?technique=edge", &oil, "").await;
280
    assert_eq!(header(&edge, "x-friction"), "0/10");
281
    let empty = send(&oiled, "JELQ", "/runs/cafebabe?technique=edge", &oil, "").await;
282
    assert_eq!(empty.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE);
283
}
284
285
#[tokio::test]
286
async fn the_body_changes_how_the_skin_takes_it() {
287
    let spit = [("content-type", "text/plain; charset=spit")];
288
    for (foreskin, girth, friction) in [(0.5, 4.5, "1/10"), (1.2, 4.5, "0/10"), (0.5, 5.8, "2/10")]
289
    {
290
        let app = seeded_with(Floor::Hotel, |run| {
291
            run.here.wifi_accepted = true;
292
            run.body.eq = 55;
293
            run.body.foreskin = Some(foreskin);
294
            run.body.girth = girth;
295
        });
296
        let jelq = send(&app, "JELQ", "/runs/cafebabe?technique=wet", &spit, "").await;
297
        assert_eq!(header(&jelq, "x-friction"), friction, "{foreskin} {girth}");
298
    }
299
}
300
301
#[tokio::test]
302
async fn a_thick_girth_outgrows_the_underhand_grip() {
303
    let app = seeded_with(Floor::Bedroom, |run| run.body.girth = 5.8);
304
    let grip = send(
305
        &app,
306
        "GET",
307
        "/runs/cafebabe/grip",
308
        &[("upgrade", "underhand"), ("connection", "upgrade")],
309
        "",
310
    )
311
    .await;
312
    assert_eq!(grip.status(), StatusCode::PAYLOAD_TOO_LARGE);
313
}
314
315
#[tokio::test]
316
async fn a_long_run_fills_the_jeans_before_a_thick_one() {
317
    let app = seeded_with(Floor::Bedroom, |run| {
318
        run.items.kit.extender = true;
319
        run.body.girth = 4.8;
320
        run.body.length = 7.5;
321
    });
322
    let lube = [("content-type", "application/baby-oil")];
323
    let full = send(&app, "JELQ", "/runs/cafebabe?technique=wet", &lube, "").await;
324
    assert_eq!(full.status(), StatusCode::INSUFFICIENT_STORAGE);
325
}
326
327
#[tokio::test]
328
async fn a_refused_jelq_leaves_the_lube_alone() {
329
    let app = seeded_with(Floor::Bedroom, |run| {
330
        run.items.kit.extender = true;
331
        run.items.lubes.baby_oil = 1;
332
        run.body.girth = 6.0;
333
        run.body.length = 6.0;
334
    });
335
    let lube = [("content-type", "application/baby-oil")];
336
    let full = send(
337
        &app,
338
        "JELQ",
339
        "/runs/cafebabe?technique=wet&intensity=5",
340
        &lube,
341
        "",
342
    )
343
    .await;
344
    assert_eq!(full.status(), StatusCode::INSUFFICIENT_STORAGE);
345
    let inv = send(&app, "GET", "/runs/cafebabe/inventory", &[], "").await;
346
    let body = json(inv).await;
347
    let held = body["items"].as_array().unwrap();
348
    assert!(held.iter().any(|i| i["name"] == "baby-oil"));
349
}
350
351
#[tokio::test]
352
async fn lubes_run_out_but_spit_never_does() {
353
    let app = seeded_with(Floor::Bedroom, |run| {
354
        run.items.lubes.baby_oil = 1;
355
    });
356
    let lube = [("content-type", "application/baby-oil")];
357
    send(&app, "JELQ", "/runs/cafebabe?technique=wet", &lube, "").await;
358
    let second = send(&app, "JELQ", "/runs/cafebabe?technique=wet", &lube, "").await;
359
    assert_eq!(second.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE);
360
361
    let spit = [("content-type", "text/plain; charset=spit")];
362
    for _ in 0..3 {
363
        let wet = send(&app, "JELQ", "/runs/cafebabe?technique=wet", &spit, "").await;
364
        assert_ne!(
365
            wet.status(),
366
            StatusCode::UNSUPPORTED_MEDIA_TYPE,
367
            "spit never runs out"
368
        );
369
    }
370
}
371
372
#[tokio::test]
373
async fn a_combo_chafes_twice_and_costs_two_stamina() {
374
    let app = seeded_with(Floor::Hotel, |run| {
375
        run.here.wifi_accepted = true;
376
        run.body.eq = 55;
377
        run.body.foreskin = Some(0.5);
378
    });
379
    let spit = [("content-type", "text/plain; charset=spit")];
380
    let combo = send(
381
        &app,
382
        "JELQ",
383
        "/runs/cafebabe?technique=wet,stretch",
384
        &spit,
385
        "",
386
    )
387
    .await;
388
    assert_eq!(header(&combo, "x-friction"), "2/10");
389
    assert_eq!(header(&combo, "x-stamina"), "8/10");
390
}
391
392
#[tokio::test]
393
async fn a_combo_tells_each_technique_in_full() {
394
    let mut told = 0;
395
    for seed in 0..40 {
396
        let app = app_with(Floor::Bedroom, seed, |run| run.body.eq = 60);
397
        let lube = [("content-type", "application/baby-oil")];
398
        let combo = send(
399
            &app,
400
            "JELQ",
401
            "/runs/cafebabe?technique=wet,stretch",
402
            &lube,
403
            "",
404
        )
405
        .await;
406
        if combo.status() != StatusCode::MULTI_STATUS {
407
            continue;
408
        }
409
        for report in text(combo).await.split("<D:response>").skip(1) {
410
            if report.contains("HTTP/1.1 204") {
411
                continue;
412
            }
413
            assert!(
414
                report.contains("<D:responsedescription>{\"") && report.contains("\"detail\""),
415
                "{report}"
416
            );
417
            told += 1;
418
        }
419
    }
420
    assert!(told > 0, "no combo told anything in forty runs");
421
}
422
423
#[test]
424
fn the_drawer_starts_with_at_most_one_bottle() {
425
    let mut stocked = 0;
426
    for seed in 0..400u64 {
427
        let lubes = &Run::new("cafebabe".into(), "cafe".into(), seed).items.lubes;
428
        let bottles = lubes.baby_oil + lubes.lotion;
429
        assert!(bottles <= 1 && lubes.coconut_oil + lubes.vaseline == 0);
430
        stocked += bottles;
431
    }
432
    assert!(
433
        (160..=240).contains(&stocked),
434
        "{stocked}/400 started with lube"
435
    );
436
}
437
438
#[tokio::test]
439
async fn stretching_reports_the_foreskin_it_grew() {
440
    let mut landed = 0;
441
    for seed in 0..40u64 {
442
        let app = app_with(Floor::Bedroom, seed, |run| {
443
            run.body.eq = 60;
444
            run.body.foreskin = Some(1.0);
445
        });
446
        let stretch = send(&app, "JELQ", "/runs/cafebabe?technique=stretch", &[], "").await;
447
        if stretch.status() == StatusCode::OK {
448
            assert_eq!(header(&stretch, "x-foreskin"), "1.03");
449
            assert_eq!(json(stretch).await["foreskin"], 0.03);
450
            landed += 1;
451
        }
452
    }
453
    assert!(landed > 0, "forty stretches and none landed clean");
454
}
455
456
#[tokio::test]
457
async fn lotion_sheds_friction_at_a_gentle_pace() {
458
    let app = seeded_with(Floor::Bedroom, |run| {
459
        run.body.eq = 55;
460
        run.body.friction = 4;
461
        run.items.lubes.lotion = 1;
462
    });
463
    let stroke = send(
464
        &app,
465
        "JELQ",
466
        "/runs/cafebabe?technique=wet&intensity=5",
467
        &[("content-type", "application/lotion")],
468
        "",
469
    )
470
    .await;
471
    assert_eq!(header(&stroke, "x-friction"), "3/10");
472
}
473
474
#[tokio::test]
475
async fn lotion_soothes_a_friction_burn() {
476
    let app = seeded_with(Floor::ChangingRoom, |run| {
477
        run.body.eq = 60;
478
        run.body.friction = 7;
479
        run.body.conditions.friction_burn = true;
480
        run.items.lubes.lotion = 1;
481
    });
482
    let soothed = send(
483
        &app,
484
        "JELQ",
485
        "/runs/cafebabe?technique=wet",
486
        &[("content-type", "application/lotion")],
487
        "",
488
    )
489
    .await;
490
    let warned = warnings(&soothed);
491
    assert!(warned.iter().any(|w| w.contains("soothed")));
492
    assert!(warned.iter().all(|w| w != "199 - \"friction burn\""));
493
}