pub use crate::client::repo::{verify as certify, CertificationStage, Verdict};
use crate::web::remote_control_api::dto::BlockerKind;
pub const CLAIMED_SUCCESS_STATUSES: [&str; 3] = ["archived", "merged", "pushed"];
pub fn claims_terminal_success(display_status: Option<&str>) -> bool {
match display_status {
Some(status) => CLAIMED_SUCCESS_STATUSES.contains(&status),
None => true,
}
}
pub const MANUAL_ACTION_STATUSES: [&str; 4] = ["error", "merge wait", "stopped", "stalled"];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Disposition {
KeepObserving,
Certify,
Rejected,
RequiresAction,
}
pub fn classify(display_status: Option<&str>, blocker: Option<BlockerKind>) -> Disposition {
match display_status {
Some("rejected") => Disposition::Rejected,
Some(status) if MANUAL_ACTION_STATUSES.contains(&status) => Disposition::RequiresAction,
Some("blocked") if blocker == Some(BlockerKind::External) => Disposition::RequiresAction,
other if claims_terminal_success(other) => Disposition::Certify,
_ => Disposition::KeepObserving,
}
}
pub fn is_settled_success_claim(display_status: Option<&str>) -> bool {
display_status.is_some_and(|status| CLAIMED_SUCCESS_STATUSES.contains(&status))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn only_post_archive_statuses_claim_a_success_worth_verifying() {
assert!(claims_terminal_success(Some("archived")));
assert!(claims_terminal_success(Some("merged")));
assert!(claims_terminal_success(Some("pushed")));
assert!(!claims_terminal_success(Some("applying")));
assert!(!claims_terminal_success(Some("accepted")));
assert!(!claims_terminal_success(Some("rejected")));
}
#[test]
fn a_change_that_stopped_being_tracked_must_still_be_verified() {
assert!(claims_terminal_success(None));
}
#[test]
fn every_owner_status_is_classified_by_whether_the_owner_can_still_advance_it() {
for status in [
"not queued",
"queued",
"blocked",
"preparing",
"applying",
"accepting",
"rejecting",
"archiving",
"resolving",
"resolve pending",
"reject pending",
] {
assert_eq!(
classify(Some(status), None),
Disposition::KeepObserving,
"'{status}' can still advance without an operator"
);
}
for status in ["error", "merge wait", "stopped", "stalled"] {
assert_eq!(
classify(Some(status), None),
Disposition::RequiresAction,
"'{status}' needs a new operator action"
);
}
for status in ["merged", "pushed", "archived"] {
assert_eq!(
classify(Some(status), None),
Disposition::Certify,
"{status}"
);
}
assert_eq!(classify(Some("rejected"), None), Disposition::Rejected);
assert_eq!(classify(None, None), Disposition::Certify);
}
#[test]
fn a_blocked_row_is_classified_by_its_structured_blocker() {
assert_eq!(
classify(Some("blocked"), Some(BlockerKind::External)),
Disposition::RequiresAction,
"an external prerequisite will not clear without an operator"
);
for kind in [None, Some(BlockerKind::None), Some(BlockerKind::Dependency)] {
assert_eq!(
classify(Some("blocked"), kind),
Disposition::KeepObserving,
"{kind:?} is a hold the owner clears by itself"
);
}
}
#[test]
fn an_external_kind_on_another_status_does_not_release_the_waiter() {
assert_eq!(
classify(Some("applying"), Some(BlockerKind::External)),
Disposition::KeepObserving
);
assert_eq!(
classify(Some("merged"), Some(BlockerKind::External)),
Disposition::Certify
);
assert_eq!(
classify(Some("stalled"), Some(BlockerKind::External)),
Disposition::RequiresAction
);
}
#[test]
fn an_unrecognized_status_keeps_observing() {
assert_eq!(
classify(Some("polishing"), None),
Disposition::KeepObserving
);
assert_eq!(classify(Some(""), None), Disposition::KeepObserving);
}
#[test]
fn only_a_present_success_row_is_a_settled_claim() {
assert!(is_settled_success_claim(Some("merged")));
assert!(is_settled_success_claim(Some("pushed")));
assert!(is_settled_success_claim(Some("archived")));
assert!(!is_settled_success_claim(None));
assert!(!is_settled_success_claim(Some("applying")));
}
#[test]
fn no_status_both_claims_success_and_demands_an_operator() {
for status in MANUAL_ACTION_STATUSES {
assert!(
!CLAIMED_SUCCESS_STATUSES.contains(&status),
"'{status}' cannot be both"
);
}
}
}