diff --git a/src/commands/plugin.rs b/src/commands/plugin.rs index 2ba19593..a576a797 100644 --- a/src/commands/plugin.rs +++ b/src/commands/plugin.rs @@ -409,13 +409,7 @@ fn update(name: Option, yes: bool) -> Result<()> { match status { Ok(s) if s.success() => { - registry::install_plugin( - &pl.name, - std::path::Path::new(&pl.path), - &pl.source, - &pl.starforge_version, - &pl.plugin_version, - )?; + registry::install_plugin(&pl.name, std::path::Path::new(&pl.path), &pl.source, &pl.starforge_version, &pl.plugin_version)?; p::success(&format!(" '{}' updated via cargo install", pl.name)); updated += 1; } @@ -447,7 +441,7 @@ fn update(name: Option, yes: bool) -> Result<()> { }) .unwrap_or(0); - let installed_epoch = 0; + let installed_epoch = 0u64; if modified > installed_epoch { // Library on disk is newer — refresh the registry entry. diff --git a/src/utils/templates.rs b/src/utils/templates.rs index 38e38788..2a3911e6 100644 --- a/src/utils/templates.rs +++ b/src/utils/templates.rs @@ -1991,4 +1991,175 @@ mod tests { )); assert!(assert_template_compatible(&entry).is_err()); } + + // ── parse_semver edge cases ──────────────────────────────────────────────── + + #[test] + fn parse_semver_large_numbers() { + assert_eq!(parse_semver("999.0.0"), Ok((999, 0, 0))); + assert_eq!(parse_semver("0.0.999999"), Ok((0, 0, 999999))); + } + + #[test] + fn parse_semver_rejects_single_component() { + assert!(parse_semver("1").is_err()); + } + + #[test] + fn parse_semver_rejects_two_components() { + assert!(parse_semver("1.2").is_err()); + } + + #[test] + fn parse_semver_rejects_extra_dots() { + assert!(parse_semver("1.2.3.4").is_err(), "four components should fail"); + } + + #[test] + fn parse_semver_rejects_whitespace() { + assert!(parse_semver(" 1.2.3").is_err()); + assert!(parse_semver("1.2.3 ").is_err()); + assert!(parse_semver("1. 2.3").is_err()); + } + + #[test] + fn parse_semver_rejects_negative_component() { + // A leading '-' makes the component non-numeric. + assert!(parse_semver("1.-2.3").is_err()); + } + + #[test] + fn parse_semver_rejects_alpha_component() { + assert!(parse_semver("1.2.alpha").is_err()); + assert!(parse_semver("v1.2.3").is_err()); + } + + // ── check_version_range payload verification ─────────────────────────────── + + #[test] + fn check_version_range_too_old_carries_correct_payload() { + let result = check_version_range("0.0.9", Some("0.1.0"), None); + match result { + CompatibilityStatus::TooOld { + required_min, + running, + } => { + assert_eq!(required_min, "0.1.0"); + assert_eq!(running, "0.0.9"); + } + other => panic!("expected TooOld, got {:?}", other), + } + } + + #[test] + fn check_version_range_too_new_carries_correct_payload() { + let result = check_version_range("2.0.0", None, Some("1.99.99")); + match result { + CompatibilityStatus::TooNew { + required_max, + running, + } => { + assert_eq!(required_max, "1.99.99"); + assert_eq!(running, "2.0.0"); + } + other => panic!("expected TooNew, got {:?}", other), + } + } + + #[test] + fn check_version_range_exact_min_boundary_is_compatible() { + // version == min should be Compatible, not TooOld. + assert_eq!( + check_version_range("1.0.0", Some("1.0.0"), None), + CompatibilityStatus::Compatible + ); + } + + #[test] + fn check_version_range_exact_max_boundary_is_compatible() { + // version == max should be Compatible, not TooNew. + assert_eq!( + check_version_range("1.0.0", None, Some("1.0.0")), + CompatibilityStatus::Compatible + ); + } + + #[test] + fn check_version_range_min_only_above_min_is_compatible() { + assert_eq!( + check_version_range("1.2.0", Some("1.0.0"), None), + CompatibilityStatus::Compatible + ); + } + + #[test] + fn check_version_range_max_only_below_max_is_compatible() { + assert_eq!( + check_version_range("0.9.0", None, Some("1.0.0")), + CompatibilityStatus::Compatible + ); + } + + #[test] + fn check_version_range_malformed_running_version_is_error() { + // The running version itself being malformed should yield MalformedMetadata. + let result = check_version_range("not-a-version", Some("0.1.0"), None); + assert!(matches!( + result, + CompatibilityStatus::MalformedMetadata { .. } + )); + } + + #[test] + fn check_version_range_malformed_max_carries_reason() { + let result = check_version_range("0.1.0", None, Some("1.x.0")); + match result { + CompatibilityStatus::MalformedMetadata { reason } => { + assert!(!reason.is_empty(), "reason should not be empty"); + } + other => panic!("expected MalformedMetadata, got {:?}", other), + } + } + + // ── assert_template_compatible error message content ────────────────────── + + #[test] + fn assert_template_compatible_too_old_message_contains_min_and_running() { + let mut entry = make_entry("future-tpl"); + let (major, _, _) = parse_semver(CLI_VERSION).unwrap(); + let min = format!("{}.0.0", major + 100); + entry.cli_version_min = Some(min.clone()); + let err = assert_template_compatible(&entry).unwrap_err(); + let msg = err.to_string(); + assert!(msg.contains(&min), "error should contain required_min"); + assert!(msg.contains(CLI_VERSION), "error should contain running version"); + assert!(msg.contains("future-tpl"), "error should contain template name"); + } + + #[test] + fn assert_template_compatible_too_new_message_contains_max_and_running() { + let mut entry = make_entry("old-tpl"); + let (major, minor, _) = parse_semver(CLI_VERSION).unwrap(); + if major > 0 || minor > 0 { + entry.cli_version_max = Some("0.0.0".to_string()); + let err = assert_template_compatible(&entry).unwrap_err(); + let msg = err.to_string(); + assert!(msg.contains("0.0.0"), "error should contain required_max"); + assert!(msg.contains(CLI_VERSION), "error should contain running version"); + assert!(msg.contains("old-tpl"), "error should contain template name"); + } + } + + #[test] + fn assert_template_compatible_malformed_message_contains_reason() { + let mut entry = make_entry("broken-tpl"); + entry.cli_version_min = Some("bad-version".to_string()); + let err = assert_template_compatible(&entry).unwrap_err(); + let msg = err.to_string(); + assert!(msg.contains("broken-tpl"), "error should contain template name"); + assert!( + msg.contains("malformed") || msg.contains("bad-version"), + "error should describe the problem" + ); + } }