[OCaml] High Intensity Training Online
1
open Hito_app
2
open Support
3
4
let suite =
5
[
6
( "web.workout",
7
[
8
( "a signed-in trainee selects a routine and begins a workout",
9
`Quick,
10
fun () ->
11
let c, now, repo = clocked_client () in
12
now := day 10;
13
let _ = register c ~username:"lifter" ~password:"heavyduty1" in
14
let trainee = the_trainee repo ~username:"lifter" in
15
let token = Option.get (csrf_token (body (get c "/"))) in
16
let selection =
17
post c "/routines/ideal/select" [ ("dream.csrf", token) ]
18
in
19
Alcotest.(check int) "routine selected" 303 (status selection);
20
let token = Option.get (csrf_token (body (get c "/"))) in
21
let started =
22
post c "/workout" [ ("dream.csrf", token); ("override", "false") ]
23
in
24
Alcotest.(check int) "workout started" 303 (status started);
25
(* The workout is now in progress in the domain, and its view is
26
served. *)
27
Alcotest.(check bool)
28
"a workout is in progress" true
29
(Option.is_some
30
(Lwt_main.run (Test_repo.in_progress repo trainee.Trainee.id)));
31
Alcotest.(check int)
32
"the workout view is served" 200
33
(status (get c "/workout"));
34
let workout_page = body (get c "/workout") in
35
Alcotest.(check bool)
36
"the workout date is shown" true
37
(Option.is_some
38
(index_from ~needle:"11th January 1970" workout_page 0));
39
(* The static workout client asset is served. *)
40
let asset = get c "/assets/workout-client.js" in
41
Alcotest.(check int) "serves the workout client" 200 (status asset)
42
);
43
( "self-hosted fonts are served as woff2, and an unknown name is 404",
44
`Quick,
45
fun () ->
46
(* The fonts are embedded and served from the app's own origin, so a
47
visitor never reaches a third party. Each known face returns 200
48
with the woff2 media type. An unknown or crafted name returns a
49
plain 404 rather than reaching the filesystem. No sign-in is
50
needed: fonts are public assets. *)
51
let c = client () in
52
let faces =
53
[
54
"work-sans-latin-var.woff2";
55
"courier-prime-400-latin.woff2";
56
"courier-prime-700-latin.woff2";
57
]
58
in
59
List.iter
60
(fun face ->
61
let response = get c ("/assets/fonts/" ^ face) in
62
Alcotest.(check int)
63
(Printf.sprintf "%s is served" face)
64
200 (status response);
65
Alcotest.(check (option string))
66
(Printf.sprintf "%s is woff2" face)
67
(Some "font/woff2")
68
(Dream.header response "Content-Type");
69
(* The body is the real font: a non-empty payload whose first
70
four bytes are the WOFF2 signature. *)
71
let bytes = body response in
72
Alcotest.(check bool)
73
(Printf.sprintf "%s has a woff2 signature" face)
74
true
75
(String.length bytes >= 4 && String.sub bytes 0 4 = "wOF2"))
76
faces;
77
let missing = get c "/assets/fonts/does-not-exist.woff2" in
78
Alcotest.(check int)
79
"an unknown font is not found" 404 (status missing) );
80
( "the favicon is served as an SVG",
81
`Quick,
82
fun () ->
83
let c = client () in
84
let response = get c "/assets/favicon.svg" in
85
Alcotest.(check int) "serves the favicon" 200 (status response);
86
Alcotest.(check (option string))
87
"declares SVG content" (Some "image/svg+xml; charset=utf-8")
88
(Dream.header response "Content-Type") );
89
( "beginning before recovery is refused without an override",
90
`Quick,
91
fun () ->
92
let c, repo = client_with_repo () in
93
let _ = register c ~username:"lifter" ~password:"heavyduty1" in
94
let trainee = the_trainee repo ~username:"lifter" in
95
let token = Option.get (csrf_token (body (get c "/"))) in
96
let _ = post c "/routines/ideal/select" [ ("dream.csrf", token) ] in
97
let token = Option.get (csrf_token (body (get c "/"))) in
98
let _ =
99
post c "/workout" [ ("dream.csrf", token); ("override", "false") ]
100
in
101
let token = Option.get (csrf_token (body (get c "/workout"))) in
102
let _ = post c "/workout/finish" [ ("dream.csrf", token) ] in
103
(* Recovery has not elapsed. Beginning the next workout without an
104
override is bounced back to Home and starts nothing. *)
105
let token = Option.get (csrf_token (body (get c "/"))) in
106
let refused =
107
post c "/workout" [ ("dream.csrf", token); ("override", "false") ]
108
in
109
Alcotest.(check int) "early begin bounced" 303 (status refused);
110
Alcotest.(check bool)
111
"returns Home, not the workout" true
112
(List.mem "/" (Dream.headers refused "Location"));
113
Alcotest.(check bool)
114
"no new workout in progress" true
115
(Option.is_none
116
(Lwt_main.run (Test_repo.in_progress repo trainee.Trainee.id)))
117
);
118
( "overriding recovery begins the next workout on an override basis",
119
`Quick,
120
fun () ->
121
let c, repo = client_with_repo () in
122
let _ = register c ~username:"lifter" ~password:"heavyduty1" in
123
let trainee = the_trainee repo ~username:"lifter" in
124
let token = Option.get (csrf_token (body (get c "/"))) in
125
let _ = post c "/routines/ideal/select" [ ("dream.csrf", token) ] in
126
let token = Option.get (csrf_token (body (get c "/"))) in
127
let _ =
128
post c "/workout" [ ("dream.csrf", token); ("override", "false") ]
129
in
130
let token = Option.get (csrf_token (body (get c "/workout"))) in
131
let _ = post c "/workout/finish" [ ("dream.csrf", token) ] in
132
(* Recovery has not elapsed, so the next workout is begun under an
133
explicit override. The deviation is recorded on the workout's
134
clearance basis, never silent. *)
135
let token = Option.get (csrf_token (body (get c "/"))) in
136
let began =
137
post c "/workout" [ ("dream.csrf", token); ("override", "true") ]
138
in
139
Alcotest.(check int) "override begin succeeds" 303 (status began);
140
let basis =
141
match
142
Lwt_main.run (Test_repo.in_progress repo trainee.Trainee.id)
143
with
144
| Some w -> Recovery.basis (Evidence.Workout.clearance w)
145
| None -> Alcotest.failf "expected a workout in progress"
146
in
147
Alcotest.(check bool)
148
"the workout carries an override basis" true
149
(match basis with Recovery.Overridden _ -> true | _ -> false) );
150
( "recording a slot advances the count of filled slots",
151
`Quick,
152
fun () ->
153
let c, repo = client_with_repo () in
154
let _ = register c ~username:"lifter" ~password:"heavyduty1" in
155
let trainee = the_trainee repo ~username:"lifter" in
156
let token = Option.get (csrf_token (body (get c "/"))) in
157
let _ = post c "/routines/ideal/select" [ ("dream.csrf", token) ] in
158
let token = Option.get (csrf_token (body (get c "/"))) in
159
let _ =
160
post c "/workout" [ ("dream.csrf", token); ("override", "false") ]
161
in
162
let filled () =
163
match
164
Lwt_main.run (Test_repo.in_progress repo trainee.Trainee.id)
165
with
166
| Some w -> Evidence.Workout.filled_slots w
167
| None -> Alcotest.failf "expected a workout in progress"
168
in
169
Alcotest.(check int) "nothing recorded yet" 0 (filled ());
170
let token = Option.get (csrf_token (body (get c "/workout"))) in
171
let recorded =
172
post c "/workout/slots/0"
173
[
174
("dream.csrf", token);
175
("iso_load", "10");
176
("iso_reps", "8");
177
("comp_load", "40");
178
("comp_reps", "6");
179
("extension", "");
180
]
181
in
182
Alcotest.(check int) "recorded slot 0" 303 (status recorded);
183
Alcotest.(check int) "one slot filled" 1 (filled ()) );
184
( "a recorded slot can be corrected, replacing it rather than adding \
185
volume",
186
`Quick,
187
fun () ->
188
let c, repo = client_with_repo () in
189
let _ = register c ~username:"lifter" ~password:"heavyduty1" in
190
let trainee = the_trainee repo ~username:"lifter" in
191
let token = Option.get (csrf_token (body (get c "/"))) in
192
let _ = post c "/routines/ideal/select" [ ("dream.csrf", token) ] in
193
let token = Option.get (csrf_token (body (get c "/"))) in
194
let _ =
195
post c "/workout" [ ("dream.csrf", token); ("override", "false") ]
196
in
197
(* Slot 1 is the single Laterals set. Record it, then correct it —
198
one slot at a time, addressed by [?slot=]. *)
199
let token =
200
Option.get (csrf_token (body (get c "/workout?slot=1")))
201
in
202
let recorded =
203
post c "/workout/slots/1"
204
[
205
("dream.csrf", token);
206
("load", "12");
207
("reps", "8");
208
("extension", "");
209
]
210
in
211
Alcotest.(check int) "recorded" 303 (status recorded);
212
let filled_after_record () =
213
match
214
Lwt_main.run (Test_repo.in_progress repo trainee.Trainee.id)
215
with
216
| Some w -> Evidence.Workout.filled_slots w
217
| None -> Alcotest.failf "expected a workout in progress"
218
in
219
let slot1_effort () =
220
match
221
Lwt_main.run (Test_repo.in_progress repo trainee.Trainee.id)
222
with
223
| Some w -> (
224
match List.assoc_opt 1 (Evidence.Workout.performed w) with
225
| Some s -> List.hd (Evidence.Stimulus.efforts s)
226
| None -> Alcotest.failf "expected slot 1 to be filled")
227
| None -> Alcotest.failf "expected a workout in progress"
228
in
229
Alcotest.(check int) "one filled slot" 1 (filled_after_record ());
230
let edit_page = body (get c "/workout?slot=1") in
231
let before_edit = slot1_effort () in
232
Alcotest.(check (float 0.001))
233
"opening correction keeps the saved load" 12.
234
(Evidence.Stimulus.Effort.load before_edit);
235
Alcotest.(check int)
236
"opening correction keeps the saved reps" 8
237
(Evidence.Stimulus.Effort.reps before_edit);
238
let token = Option.get (csrf_token edit_page) in
239
let corrected =
240
post c "/workout/slots/1/edit"
241
[
242
("dream.csrf", token);
243
("load", "16");
244
("reps", "6");
245
("extension", "");
246
]
247
in
248
Alcotest.(check int) "corrected" 303 (status corrected);
249
(* A correction replaces the slot; it does not add volume. *)
250
Alcotest.(check int)
251
"still one filled slot, not two" 1 (filled_after_record ());
252
(* The stored slot now holds the corrected values, not the originals. *)
253
let effort = slot1_effort () in
254
Alcotest.(check (float 0.001))
255
"load updated to the corrected value" 16.
256
(Evidence.Stimulus.Effort.load effort);
257
Alcotest.(check int)
258
"reps updated to the corrected value" 6
259
(Evidence.Stimulus.Effort.reps effort) );
260
( "an invalid workout field is rejected and records nothing",
261
`Quick,
262
fun () ->
263
let c, repo = client_with_repo () in
264
let _ = register c ~username:"lifter" ~password:"heavyduty1" in
265
let trainee = the_trainee repo ~username:"lifter" in
266
let token = Option.get (csrf_token (body (get c "/"))) in
267
let _ = post c "/routines/ideal/select" [ ("dream.csrf", token) ] in
268
let token = Option.get (csrf_token (body (get c "/"))) in
269
let _ =
270
post c "/workout" [ ("dream.csrf", token); ("override", "false") ]
271
in
272
let token = Option.get (csrf_token (body (get c "/workout"))) in
273
let invalid =
274
post c "/workout/slots/0"
275
[
276
("dream.csrf", token);
277
("iso_load", "not-a-number");
278
("iso_reps", "8");
279
("comp_load", "40");
280
("comp_reps", "6");
281
("extension", "");
282
]
283
in
284
Alcotest.(check int) "invalid field redirects" 303 (status invalid);
285
(* The bad input recorded no slot. *)
286
Alcotest.(check int)
287
"no slot was filled" 0
288
(match
289
Lwt_main.run (Test_repo.in_progress repo trainee.Trainee.id)
290
with
291
| Some w -> Evidence.Workout.filled_slots w
292
| None -> Alcotest.failf "expected a workout in progress") );
293
( "posting to a slot beyond the workout is not found",
294
`Quick,
295
fun () ->
296
(* Day 1 has four slots (0..3). Slot 9 does not exist, so the log
297
handler answers 404 rather than recording phantom volume. *)
298
let c, repo = client_with_repo () in
299
let _ = register c ~username:"lifter" ~password:"heavyduty1" in
300
let trainee = the_trainee repo ~username:"lifter" in
301
let _ = start_day_one c in
302
let token = Option.get (csrf_token (body (get c "/workout"))) in
303
let response =
304
post c "/workout/slots/9"
305
[
306
("dream.csrf", token);
307
("load", "10");
308
("reps", "8");
309
("extension", "");
310
]
311
in
312
Alcotest.(check int)
313
"out-of-range slot is not found" 404 (status response);
314
(* No phantom volume was recorded. *)
315
Alcotest.(check int)
316
"nothing recorded" 0
317
(match
318
Lwt_main.run (Test_repo.in_progress repo trainee.Trainee.id)
319
with
320
| Some w -> Evidence.Workout.filled_slots w
321
| None -> Alcotest.failf "expected a workout in progress") );
322
( "editing a slot beyond the workout is not found",
323
`Quick,
324
fun () ->
325
let c = client () in
326
let _ = sign_in_new c in
327
let _ = start_day_one c in
328
let token = Option.get (csrf_token (body (get c "/workout"))) in
329
let response =
330
post c "/workout/slots/9/edit"
331
[
332
("dream.csrf", token);
333
("load", "10");
334
("reps", "8");
335
("extension", "");
336
]
337
in
338
Alcotest.(check int)
339
"out-of-range edit is not found" 404 (status response) );
340
( "logging with no workout in progress is not found",
341
`Quick,
342
fun () ->
343
let c = client () in
344
let _ = sign_in_new c in
345
(* No routine selected, no workout begun: the CSRF token comes from
346
Home, but the slot route has no workout to record against. *)
347
let token = Option.get (csrf_token (body (get c "/"))) in
348
let response =
349
post c "/workout/slots/0"
350
[
351
("dream.csrf", token);
352
("load", "10");
353
("reps", "8");
354
("extension", "");
355
]
356
in
357
Alcotest.(check int) "no workout is not found" 404 (status response)
358
);
359
( "cancelling a workout without a CSRF token leaves it in progress",
360
`Quick,
361
fun () ->
362
let c, repo = client_with_repo () in
363
let _ = register c ~username:"lifter" ~password:"heavyduty1" in
364
let trainee = the_trainee repo ~username:"lifter" in
365
let token = Option.get (csrf_token (body (get c "/"))) in
366
let _ = post c "/routines/ideal/select" [ ("dream.csrf", token) ] in
367
let token = Option.get (csrf_token (body (get c "/"))) in
368
let _ =
369
post c "/workout" [ ("dream.csrf", token); ("override", "false") ]
370
in
371
let response = post c "/workout/cancel" [ (* no dream.csrf *) ] in
372
(* Cancelling always navigates away to Home. A forged or missing
373
token skips the discard, so the workout remains in progress. *)
374
Alcotest.(check int)
375
"navigates away without an error page" 303 (status response);
376
Alcotest.(check bool)
377
"returns Home" true
378
(List.mem "/" (Dream.headers response "Location"));
379
Alcotest.(check bool)
380
"workout still in progress" true
381
(Option.is_some
382
(Lwt_main.run (Test_repo.in_progress repo trainee.Trainee.id)))
383
);
384
( "cancelling after recording a set discards the workout entirely",
385
`Quick,
386
fun () ->
387
let c, repo = client_with_repo () in
388
let _ = register c ~username:"lifter" ~password:"heavyduty1" in
389
let trainee = the_trainee repo ~username:"lifter" in
390
let token = Option.get (csrf_token (body (get c "/"))) in
391
let _ = post c "/routines/ideal/select" [ ("dream.csrf", token) ] in
392
let token = Option.get (csrf_token (body (get c "/"))) in
393
let _ =
394
post c "/workout" [ ("dream.csrf", token); ("override", "false") ]
395
in
396
let token =
397
Option.get (csrf_token (body (get c "/workout?slot=1")))
398
in
399
let recorded =
400
post c "/workout/slots/1"
401
[
402
("dream.csrf", token);
403
("load", "12");
404
("reps", "8");
405
("extension", "");
406
]
407
in
408
Alcotest.(check int) "recorded a set" 303 (status recorded);
409
let token = Option.get (csrf_token (body (get c "/workout"))) in
410
let cancelled =
411
post c "/workout/cancel" [ ("dream.csrf", token) ]
412
in
413
Alcotest.(check int) "cancel returns Home" 303 (status cancelled);
414
Alcotest.(check bool)
415
"returns Home, not the logbook" true
416
(List.mem "/" (Dream.headers cancelled "Location"));
417
(* No current workout, and no record: an abandoned session is not
418
evidence. *)
419
Alcotest.(check bool)
420
"no workout in progress" true
421
(Option.is_none
422
(Lwt_main.run (Test_repo.in_progress repo trainee.Trainee.id)));
423
Alcotest.(check int)
424
"history is empty" 0
425
(List.length
426
(Lwt_main.run (Test_repo.history repo trainee.Trainee.id)));
427
Alcotest.(check int)
428
"the discarded workout has no record" 404
429
(status (get c "/logbook/t1:w1")) );
430
( "finishing a workout redirects to the logbook feedback prompt",
431
`Quick,
432
fun () ->
433
let c, repo = client_with_repo () in
434
let _ = register c ~username:"lifter" ~password:"heavyduty1" in
435
let trainee = the_trainee repo ~username:"lifter" in
436
let token = Option.get (csrf_token (body (get c "/"))) in
437
let _ = post c "/routines/ideal/select" [ ("dream.csrf", token) ] in
438
let token = Option.get (csrf_token (body (get c "/"))) in
439
let _ =
440
post c "/workout" [ ("dream.csrf", token); ("override", "false") ]
441
in
442
let token = Option.get (csrf_token (body (get c "/workout"))) in
443
let finished = post c "/workout/finish" [ ("dream.csrf", token) ] in
444
Alcotest.(check int) "finish redirects" 303 (status finished);
445
Alcotest.(check bool)
446
"redirects to the logbook with a feedback prompt" true
447
(List.mem "/logbook?prompt=feedback"
448
(Dream.headers finished "Location"));
449
(* The workout is now in history and no longer in progress. *)
450
Alcotest.(check int)
451
"one workout in history" 1
452
(List.length
453
(Lwt_main.run (Test_repo.history repo trainee.Trainee.id)));
454
Alcotest.(check bool)
455
"nothing in progress" true
456
(Option.is_none
457
(Lwt_main.run (Test_repo.in_progress repo trainee.Trainee.id)))
458
);
459
( "a full cycle: begin Day 1, record every slot, finish, and see it \
460
saved complete",
461
`Quick,
462
fun () ->
463
(* A deep end-to-end pass over the primary flow with a driven clock.
464
Begin Day 1, record all four slots, finish, and confirm the
465
logbook persists a complete Day 1 with four stimuli. The clocked
466
client and repo share a store, so the flow's domain effect is
467
read straight from history. *)
468
let c, now, repo = clocked_client () in
469
now := day 10;
470
let _ = register c ~username:"lifter" ~password:"heavyduty1" in
471
let trainee = the_trainee repo ~username:"lifter" in
472
let _ = start_day_one c in
473
let in_progress_filled () =
474
match
475
Lwt_main.run (Test_repo.in_progress repo trainee.Trainee.id)
476
with
477
| Some w -> Evidence.Workout.filled_slots w
478
| None -> Alcotest.failf "expected a workout in progress"
479
in
480
Alcotest.(check int)
481
"opens with no slots recorded" 0 (in_progress_filled ());
482
record_all_day_one_slots c;
483
Alcotest.(check int)
484
"all four slots recorded" 4 (in_progress_filled ());
485
let token = Option.get (csrf_token (body (get c "/workout"))) in
486
let finished = post c "/workout/finish" [ ("dream.csrf", token) ] in
487
Alcotest.(check int) "finish redirects" 303 (status finished);
488
Alcotest.(check bool)
489
"sends the trainee to the logbook feedback prompt" true
490
(List.mem "/logbook?prompt=feedback"
491
(Dream.headers finished "Location"));
492
(* History holds one complete Day 1 with four stimuli. *)
493
let history =
494
Lwt_main.run (Test_repo.history repo trainee.Trainee.id)
495
in
496
Alcotest.(check int)
497
"one workout in history" 1 (List.length history);
498
let saved = (List.hd history).Repository.workout in
499
Alcotest.(check string)
500
"the saved workout is Day 1" "Day 1"
501
(Prescription.Workout.name (Evidence.Workout.prescription saved));
502
Alcotest.(check int)
503
"records four stimuli" 4
504
(List.length (Evidence.Workout.stimuli saved));
505
Alcotest.(check bool)
506
"the saved workout is complete" true
507
(Evidence.Workout.completeness saved = Evidence.Workout.Complete);
508
(* The saved workout is durable and reachable at its own URL. *)
509
Alcotest.(check int)
510
"the saved workout is reachable" 200
511
(status (get c "/logbook/t1:w1")) );
512
( "the recovery gate blocks the next workout until enough rest, then \
513
Day 2 begins",
514
`Quick,
515
fun () ->
516
(* The deepest flow: after finishing Day 1, the next workout is
517
gated until 48 hours of recovery pass. The web tier drives the
518
clock, so this proves the gate over HTTP, not just in the
519
service. Beginning too early is refused; resting past the
520
interval clears the gate and advances the cycle to Day 2. *)
521
let c, now, repo = clocked_client () in
522
now := day 10;
523
let _ = register c ~username:"lifter" ~password:"heavyduty1" in
524
let trainee = the_trainee repo ~username:"lifter" in
525
let _ = start_day_one c in
526
record_all_day_one_slots c;
527
let token = Option.get (csrf_token (body (get c "/workout"))) in
528
let _ = post c "/workout/finish" [ ("dream.csrf", token) ] in
529
(* One hour later: recovery is far from complete. Beginning without
530
an override is refused and starts nothing. *)
531
now := day 10 + 3600;
532
let token = Option.get (csrf_token (body (get c "/"))) in
533
let refused =
534
post c "/workout" [ ("dream.csrf", token); ("override", "false") ]
535
in
536
Alcotest.(check int)
537
"an early begin is bounced back" 303 (status refused);
538
Alcotest.(check bool)
539
"the early begin returns Home, not the workout" true
540
(List.mem "/" (Dream.headers refused "Location"));
541
Alcotest.(check bool)
542
"no workout was started" true
543
(Option.is_none
544
(Lwt_main.run (Test_repo.in_progress repo trainee.Trainee.id)));
545
(* Rest two full days: past the 48-hour training interval. The gate
546
clears and the next begin succeeds. *)
547
now := day 13;
548
let token = Option.get (csrf_token (body (get c "/"))) in
549
let began =
550
post c "/workout" [ ("dream.csrf", token); ("override", "false") ]
551
in
552
Alcotest.(check int) "the rested begin succeeds" 303 (status began);
553
Alcotest.(check bool)
554
"the rested begin reaches the workout" true
555
(List.mem "/workout" (Dream.headers began "Location"));
556
(* The cycle has advanced to Day 2, cleared on recovery, not an
557
override. *)
558
let workout =
559
match
560
Lwt_main.run (Test_repo.in_progress repo trainee.Trainee.id)
561
with
562
| Some w -> w
563
| None -> Alcotest.failf "expected a workout in progress"
564
in
565
Alcotest.(check string)
566
"the cycle advanced to Day 2" "Day 2"
567
(Prescription.Workout.name
568
(Evidence.Workout.prescription workout));
569
Alcotest.(check bool)
570
"cleared on recovery, not an override" true
571
(match Recovery.basis (Evidence.Workout.clearance workout) with
572
| Recovery.Recovered -> true
573
| Recovery.Overridden _ -> false) );
574
( "completed workouts remain available when the routine cycle returns",
575
`Quick,
576
fun () ->
577
let c, now, repo = clocked_client () in
578
now := day 10;
579
let _ = register c ~username:"lifter" ~password:"heavyduty1" in
580
let trainee = the_trainee repo ~username:"lifter" in
581
let _ = start_day_one c in
582
record_all_day_one_slots c;
583
let token = Option.get (csrf_token (body (get c "/workout"))) in
584
let _ = post c "/workout/finish" [ ("dream.csrf", token) ] in
585
let rec reach_day_one attempts d =
586
if attempts = 0 then Alcotest.failf "Day 1 did not recur"
587
else begin
588
now := day d;
589
let token = Option.get (csrf_token (body (get c "/"))) in
590
let _ =
591
post c "/workout"
592
[ ("dream.csrf", token); ("override", "false") ]
593
in
594
match
595
Lwt_main.run (Test_repo.in_progress repo trainee.Trainee.id)
596
with
597
| Some workout
598
when String.equal
599
(Prescription.Workout.name
600
(Evidence.Workout.prescription workout))
601
"Day 1" ->
602
()
603
| Some _ ->
604
record_all_day_one_slots c;
605
let token =
606
Option.get (csrf_token (body (get c "/workout")))
607
in
608
let _ =
609
post c "/workout/finish" [ ("dream.csrf", token) ]
610
in
611
reach_day_one (attempts - 1) (d + 3)
612
| None -> Alcotest.failf "expected a workout in progress"
613
end
614
in
615
reach_day_one 6 13;
616
Alcotest.(check bool)
617
"the original completed workout remains in history" true
618
(List.length
619
(Lwt_main.run (Test_repo.history repo trainee.Trainee.id))
620
>= 1) );
621
] );
622
]
623