1#![forbid(unsafe_code)]
10#![warn(missing_docs)]
11
12use std::fmt::Debug;
13
14use pacta_contract::{Pact, Registry, Retainer, Timestamp};
15use uuid::Uuid;
16
17pub const LEASE_MILLIS: u64 = 1000;
19
20const DOCKET: &str = "conformance";
21
22fn at(millis: u64) -> Timestamp {
23 Timestamp::from_millis(millis)
24}
25
26fn a_pact_on(docket: &str) -> Pact {
27 Pact::new(
28 Uuid::new_v4(),
29 docket.to_string(),
30 "conformance".to_string(),
31 Vec::new(),
32 )
33}
34
35fn a_pact() -> Pact {
36 a_pact_on(DOCKET)
37}
38
39pub fn run<R, F>(make: F)
45where
46 R: Registry,
47 R::Error: Debug,
48 F: Fn(Vec<Pact>, u64) -> R,
49{
50 no_available_pact_returns_none(&make);
51 unrequested_docket_is_not_claimed(&make);
52 claim_returns_claim_with_lease(&make);
53 held_pact_not_reclaimable_before_expiry(&make);
54 expired_lease_lapses_and_reclaims_with_rotated_retainer(&make);
55 stale_retainer_settle_rejected_after_reclaim(&make);
56 late_fulfill_before_reclaim_succeeds(&make);
57 fulfill_settles_and_pact_not_claimable(&make);
58 breach_settles_terminally(&make);
59 released_pact_withheld_until_reclaimable(&make);
60 released_pact_reclaimable_at_its_instant(&make);
61 immediate_reclaim_reclaims_like_lapse(&make);
62 release_rotates_authority_from_prior_holder(&make);
63 heartbeat_extends_lease_preventing_lapse(&make);
64 heartbeat_on_lapsed_lease_rejected(&make);
65 heartbeat_unknown_retainer_rejected(&make);
66}
67
68fn no_available_pact_returns_none<R, F>(make: &F)
69where
70 R: Registry,
71 R::Error: Debug,
72 F: Fn(Vec<Pact>, u64) -> R,
73{
74 let registry = make(Vec::new(), LEASE_MILLIS);
75 assert!(
76 registry
77 .claim(&[DOCKET], at(0))
78 .expect("claim should not error")
79 .is_none(),
80 "an empty registry must yield no claim"
81 );
82}
83
84fn unrequested_docket_is_not_claimed<R, F>(make: &F)
85where
86 R: Registry,
87 R::Error: Debug,
88 F: Fn(Vec<Pact>, u64) -> R,
89{
90 let registry = make(vec![a_pact_on("other")], LEASE_MILLIS);
91 assert!(
92 registry
93 .claim(&[DOCKET], at(0))
94 .expect("claim should not error")
95 .is_none(),
96 "a pact on an unrequested docket must not be claimed"
97 );
98 assert!(
99 registry
100 .claim(&["other"], at(0))
101 .expect("claim should not error")
102 .is_some(),
103 "the same pact must be claimable from its own docket"
104 );
105}
106
107fn claim_returns_claim_with_lease<R, F>(make: &F)
108where
109 R: Registry,
110 R::Error: Debug,
111 F: Fn(Vec<Pact>, u64) -> R,
112{
113 let registry = make(vec![a_pact()], LEASE_MILLIS);
114 let claim = registry
115 .claim(&[DOCKET], at(100))
116 .expect("claim should not error")
117 .expect("a pact should be claimable");
118 assert_eq!(
119 claim.lease_expiry,
120 at(100 + LEASE_MILLIS),
121 "lease expiry must be now plus the lease duration"
122 );
123}
124
125fn held_pact_not_reclaimable_before_expiry<R, F>(make: &F)
126where
127 R: Registry,
128 R::Error: Debug,
129 F: Fn(Vec<Pact>, u64) -> R,
130{
131 let registry = make(vec![a_pact()], LEASE_MILLIS);
132 let _first = registry
133 .claim(&[DOCKET], at(0))
134 .expect("claim should not error")
135 .expect("a pact should be claimable");
136 assert!(
137 registry
138 .claim(&[DOCKET], at(500))
139 .expect("claim should not error")
140 .is_none(),
141 "a held pact must not be reclaimable before its lease expires"
142 );
143}
144
145fn expired_lease_lapses_and_reclaims_with_rotated_retainer<R, F>(make: &F)
146where
147 R: Registry,
148 R::Error: Debug,
149 F: Fn(Vec<Pact>, u64) -> R,
150{
151 let registry = make(vec![a_pact()], LEASE_MILLIS);
152 let first = registry
153 .claim(&[DOCKET], at(0))
154 .expect("claim should not error")
155 .expect("a pact should be claimable");
156 let second = registry
157 .claim(&[DOCKET], at(1500))
158 .expect("claim should not error")
159 .expect("an expired pact should be reclaimable through the claim path");
160 assert_ne!(
161 first.retainer.id(),
162 second.retainer.id(),
163 "reclaiming a lapsed pact must rotate the retainer"
164 );
165 assert_eq!(
166 second.lease_expiry,
167 at(1500 + LEASE_MILLIS),
168 "the reclaim must set a fresh lease"
169 );
170}
171
172fn stale_retainer_settle_rejected_after_reclaim<R, F>(make: &F)
173where
174 R: Registry,
175 R::Error: Debug,
176 F: Fn(Vec<Pact>, u64) -> R,
177{
178 let registry = make(vec![a_pact()], LEASE_MILLIS);
179 let first = registry
180 .claim(&[DOCKET], at(0))
181 .expect("claim should not error")
182 .expect("a pact should be claimable");
183 let _second = registry
184 .claim(&[DOCKET], at(1500))
185 .expect("claim should not error")
186 .expect("an expired pact should be reclaimable");
187 assert!(
188 registry.fulfill(&first.retainer).is_err(),
189 "the prior holder must not settle after a reclaim (at-least-once safety)"
190 );
191}
192
193fn late_fulfill_before_reclaim_succeeds<R, F>(make: &F)
194where
195 R: Registry,
196 R::Error: Debug,
197 F: Fn(Vec<Pact>, u64) -> R,
198{
199 let registry = make(vec![a_pact()], LEASE_MILLIS);
200 let claim = registry
201 .claim(&[DOCKET], at(0))
202 .expect("claim should not error")
203 .expect("a pact should be claimable");
204 assert!(
207 registry.fulfill(&claim.retainer).is_ok(),
208 "a late fulfill before any reclaim must settle"
209 );
210 assert!(
211 registry
212 .claim(&[DOCKET], at(9999))
213 .expect("claim should not error")
214 .is_none(),
215 "a settled pact must not be claimable"
216 );
217}
218
219fn fulfill_settles_and_pact_not_claimable<R, F>(make: &F)
220where
221 R: Registry,
222 R::Error: Debug,
223 F: Fn(Vec<Pact>, u64) -> R,
224{
225 let registry = make(vec![a_pact()], LEASE_MILLIS);
226 let claim = registry
227 .claim(&[DOCKET], at(0))
228 .expect("claim should not error")
229 .expect("a pact should be claimable");
230 registry
231 .fulfill(&claim.retainer)
232 .expect("fulfill should settle");
233 assert!(
234 registry
235 .claim(&[DOCKET], at(0))
236 .expect("claim should not error")
237 .is_none(),
238 "a fulfilled pact must not be claimable"
239 );
240}
241
242fn breach_settles_terminally<R, F>(make: &F)
243where
244 R: Registry,
245 R::Error: Debug,
246 F: Fn(Vec<Pact>, u64) -> R,
247{
248 let registry = make(vec![a_pact()], LEASE_MILLIS);
249 let claim = registry
250 .claim(&[DOCKET], at(0))
251 .expect("claim should not error")
252 .expect("a pact should be claimable");
253 registry
254 .breach(&claim.retainer)
255 .expect("breach should settle");
256 assert!(
257 registry
258 .claim(&[DOCKET], at(5000))
259 .expect("claim should not error")
260 .is_none(),
261 "a breached pact must not be claimable, even after its lease would have expired"
262 );
263}
264
265fn released_pact_withheld_until_reclaimable<R, F>(make: &F)
266where
267 R: Registry,
268 R::Error: Debug,
269 F: Fn(Vec<Pact>, u64) -> R,
270{
271 let registry = make(vec![a_pact()], LEASE_MILLIS);
272 let claim = registry
273 .claim(&[DOCKET], at(0))
274 .expect("claim should not error")
275 .expect("a pact should be claimable");
276 registry
277 .release(&claim.retainer, at(5000))
278 .expect("release should succeed for the current holder");
279 assert!(
282 registry
283 .claim(&[DOCKET], at(3000))
284 .expect("claim should not error")
285 .is_none(),
286 "a released pact must not be claimable before its reclaimable instant"
287 );
288}
289
290fn released_pact_reclaimable_at_its_instant<R, F>(make: &F)
291where
292 R: Registry,
293 R::Error: Debug,
294 F: Fn(Vec<Pact>, u64) -> R,
295{
296 let registry = make(vec![a_pact()], LEASE_MILLIS);
297 let first = registry
298 .claim(&[DOCKET], at(0))
299 .expect("claim should not error")
300 .expect("a pact should be claimable");
301 registry
302 .release(&first.retainer, at(5000))
303 .expect("release should succeed");
304 let second = registry
305 .claim(&[DOCKET], at(5000))
306 .expect("claim should not error")
307 .expect("a released pact must be claimable at its reclaimable instant");
308 assert_ne!(
309 first.retainer.id(),
310 second.retainer.id(),
311 "reclaiming a released pact must rotate the retainer"
312 );
313}
314
315fn immediate_reclaim_reclaims_like_lapse<R, F>(make: &F)
316where
317 R: Registry,
318 R::Error: Debug,
319 F: Fn(Vec<Pact>, u64) -> R,
320{
321 let registry = make(vec![a_pact()], LEASE_MILLIS);
322 let claim = registry
323 .claim(&[DOCKET], at(0))
324 .expect("claim should not error")
325 .expect("a pact should be claimable");
326 registry
327 .release(&claim.retainer, at(0))
328 .expect("release with an immediate reclaim should succeed");
329 assert!(
330 registry
331 .claim(&[DOCKET], at(0))
332 .expect("claim should not error")
333 .is_some(),
334 "an immediate reclaim must make the pact claimable at once, as a voluntary lapse"
335 );
336}
337
338fn release_rotates_authority_from_prior_holder<R, F>(make: &F)
339where
340 R: Registry,
341 R::Error: Debug,
342 F: Fn(Vec<Pact>, u64) -> R,
343{
344 let registry = make(vec![a_pact()], LEASE_MILLIS);
345 let claim = registry
346 .claim(&[DOCKET], at(0))
347 .expect("claim should not error")
348 .expect("a pact should be claimable");
349 registry
350 .release(&claim.retainer, at(0))
351 .expect("release should succeed");
352 assert!(
353 registry.fulfill(&claim.retainer).is_err(),
354 "the prior holder must not settle after releasing (release rotates authority)"
355 );
356}
357
358fn heartbeat_extends_lease_preventing_lapse<R, F>(make: &F)
359where
360 R: Registry,
361 R::Error: Debug,
362 F: Fn(Vec<Pact>, u64) -> R,
363{
364 let registry = make(vec![a_pact()], LEASE_MILLIS);
365 let claim = registry
366 .claim(&[DOCKET], at(0))
367 .expect("claim should not error")
368 .expect("a pact should be claimable");
369 registry
370 .heartbeat(&claim.retainer, at(800))
371 .expect("an in-window heartbeat should extend the lease");
372 assert!(
375 registry
376 .claim(&[DOCKET], at(1500))
377 .expect("claim should not error")
378 .is_none(),
379 "a heartbeat within the window must prevent a lapse"
380 );
381}
382
383fn heartbeat_on_lapsed_lease_rejected<R, F>(make: &F)
384where
385 R: Registry,
386 R::Error: Debug,
387 F: Fn(Vec<Pact>, u64) -> R,
388{
389 let registry = make(vec![a_pact()], LEASE_MILLIS);
390 let claim = registry
391 .claim(&[DOCKET], at(0))
392 .expect("claim should not error")
393 .expect("a pact should be claimable");
394 assert!(
395 registry.heartbeat(&claim.retainer, at(1200)).is_err(),
396 "a heartbeat after the lease expired must be rejected, forcing a re-claim"
397 );
398}
399
400fn heartbeat_unknown_retainer_rejected<R, F>(make: &F)
401where
402 R: Registry,
403 R::Error: Debug,
404 F: Fn(Vec<Pact>, u64) -> R,
405{
406 let registry = make(vec![a_pact()], LEASE_MILLIS);
407 let _claim = registry
408 .claim(&[DOCKET], at(0))
409 .expect("claim should not error")
410 .expect("a pact should be claimable");
411 let unknown = Retainer::new(Uuid::new_v4());
412 assert!(
413 registry.heartbeat(&unknown, at(100)).is_err(),
414 "a heartbeat with an unissued retainer must be rejected"
415 );
416}