use std::path::Path;
use crate::commands::self_cmd::*;
#[test]
fn render_human_shows_describe_value_separately_from_version() {
let info = VersionInfo {
version: "0.29.1".into(),
build_version: "v0.29.1-2-g3e2822c".into(),
target: "x".into(),
install_source: "dev".into(),
last_update_check: None,
pending_update: None,
};
let out = info.render_human();
assert!(out.lines().any(|l| l == "cartog 0.29.1"));
assert!(out
.lines()
.any(|l| l.trim_start().starts_with("describe:") && l.contains("v0.29.1-2-g3e2822c")));
}
#[test]
fn resolve_release_tarball_short_circuits_runtime_detection() {
assert_eq!(
resolve_install_source(
"release-tarball",
Some(Path::new("/home/user/.cargo/bin/cartog")),
None,
),
"release-tarball",
);
}
#[test]
fn resolve_dev_binary_in_cargo_home_classified_as_cargo() {
let cargo_home = Path::new("/home/user/.cargo");
let bin = Path::new("/home/user/.cargo/bin/cartog");
assert_eq!(
resolve_install_source("dev", Some(bin), Some(cargo_home)),
"cargo",
);
}
#[test]
fn resolve_dev_binary_outside_cargo_classified_as_dev() {
assert_eq!(
resolve_install_source(
"dev",
Some(Path::new("/usr/local/bin/cartog")),
Some(Path::new("/home/user/.cargo")),
),
"dev",
);
}
#[test]
fn resolve_dev_falls_back_to_path_heuristic_when_cargo_home_unset() {
assert_eq!(
resolve_install_source(
"dev",
Some(Path::new("/Users/alice/.cargo/bin/cartog")),
None,
),
"cargo",
);
}
#[test]
fn resolve_dev_no_binary_path_stays_dev() {
assert_eq!(resolve_install_source("dev", None, None), "dev");
}
#[test]
fn parse_release_tag_strips_v_prefix() {
assert_eq!(
parse_release_tag(r#"{"tag_name":"v0.14.0"}"#),
Some("0.14.0".to_string()),
);
assert_eq!(
parse_release_tag(r#"{"tag_name":"0.14.0"}"#),
Some("0.14.0".to_string()),
"leading v is optional",
);
}
#[test]
fn parse_release_tag_rejects_prereleases() {
for tag in [
r#"{"tag_name":"v0.14.0-rc.1"}"#,
r#"{"tag_name":"v0.14.0-alpha"}"#,
r#"{"tag_name":"v0.14.0-nightly.42"}"#,
] {
assert_eq!(parse_release_tag(tag), None, "must reject {tag}");
}
}
#[test]
fn parse_release_tag_rejects_malformed() {
assert_eq!(parse_release_tag("not json at all"), None);
assert_eq!(parse_release_tag(r#"{}"#), None, "missing tag_name");
assert_eq!(
parse_release_tag(r#"{"tag_name":"v0.14"}"#),
None,
"two-part is not stable semver",
);
assert_eq!(
parse_release_tag(r#"{"tag_name":"v0.14.0.0"}"#),
None,
"four-part is not stable semver",
);
assert_eq!(
parse_release_tag(r#"{"tag_name":"vfoo.bar.baz"}"#),
None,
"non-numeric components rejected",
);
}
#[test]
fn is_stable_semver_accepts_canonical_triples() {
assert!(is_stable_semver("0.0.0"));
assert!(is_stable_semver("1.2.3"));
assert!(is_stable_semver("99.0.0"));
}
#[test]
fn is_stable_semver_rejects_anything_else() {
assert!(!is_stable_semver(""));
assert!(!is_stable_semver("1.2"));
assert!(!is_stable_semver("1.2.3.4"));
assert!(!is_stable_semver("1.2.x"));
assert!(!is_stable_semver("1..3"));
}
#[test]
fn compare_stable_versions_orders_lexicographically() {
use std::cmp::Ordering::*;
assert_eq!(compare_stable_versions("0.13.2", "0.14.0"), Less);
assert_eq!(compare_stable_versions("0.14.0", "0.13.2"), Greater);
assert_eq!(compare_stable_versions("0.14.0", "0.14.0"), Equal);
assert_eq!(compare_stable_versions("1.0.0", "0.99.99"), Greater);
assert_eq!(compare_stable_versions("0.13.10", "0.13.2"), Greater);
}
#[test]
fn compare_stable_versions_garbage_treated_as_zero() {
use std::cmp::Ordering::*;
assert_eq!(compare_stable_versions("0.0.0", "abc.def.ghi"), Equal);
assert_eq!(compare_stable_versions("0.1.0", "abc.def.ghi"), Greater);
}
#[test]
fn check_outcome_ok_marks_outdated_when_latest_is_newer() {
let outcome = CheckOutcome::ok("0.13.2", "0.14.0");
assert_eq!(outcome.outdated, Some(true));
assert_eq!(outcome.latest.as_deref(), Some("0.14.0"));
assert_eq!(outcome.current, "0.13.2");
assert_eq!(outcome.error, None);
}
#[test]
fn check_outcome_ok_not_outdated_when_versions_match() {
let outcome = CheckOutcome::ok("0.14.0", "0.14.0");
assert_eq!(outcome.outdated, Some(false));
}
#[test]
fn check_outcome_ok_not_outdated_when_local_is_ahead() {
let outcome = CheckOutcome::ok("0.15.0", "0.14.0");
assert_eq!(outcome.outdated, Some(false));
}
#[test]
fn check_outcome_failed_reports_error_with_null_latest() {
let outcome = CheckOutcome::failed("0.13.2", "connection refused");
assert_eq!(outcome.latest, None);
assert_eq!(outcome.outdated, None);
assert_eq!(outcome.error.as_deref(), Some("connection refused"));
}
#[test]
fn check_outcome_to_human_outdated() {
let s = CheckOutcome::ok("0.13.2", "0.14.0").to_human();
assert!(s.contains("update available"), "got: {s}");
assert!(s.contains("0.13.2"));
assert!(s.contains("0.14.0"));
}
#[test]
fn check_outcome_to_human_up_to_date() {
let s = CheckOutcome::ok("0.14.0", "0.14.0").to_human();
assert!(s.contains("up to date"), "got: {s}");
assert!(s.contains("0.14.0"));
}
#[test]
fn check_outcome_to_human_failed() {
let s = CheckOutcome::failed("0.13.2", "DNS lookup failed").to_human();
assert!(s.contains("update check failed"), "got: {s}");
assert!(s.contains("DNS lookup failed"));
}
#[test]
fn check_outcome_serialises_with_skip_none_keys() {
let outcome = CheckOutcome::failed("0.13.2", "boom");
let json = serde_json::to_string(&outcome).unwrap();
assert!(json.contains(r#""error":"boom""#));
assert!(json.contains(r#""current":"0.13.2""#));
assert!(
!json.contains("latest"),
"latest must be skipped, got: {json}"
);
assert!(
!json.contains("outdated"),
"outdated must be skipped, got: {json}"
);
}
#[test]
fn check_outcome_serialises_success_with_all_fields() {
let outcome = CheckOutcome::ok("0.13.2", "0.14.0");
let json = serde_json::to_string(&outcome).unwrap();
assert!(json.contains(r#""current":"0.13.2""#));
assert!(json.contains(r#""latest":"0.14.0""#));
assert!(json.contains(r#""outdated":true"#));
assert!(!json.contains("error"), "error must be skipped on success");
}
#[test]
fn cargo_install_detected_via_cargo_home_prefix() {
assert!(looks_like_cargo_install(
Path::new("/home/u/.cargo/bin/cartog"),
Some(Path::new("/home/u/.cargo")),
));
}
#[test]
fn cargo_install_detected_via_dotcargo_path_segment() {
assert!(looks_like_cargo_install(
Path::new("/Users/alice/.cargo/bin/cartog"),
None,
));
}
#[test]
fn cargo_install_not_detected_for_unrelated_paths() {
assert!(!looks_like_cargo_install(
Path::new("/usr/local/bin/cartog"),
None,
));
assert!(!looks_like_cargo_install(
Path::new("/home/u/.cargo-tools/bin/cartog"),
None,
));
}