Skip to main content

persistant_relational/
contract.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! Stage 1 of backend benchmarking for the relational kind: **eligibility**, pass or fail, nothing else.
3//!
4//! A consumer names the leaves it needs; a backend that does not pass every one of them is
5//! `Eligibility::Excluded`, with the reason per leaf. It is never "ranked slower". A required leaf
6//! nobody tested is an exclusion too (`Reason::Untested`), so a gap in the suite cannot read as a pass.
7//! The result type is [`persistant_core::choose::Eligibility`], shared with every kind.
8
9use crate::RelationalBackend;
10use crate::conformance::{Certified, Leaf, certify_leaves};
11use persistant_core::choose::{Eligibility, LeafFailure};
12use persistant_core::{Config, Reason};
13
14/// Decide eligibility from certified rows. A required leaf with no row is `Untested`.
15pub fn evaluate(rows: &[Certified], required: &[Leaf]) -> Eligibility {
16    let mut out: Vec<LeafFailure> = Vec::new();
17    for leaf in required.iter().copied() {
18        if out.iter().any(|e| e.leaf == leaf.id()) {
19            continue;
20        }
21        match rows.iter().find(|c| c.leaf == leaf) {
22            Some(c) if c.row.enabled => {}
23            Some(c) => out.push(LeafFailure {
24                leaf: leaf.id().to_string(),
25                reason: c.row.reason.unwrap_or(Reason::Untested),
26                note: c.row.note.clone(),
27            }),
28            None => out.push(LeafFailure {
29                leaf: leaf.id().to_string(),
30                reason: Reason::Untested,
31                note: "no test ran for this leaf".to_string(),
32            }),
33        }
34    }
35    if out.is_empty() {
36        Eligibility::Eligible
37    } else {
38        Eligibility::Excluded(out)
39    }
40}
41
42/// Run the required leaves (each against its known-bad controls) on `target`.
43pub async fn check<R: RelationalBackend>(target: &R, required: &[Leaf]) -> Eligibility {
44    let rows = certify_leaves(target, &Config::new(), &Config::new(), required).await;
45    evaluate(&rows, required)
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51    use crate::conformance::Certified;
52    use persistant_core::Row;
53    use std::collections::BTreeMap;
54
55    fn row(leaf: Leaf, enabled: bool, reason: Option<Reason>) -> Certified {
56        Certified {
57            leaf,
58            row: Row {
59                subject_kind: "capability".to_string(),
60                subject: persistant_core::CapabilityId::new(leaf.id()).unwrap(),
61                backend: "test".to_string(),
62                version: "0".to_string(),
63                config: Config::new(),
64                enabled,
65                reason,
66                metadata: Config::new(),
67                note: "n".to_string(),
68                extra: BTreeMap::new(),
69            },
70        }
71    }
72
73    #[test]
74    fn a_leaf_that_is_on_is_eligible_and_one_that_is_off_is_excluded_with_its_reason() {
75        let rows = [
76            row(Leaf::Values, true, None),
77            row(Leaf::Transaction, false, Some(Reason::Wrong)),
78        ];
79        assert_eq!(
80            evaluate(&rows, &[Leaf::Values]),
81            Eligibility::Eligible,
82            "control: the passing leaf alone is eligible"
83        );
84        let r = evaluate(&rows, &[Leaf::Values, Leaf::Transaction]);
85        assert!(
86            matches!(&r, Eligibility::Excluded(ex) if ex.len() == 1 && ex[0].reason == Reason::Wrong && ex[0].leaf == "rel.transaction"),
87            "{r:?}"
88        );
89    }
90
91    #[test]
92    fn a_required_leaf_with_no_row_is_untested_not_a_pass() {
93        let r = evaluate(&[], &[Leaf::Values]);
94        assert!(matches!(&r, Eligibility::Excluded(ex) if ex[0].reason == Reason::Untested));
95        assert_eq!(evaluate(&[], &[]), Eligibility::Eligible);
96    }
97}