use serde::{Deserialize, Serialize};
use crate::actions::Via;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum Producer {
Review,
BugConfirmation,
PersonTest,
}
impl Producer {
#[must_use]
pub const fn source(self) -> AcceptanceSource {
match self {
Self::Review => AcceptanceSource::Review,
Self::BugConfirmation => AcceptanceSource::BugConfirmation,
Self::PersonTest => AcceptanceSource::PersonTest,
}
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Review => "review",
Self::BugConfirmation => "bug_confirmation",
Self::PersonTest => "person_test",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum AcceptanceSource {
Authored,
Review,
BugConfirmation,
PersonTest,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
pub struct VisibleTest {
pub path: String,
pub sha256: String,
pub cites: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
pub struct SealedRef {
pub key: String,
pub manifest_sha256: String,
pub count: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
pub struct Decided {
pub by: String,
pub via: Via,
pub at: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
pub struct PreFrozenAcceptance {
pub unit: String,
pub requirement: String,
pub wording_digest: String,
pub approval_digest: String,
pub visible: Vec<VisibleTest>,
pub held_out_sealed: Option<SealedRef>,
pub producer: Producer,
pub decided: Decided,
}
impl PreFrozenAcceptance {
pub fn validate(&self) -> Result<(), String> {
if self.visible.is_empty() {
return Err("a pre-frozen set needs at least one visible test".to_owned());
}
if let Some(t) = self.visible.iter().find(|t| t.cites.is_empty()) {
return Err(format!("{} cites nothing", t.path));
}
Ok(())
}
#[must_use]
pub fn is_current(&self, wording_now: &str) -> bool {
current(self, wording_now)
}
#[must_use]
pub fn trailer(&self, manifest: &str) -> String {
format!(
"Acceptance: {} manifest={manifest} approval={} wording={} by={}\n",
self.producer.as_str(),
self.approval_digest,
self.wording_digest,
self.decided
.by
.split_whitespace()
.collect::<Vec<_>>()
.join(" ")
)
}
}
#[must_use]
pub fn current(record: &PreFrozenAcceptance, wording_now: &str) -> bool {
record.wording_digest == wording_now
}
#[must_use]
pub fn manifest_text(visible: &[VisibleTest]) -> String {
let mut lines: Vec<String> = visible
.iter()
.map(|t| format!("{}={}", t.path, t.sha256))
.collect();
lines.sort();
lines.join("\n")
}
#[cfg(test)]
mod tests {
use proptest::prelude::*;
use super::*;
fn record(wording: &str) -> PreFrozenAcceptance {
PreFrozenAcceptance {
unit: "007-bugfixes".to_owned(),
requirement: "FR-003".to_owned(),
wording_digest: wording.to_owned(),
approval_digest: "sha256:a".to_owned(),
visible: vec![VisibleTest {
path: "tests/repro_e1.rs".to_owned(),
sha256: "sha256:t".to_owned(),
cites: vec!["E1".to_owned()],
}],
held_out_sealed: None,
producer: Producer::BugConfirmation,
decided: Decided {
by: "Ada".to_owned(),
via: Via::Terminal,
at: 1,
},
}
}
proptest! {
#[test]
fn any_wording_change_lapses_it(a in "[a-z0-9:]{1,20}", b in "[a-z0-9:]{1,20}") {
let r = record(&a);
prop_assert!(current(&r, &a));
prop_assert_eq!(current(&r, &b), a == b);
prop_assert_eq!(r.is_current(&b), a == b);
}
}
#[test]
fn the_trailer_has_one_form_for_every_producer() {
let r = record("sha256:w");
assert_eq!(
r.trailer("sha256:m"),
"Acceptance: bug_confirmation manifest=sha256:m approval=sha256:a wording=sha256:w by=Ada\n"
);
assert!(r.validate().is_ok());
let mut empty = r;
empty.visible[0].cites.clear();
assert!(empty.validate().is_err());
assert_eq!(Producer::PersonTest.source(), AcceptanceSource::PersonTest);
}
}