1
1
(** Unit tests for {!Hito_core.Recovery}, authored against recovery.mli.
2
2
3
-
Removed:
NOTE: Not yet registered in the main runner; wired in during implementation
4
-
Removed:
(Task 8). recovery.mli is the source of truth over these assertions.
3
+
Added:
Recovery is now just time primitives plus a readiness judgment; sessions,
4
+
Added:
overrides, and evidence-based prescription moved to {!Hito_core.Logbook}
5
+
Added:
(informational gating) and were dropped from this module entirely. *)
5
6
6
-
Removed:
Under test: time-based (systemic) readiness via an injected fake clock, that
7
-
Removed:
an unrecovered state blocks the ordinary start, and that the override path
8
-
Removed:
always succeeds while recording the early-training fact. *)
9
-
Removed:
10
7
open Hito_core
11
8
12
-
Removed:
let ok = function Ok v -> v | Error _ -> Alcotest.fail "expected Ok"
9
+
Added:
let ts = Recovery.timestamp_of_unix_seconds
13
10
14
-
Removed:
let fake_clock at : (module Recovery.CLOCK) =
15
-
Removed:
(module struct
16
-
Removed:
let now () = Recovery.timestamp_of_unix_seconds at
17
-
Removed:
end)
11
+
Added:
let elapsed_tests =
12
+
Added:
[
13
+
Added:
( "elapsed is the gap between two timestamps",
14
+
Added:
`Quick,
15
+
Added:
fun () ->
16
+
Added:
let e = Recovery.elapsed ~since:(ts 1000) ~now:(ts 1900) in
17
+
Added:
Alcotest.(check int) "900s" 900 (Recovery.duration_to_seconds e) );
18
+
Added:
]
18
19
19
20
let readiness_tests =
20
21
[
21
22
( "fully elapsed window is Ready",
22
23
`Quick,
23
24
fun () ->
24
-
Removed:
let now = Recovery.timestamp_of_unix_seconds 1_000_000 in
25
-
Removed:
let last = Recovery.timestamp_of_unix_seconds 0 in
25
+
Added:
let e = Recovery.elapsed ~since:(ts 0) ~now:(ts 1_000_000) in
26
26
let r =
27
-
Removed:
Recovery.evaluate_readiness ~now ~last_workout:last
28
-
Removed:
~recommended:(Recovery.days 4)
27
+
Added:
Recovery.evaluate_readiness ~elapsed:e ~recommended:(Recovery.days 4)
29
28
in
30
29
Alcotest.(check bool) "ready" true (Recovery.is_ready r) );
31
-
Removed:
( "within window is not Ready",
30
+
Added:
( "within the window is not Ready",
32
31
`Quick,
33
32
fun () ->
34
-
Removed:
let now = Recovery.timestamp_of_unix_seconds 3600 in
35
-
Removed:
let last = Recovery.timestamp_of_unix_seconds 0 in
33
+
Added:
let e = Recovery.elapsed ~since:(ts 0) ~now:(ts 3600) in
36
34
let r =
37
-
Removed:
Recovery.evaluate_readiness ~now ~last_workout:last
38
-
Removed:
~recommended:(Recovery.days 4)
35
+
Added:
Recovery.evaluate_readiness ~elapsed:e ~recommended:(Recovery.days 4)
39
36
in
40
37
Alcotest.(check bool) "not ready" false (Recovery.is_ready r) );
41
38
]
42
39
43
-
Removed:
let gating_tests =
44
-
Removed:
[
45
-
Removed:
( "unrecovered blocks ordinary start",
46
-
Removed:
`Quick,
47
-
Removed:
fun () ->
48
-
Removed:
let clock = fake_clock 3600 in
49
-
Removed:
let last = Some (Recovery.timestamp_of_unix_seconds 0) in
50
-
Removed:
let result =
51
-
Removed:
Recovery.start_session clock ~last_workout:last
52
-
Removed:
~recommended:(Recovery.days 4)
53
-
Removed:
in
54
-
Removed:
Alcotest.(check bool) "blocked" true (Result.is_error result) );
55
-
Removed:
( "first workout (no last) is always ready",
56
-
Removed:
`Quick,
57
-
Removed:
fun () ->
58
-
Removed:
let clock = fake_clock 3600 in
59
-
Removed:
let result =
60
-
Removed:
Recovery.start_session clock ~last_workout:None
61
-
Removed:
~recommended:(Recovery.days 4)
62
-
Removed:
in
63
-
Removed:
Alcotest.(check bool) "allowed" true (Result.is_ok result) );
64
-
Removed:
( "override always succeeds and records the reason",
65
-
Removed:
`Quick,
66
-
Removed:
fun () ->
67
-
Removed:
let clock = fake_clock 3600 in
68
-
Removed:
let last = Some (Recovery.timestamp_of_unix_seconds 0) in
69
-
Removed:
let session =
70
-
Removed:
Recovery.start_session_overriding_recovery clock ~last_workout:last
71
-
Removed:
~recommended:(Recovery.days 4) ~acknowledged:"felt strong today"
72
-
Removed:
in
73
-
Removed:
Alcotest.(check bool)
74
-
Removed:
"override recorded" true
75
-
Removed:
(Option.is_some (Recovery.overridden_recovery session)) );
76
-
Removed:
]
77
-
Removed:
78
40
let suite =
79
-
Removed:
[ ("recovery.readiness", readiness_tests); ("recovery.gating", gating_tests) ]
80
-
Removed:
81
-
Removed:
let duration_tests =
82
41
[
83
-
Removed:
( "session_duration is None before ending",
84
-
Removed:
`Quick,
85
-
Removed:
fun () ->
86
-
Removed:
let clock = fake_clock 3600 in
87
-
Removed:
let session =
88
-
Removed:
ok
89
-
Removed:
(Recovery.start_session clock ~last_workout:None
90
-
Removed:
~recommended:(Recovery.days 4))
91
-
Removed:
in
92
-
Removed:
Alcotest.(check bool)
93
-
Removed:
"none" true
94
-
Removed:
(Option.is_none (Recovery.session_duration session)) );
95
-
Removed:
( "session_duration is the gap between start and end",
96
-
Removed:
`Quick,
97
-
Removed:
fun () ->
98
-
Removed:
let started = fake_clock 1000 in
99
-
Removed:
let session =
100
-
Removed:
ok
101
-
Removed:
(Recovery.start_session started ~last_workout:None
102
-
Removed:
~recommended:(Recovery.days 4))
103
-
Removed:
in
104
-
Removed:
let ended = fake_clock 1900 in
105
-
Removed:
let session = Recovery.end_session ended session in
106
-
Removed:
Alcotest.(check (option int))
107
-
Removed:
"900s" (Some 900)
108
-
Removed:
(Option.map Recovery.duration_to_seconds
109
-
Removed:
(Recovery.session_duration session)) );
42
+
Added:
("recovery.elapsed", elapsed_tests); ("recovery.readiness", readiness_tests);
110
43
]
111
-
Removed:
112
-
Removed:
let prescribe_tests =
113
-
Removed:
let sample kg reps : Progression.sample =
114
-
Removed:
{ load = ok (Units.Weight.of_kg kg); reps = ok (Units.Reps.of_int reps) }
115
-
Removed:
in
116
-
Removed:
[
117
-
Removed:
( "progressing keeps the nominal base",
118
-
Removed:
`Quick,
119
-
Removed:
fun () ->
120
-
Removed:
let evidence = [ sample 80.0 6; sample 82.5 6 ] in
121
-
Removed:
let p = Recovery.prescribe ~base:(Recovery.days 4) ~evidence in
122
-
Removed:
Alcotest.(check int)
123
-
Removed:
"base"
124
-
Removed:
(Recovery.duration_to_seconds (Recovery.days 4))
125
-
Removed:
(Recovery.duration_to_seconds p.Progression.value) );
126
-
Removed:
( "a stall lengthens the window",
127
-
Removed:
`Quick,
128
-
Removed:
fun () ->
129
-
Removed:
let evidence = [ sample 80.0 6; sample 80.0 6 ] in
130
-
Removed:
let p = Recovery.prescribe ~base:(Recovery.days 4) ~evidence in
131
-
Removed:
Alcotest.(check bool)
132
-
Removed:
"longer" true
133
-
Removed:
(Recovery.duration_to_seconds p.Progression.value
134
-
Removed:
> Recovery.duration_to_seconds (Recovery.days 4)) );
135
-
Removed:
]
136
-
Removed:
137
-
Removed:
let suite =
138
-
Removed:
suite
139
-
Removed:
@ [
140
-
Removed:
("recovery.duration", duration_tests);
141
-
Removed:
("recovery.prescribe", prescribe_tests);
142
-
Removed:
]