use crate::connector::{ServiceConnector, ServiceInfo, ServiceLifecycle, ServiceStatus};
use super::helpers::{VersionProbe, binary_on_path, binary_version};
pub struct ReviewConnector {
binary: Option<String>,
}
impl ReviewConnector {
pub fn new() -> Self {
Self { binary: None }
}
pub fn with_binary(binary: impl Into<String>) -> Self {
Self {
binary: Some(binary.into()),
}
}
fn binary(&self) -> &str {
self.binary.as_deref().unwrap_or("trusty-review")
}
}
impl Default for ReviewConnector {
fn default() -> Self {
Self::new()
}
}
impl ServiceConnector for ReviewConnector {
fn id(&self) -> &'static str {
"trusty-review"
}
fn display_name(&self) -> &'static str {
"Trusty Review"
}
fn lifecycle(&self) -> ServiceLifecycle {
ServiceLifecycle::OnDemand
}
fn detect(&self) -> ServiceInfo {
let binary = self.binary();
if !binary_on_path(binary) {
return ServiceInfo {
id: self.id().to_string(),
display_name: self.display_name().to_string(),
status: ServiceStatus::Absent,
version: None,
url: None,
hint: None,
lifecycle: self.lifecycle(),
};
}
let (status, version, hint) = match binary_version(binary) {
VersionProbe::Ran(version) => (ServiceStatus::Available, version, None),
VersionProbe::CannotExecute(why) => (
ServiceStatus::Degraded,
None,
Some(format!(
"{binary} is on PATH but did not run: {why}. Reinstall it \
with `cargo install {binary}`."
)),
),
};
ServiceInfo {
id: self.id().to_string(),
display_name: self.display_name().to_string(),
status,
version,
url: None,
hint,
lifecycle: self.lifecycle(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn review_connector_reports_available_when_the_binary_is_present() {
if which::which("cargo").is_err() {
eprintln!("skip: no cargo on PATH to probe as a stand-in binary");
return;
}
let started = std::time::Instant::now();
let info = ReviewConnector::with_binary("cargo").detect();
let elapsed = started.elapsed();
assert_eq!(
info.status,
ServiceStatus::Available,
"a present per-invocation binary is Available"
);
assert!(
info.version.is_some(),
"the card renders the version read off `--version`"
);
assert_eq!(info.id, "trusty-review");
assert_eq!(info.display_name, "Trusty Review");
assert!(info.url.is_none(), "a per-invocation tool has no URL");
assert!(
elapsed < Duration::from_secs(3),
"the detect must not dial anything — a socket dial's own budget is \
3 s, so this bound is what a reintroduced dial would trip: {elapsed:?}"
);
}
#[test]
fn review_connector_reports_absent_when_the_binary_is_missing() {
let info = ReviewConnector::with_binary("trusty-review-does-not-exist-9f3a").detect();
assert_eq!(info.status, ServiceStatus::Absent);
assert!(info.version.is_none());
assert!(info.hint.is_none());
}
#[test]
fn review_connector_never_reaches_running() {
for connector in [
ReviewConnector::new(),
ReviewConnector::with_binary("trusty-review-does-not-exist-9f3a"),
] {
let status = connector.detect().status;
assert!(
matches!(status, ServiceStatus::Available | ServiceStatus::Absent),
"a per-invocation member has only two honest verdicts, got {status:?}"
);
}
}
#[test]
fn review_reports_an_on_demand_lifecycle_on_every_verdict() {
for connector in [
ReviewConnector::new(),
ReviewConnector::with_binary("trusty-review-does-not-exist-9f3a"),
] {
let payload = serde_json::to_value(connector.detect()).expect("serialise");
assert_eq!(
payload.get("lifecycle"),
Some(&serde_json::json!("on_demand")),
"the card branches on this key: {payload}"
);
}
}
}