use crate::compat::Compatibility;
pub const APPLY_NOW: &str = "Apply now";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UpdateCopy {
pub headline: String,
pub detail: String,
pub action: Option<&'static str>,
}
#[must_use]
pub fn update_copy(class: &Compatibility, version: &str) -> UpdateCopy {
match class {
Compatibility::Hot => UpdateCopy {
headline: format!("Update {version} is ready to apply"),
detail: "It reaches new conversations right away, with no restart.".to_owned(),
action: Some(APPLY_NOW),
},
Compatibility::Warm => UpdateCopy {
headline: format!("Update {version} is ready to apply"),
detail: "Applying it restarts the service; conversations already underway finish \
first."
.to_owned(),
action: Some(APPLY_NOW),
},
Compatibility::Cold => UpdateCopy {
headline: format!("Update {version} needs a coordinated deploy"),
detail: "It changes how the parts talk to each other, so the whole deployment moves \
together. Reach out to whoever runs your deployment to schedule it."
.to_owned(),
action: None,
},
Compatibility::Incompatible(reason) => UpdateCopy {
headline: format!("Update {version} can't be applied here"),
detail: format!(
"This build was made for a different setup: {reason}. Reach out to whoever runs \
your deployment."
),
action: None,
},
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::pedantic, clippy::nursery, missing_docs)]
use super::*;
use crate::compat::Incompatibility;
fn all_classes() -> Vec<Compatibility> {
vec![
Compatibility::Hot,
Compatibility::Warm,
Compatibility::Cold,
Compatibility::Incompatible(Incompatibility::Wire),
Compatibility::Incompatible(Incompatibility::EventLog),
Compatibility::Incompatible(Incompatibility::Crd),
]
}
#[test]
fn hot_and_warm_carry_the_apply_verb() {
assert_eq!(
update_copy(&Compatibility::Hot, "0.2.0").action,
Some(APPLY_NOW)
);
assert_eq!(
update_copy(&Compatibility::Warm, "0.2.0").action,
Some(APPLY_NOW)
);
}
#[test]
fn cold_and_incompatible_carry_no_apply_verb() {
assert_eq!(update_copy(&Compatibility::Cold, "0.2.0").action, None);
assert_eq!(
update_copy(&Compatibility::Incompatible(Incompatibility::Wire), "0.2.0").action,
None,
);
}
#[test]
fn each_class_speaks_plainly_and_distinctly() {
assert!(
update_copy(&Compatibility::Hot, "0.2.0")
.detail
.contains("no restart"),
"hot copy must say it needs no restart"
);
assert!(
update_copy(&Compatibility::Warm, "0.2.0")
.detail
.contains("restarts the service"),
"warm copy must say it restarts the service"
);
let cold = update_copy(&Compatibility::Cold, "0.2.0");
assert!(
cold.detail.contains("coordinated deploy")
|| cold.headline.contains("coordinated deploy"),
"cold copy must route to a coordinated deploy"
);
}
#[test]
fn the_version_rides_every_headline() {
for class in all_classes() {
assert!(
update_copy(&class, "1.4.2").headline.contains("1.4.2"),
"the version must appear in the headline for {class:?}"
);
}
}
#[test]
fn no_banned_jargon_and_no_apologising_anywhere() {
const BANNED: &[&str] = &[
"operator",
"trifecta",
"rule-of-two",
"context budget",
"sub-agent",
"state-changing action",
"lethal-trifecta",
"please",
"sorry",
"unfortunately",
];
for class in all_classes() {
let copy = update_copy(&class, "0.2.0");
let surface = format!(
"{} {} {}",
copy.headline,
copy.detail,
copy.action.unwrap_or_default()
)
.to_lowercase();
for banned in BANNED {
assert!(
!surface.contains(banned),
"class {class:?} copy contains banned term {banned:?}: {surface}"
);
}
}
}
#[test]
fn incompatible_names_the_mismatched_axis() {
let copy = update_copy(
&Compatibility::Incompatible(Incompatibility::EventLog),
"0.2.0",
);
assert!(
copy.detail.contains("event-log schema"),
"an incompatible update names the axis that differs: {}",
copy.detail
);
}
}