atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
4b1f5f88827de40c3cc25edaf1c8acea7192d6cf
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git
src/tests/turns.rsRust227 lines8.9 KB
1
use super::*;
2
3
#[tokio::test]
4
async fn meditation_is_free_but_only_once_per_floor() {
5
    let app = seeded_with(Floor::Bedroom, |run| run.online.meditation_taught = true);
6
    let response = send(&app, "JELQ", "/runs/cafebabe?technique=meditate", &[], "").await;
7
    assert_eq!(response.status(), StatusCode::OK);
8
    assert_eq!(header(&response, "x-eq"), "10");
9
    assert_eq!(header(&response, "x-stamina"), "10/10");
10
    let again = send(&app, "JELQ", "/runs/cafebabe?technique=meditate", &[], "").await;
11
    assert_eq!(again.status(), StatusCode::CONFLICT);
12
}
13
14
#[tokio::test]
15
async fn meditation_respects_the_priapism_floor() {
16
    let app = seeded_with(Floor::Bedroom, |run| {
17
        run.body.eq = 80;
18
        run.body.conditions.priapism_turns = 3;
19
        run.online.meditation_taught = true;
20
    });
21
    let calmed = send(&app, "JELQ", "/runs/cafebabe?technique=meditate", &[], "").await;
22
    assert_eq!(calmed.status(), StatusCode::OK);
23
    let view = send(&app, "GET", "/runs/cafebabe", &[], "").await;
24
    assert_eq!(header(&view, "x-eq"), "75");
25
}
26
27
#[tokio::test]
28
async fn priapism_holds_the_floor_through_every_reset() {
29
    let exit = seeded_with(Floor::Bedroom, |run| {
30
        run.body.warmed_up = true;
31
        run.body.eq = 90;
32
        run.here.turns = 99;
33
        run.body.conditions.priapism_turns = 3;
34
    });
35
    let out = send(&exit, "POST", "/runs/cafebabe/floor", &[], "").await;
36
    assert_eq!(out.status(), StatusCode::SEE_OTHER);
37
    let view = send(&exit, "GET", "/runs/cafebabe", &[], "").await;
38
    assert_eq!(header(&view, "x-eq"), "75");
39
40
    let melon = seeded_with(Floor::Bedroom, |run| {
41
        run.body.eq = 95;
42
        run.here.kitchen.melon_offered = true;
43
        run.body.conditions.priapism_turns = 2;
44
    });
45
    let bite = send(&melon, "POST", "/runs/cafebabe/kitchen/watermelon", &[], "").await;
46
    assert_eq!(bite.status(), StatusCode::OK);
47
    let view = send(&melon, "GET", "/runs/cafebabe", &[], "").await;
48
    assert_eq!(header(&view, "x-eq"), "75");
49
}
50
51
#[tokio::test]
52
async fn the_towel_warms_once_until_the_kettle_boils() {
53
    let app = seeded_with(Floor::Bedroom, |_| {});
54
    let warm = send(&app, "JELQ", "/runs/cafebabe?technique=warmup", &[], "").await;
55
    assert_eq!(warm.status(), StatusCode::OK);
56
    let again = send(&app, "JELQ", "/runs/cafebabe?technique=warmup", &[], "").await;
57
    assert_eq!(again.status(), StatusCode::CONFLICT);
58
59
    let app = seeded_with(Floor::Bedroom, |run| {
60
        run.here.towel_warmed = true;
61
        run.here.kitchen.kettle_boiled = true;
62
    });
63
    let reheated = send(&app, "JELQ", "/runs/cafebabe?technique=warmup", &[], "").await;
64
    assert_eq!(reheated.status(), StatusCode::OK);
65
    let cold = send(&app, "JELQ", "/runs/cafebabe?technique=warmup", &[], "").await;
66
    assert_eq!(cold.status(), StatusCode::CONFLICT);
67
}
68
69
#[tokio::test]
70
async fn the_refractory_period_is_the_next_hold() {
71
    let app = seeded_with(Floor::Hotel, |run| {
72
        run.here.wifi_accepted = true;
73
        run.body.warmed_up = true;
74
        run.here.towel_warmed = true;
75
        run.body.eq = 60;
76
        run.body.stamina = 0;
77
        run.body.exhausted = true;
78
    });
79
    let tired = send(&app, "JELQ", "/runs/cafebabe?technique=edge", &[], "").await;
80
    assert_eq!(tired.status(), StatusCode::TOO_MANY_REQUESTS);
81
    let hold = send(&app, "HOLD", "/runs/cafebabe", &[], "").await;
82
    assert_eq!(hold.status(), StatusCode::OK);
83
    assert_eq!(header(&hold, "x-stamina"), "10/10");
84
    let towel = send(&app, "JELQ", "/runs/cafebabe?technique=warmup", &[], "").await;
85
    assert_eq!(towel.status(), StatusCode::CONFLICT, "the towel stays cold");
86
}
87
88
#[tokio::test]
89
async fn a_refused_jelq_does_not_break_the_edging_streak() {
90
    let app = seeded_with(Floor::ChangingRoom, |run| {
91
        run.body.warmed_up = true;
92
        run.body.eq = 45;
93
        run.here.edges = 2;
94
    });
95
    let refused = send(
96
        &app,
97
        "JELQ",
98
        "/runs/cafebabe?technique=dry",
99
        &[("content-type", "application/baby-oil")],
100
        "",
101
    )
102
    .await;
103
    assert_eq!(refused.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE);
104
    let third = send(&app, "JELQ", "/runs/cafebabe?technique=edge", &[], "").await;
105
    assert_eq!(third.status(), StatusCode::LOOP_DETECTED);
106
}
107
108
#[tokio::test]
109
async fn an_interrupted_edge_keeps_no_gains() {
110
    let app = seeded_with(Floor::ChangingRoom, |run| {
111
        run.body.warmed_up = true;
112
        run.body.eq = 10;
113
    });
114
    let before = send(&app, "GET", "/runs/cafebabe", &[], "").await;
115
    let girth = header(&before, "x-girth").to_owned();
116
    let edge = send(&app, "JELQ", "/runs/cafebabe?technique=edge", &[], "").await;
117
    assert_eq!(edge.status(), StatusCode::NOT_FOUND);
118
    assert_eq!(header(&edge, "x-girth"), girth);
119
}
120
121
#[tokio::test]
122
async fn greed_multiplies_gains_above_the_band() {
123
    let lube = [("content-type", "application/baby-oil")];
124
    for (eq, greed) in [(75, 1.5), (88, 2.0)] {
125
        let mut compared = false;
126
        for seed in 0..120u64 {
127
            let app = app_with(Floor::Bedroom, seed, |run| {
128
                run.body.eq = 60;
129
                run.body.warmed_up = true;
130
            });
131
            let in_band = send(&app, "JELQ", "/runs/cafebabe?technique=wet", &lube, "").await;
132
            let app = app_with(Floor::Bedroom, seed, |run| {
133
                run.body.eq = eq;
134
                run.body.warmed_up = true;
135
            });
136
            let greedy = send(&app, "JELQ", "/runs/cafebabe?technique=wet", &lube, "").await;
137
            if in_band.status() != StatusCode::OK || greedy.status() != StatusCode::OK {
138
                continue;
139
            }
140
            let steady = json(in_band).await["length"].as_f64().unwrap();
141
            let eager = json(greedy).await["length"].as_f64().unwrap();
142
            assert!(
143
                (eager - steady * greed).abs() < 1e-9,
144
                "seed {seed} gained {eager} at {eq} EQ against {steady} inside the band"
145
            );
146
            compared = true;
147
            break;
148
        }
149
        assert!(compared, "no seed produced two clean rolls at {eq} EQ");
150
    }
151
}
152
153
#[tokio::test]
154
async fn queued_gains_arrive_on_their_turn() {
155
    let mut run = Run::new("cafebabe".into(), "cafe".into(), SEED);
156
    run.body.eq = 75;
157
    let before = run.body.length;
158
    run.body.pending.push(crate::game::Pending {
159
        due: run.turn + 1,
160
        length: 0.5,
161
        girth: 0.0,
162
        foreskin: 0.0,
163
    });
164
    assert!(run.hold().is_ok());
165
    assert!((run.body.length - before - 0.5).abs() < 1e-9);
166
    assert!(run.body.pending.is_empty());
167
}
168
169
#[tokio::test]
170
async fn hold_keeps_the_shop_and_the_melon_open() {
171
    let hotel = seeded_with(Floor::Hotel, |run| {
172
        run.here.wifi_accepted = true;
173
        run.body.eq = 80;
174
    });
175
    send(&hotel, "GET", "/runs/cafebabe/shop", &[], "").await;
176
    let held = send(&hotel, "HOLD", "/runs/cafebabe", &[], "").await;
177
    assert!(linked_rels(&held).contains(&"shop-item".to_owned()));
178
179
    let mut run = Run::new("cafebabe".into(), "cafe".into(), SEED);
180
    run.body.eq = 80;
181
    run.here.kitchen.melon_offered = true;
182
    assert!(run.hold().is_ok());
183
    assert!(run.here.kitchen.melon_offered, "the offer survives a HOLD");
184
}
185
186
#[tokio::test]
187
async fn leaving_the_band_warms_the_next_floor() {
188
    let app = seeded_with(Floor::Bedroom, |run| {
189
        run.body.eq = 60;
190
        run.body.warmed_up = true;
191
        run.here.turns = 6;
192
        run.items.lubes.baby_oil = 9;
193
    });
194
    let left = send(&app, "POST", "/runs/cafebabe/floor", &[], "").await;
195
    assert_eq!(left.status(), StatusCode::SEE_OTHER);
196
    let detail = json(left).await["detail"].as_str().unwrap().to_owned();
197
    assert!(detail.contains("the warmth came with you"));
198
199
    let lube = [("content-type", "application/baby-oil")];
200
    send(&app, "JELQ", "/runs/cafebabe?technique=warmup", &[], "").await;
201
    // The shower is a +35 warm-up, so EQ lands in the band and the band bonus rides along.
202
    // The arrival bonus is the one point the second jelq loses.
203
    let first = send(&app, "JELQ", "/runs/cafebabe?technique=wet", &lube, "").await;
204
    assert!(
205
        header(&first, "server-timing").contains("d20+4"),
206
        "the first jelq of a warm arrival rolls with +1, got {}",
207
        header(&first, "server-timing")
208
    );
209
    let second = send(&app, "JELQ", "/runs/cafebabe?technique=wet", &lube, "").await;
210
    assert!(header(&second, "server-timing").contains("d20+3"));
211
}
212
213
#[tokio::test]
214
async fn energy_drinks_add_three_stamina() {
215
    let app = seeded_with(Floor::Bedroom, |run| {
216
        run.body.stamina = 2;
217
        run.items.stores.energy_drinks = 1;
218
    });
219
    let view = send(&app, "GET", "/runs/cafebabe", &[], "").await;
220
    assert!(header(&view, "link").contains("technique=energy"));
221
    let drink = send(&app, "JELQ", "/runs/cafebabe?technique=energy", &[], "").await;
222
    assert_eq!(drink.status(), StatusCode::OK);
223
    let view = send(&app, "GET", "/runs/cafebabe", &[], "").await;
224
    assert_eq!(header(&view, "x-stamina"), "5/10");
225
    let again = send(&app, "JELQ", "/runs/cafebabe?technique=energy", &[], "").await;
226
    assert_eq!(again.status(), StatusCode::FAILED_DEPENDENCY);
227
}