diff --git a/Cargo.lock b/Cargo.lock index fb76a4f..f2c1461 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1161,7 +1161,7 @@ dependencies = [ [[package]] name = "pycu" -version = "1.1.6" +version = "1.1.7" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index 06d9cbe..a5e7a8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pycu" -version = "1.1.6" +version = "1.1.7" edition = "2024" rust-version = "1.94.1" description = "Check your Python dependencies for newer versions on PyPI" diff --git a/src/output/table.rs b/src/output/table.rs index 92b55f2..80359b8 100644 --- a/src/output/table.rs +++ b/src/output/table.rs @@ -216,7 +216,14 @@ fn color_updated_constraint( let colored = new_parts[2..].join(".").truecolor(r, g, b).to_string(); format!("{}{}", plain, colored) } - _ => latest.to_string(), + _ => { + // diff is at component 3+ (e.g. pandas-stubs 3.0.0 → 3.0.0.260204) + // treat as patch: keep first two components plain, color the rest + let (r, g, b) = p.patch; + let plain = format!("{}.{}.", new_parts[0], new_parts[1]); + let colored = new_parts[2..].join(".").truecolor(r, g, b).to_string(); + format!("{}{}", plain, colored) + } }; // Append the upper bound dimmed if present @@ -338,10 +345,27 @@ mod tests { #[test] fn test_color_updated_constraint_four_component_version() { + // 1.0.0.0 → 1.0.0.1: plain prefix "1.0." + patch-colored "0.1" let result = color_updated_constraint(">=1.0.0.0", "1.0.0.1", ">=1.0.0.1", &ColorScheme::Default); - assert!(result.contains("1.0.0.1")); assert!(result.contains(">=")); + assert!(result.contains("1.0.")); + assert!(result.contains("0.1")); + } + + #[test] + fn test_color_updated_constraint_three_to_four_component() { + // pandas-stubs style: 3.0.0 → 3.0.0.260204 (new 4th component) + // plain prefix "3.0." + patch-colored "0.260204" + let result = color_updated_constraint( + ">=3.0.0", + "3.0.0.260204", + ">=3.0.0.260204", + &ColorScheme::Default, + ); + assert!(result.contains(">=")); + assert!(result.contains("3.0.")); + assert!(result.contains("260204")); } #[test] diff --git a/src/version/compare.rs b/src/version/compare.rs index 9047c9c..e77049a 100644 --- a/src/version/compare.rs +++ b/src/version/compare.rs @@ -171,6 +171,18 @@ mod tests { assert!(!is_newer("1.0.0", "not-a-version")); } + #[test] + fn test_is_newer_four_component() { + // pandas-stubs style: 3.0.0.260204 is newer than 3.0.0 + assert!(is_newer("3.0.0.260204", "3.0.0")); + // and newer than a prior 4-component release + assert!(is_newer("3.0.0.260204", "3.0.0.250204")); + // same 4-component version is not newer + assert!(!is_newer("3.0.0.260204", "3.0.0.260204")); + // older 4-component is not newer than current + assert!(!is_newer("3.0.0.250204", "3.0.0.260204")); + } + #[test] fn test_classify_bump_major() { assert_eq!(classify_bump("1.0.0", "2.0.0"), BumpKind::Major); @@ -188,6 +200,64 @@ mod tests { fn test_classify_bump_patch() { assert_eq!(classify_bump("1.0.0", "1.0.1"), BumpKind::Patch); assert_eq!(classify_bump("7.3.0", "7.3.1"), BumpKind::Patch); + // 4-component versions (e.g. pandas-stubs 3.0.0 → 3.0.0.260204) + assert_eq!(classify_bump("3.0.0", "3.0.0.260204"), BumpKind::Patch); + assert_eq!( + classify_bump("3.0.0.250204", "3.0.0.260204"), + BumpKind::Patch + ); + } + + #[tokio::test] + async fn test_check_dep_four_component_update() { + // pandas-stubs pattern: current >=3.0.0, latest 3.0.0.260204 + use wiremock::matchers::{method, path}; + use wiremock::{Mock, MockServer, ResponseTemplate}; + + let server = MockServer::start().await; + Mock::given(method("GET")) + .and(path("/pypi/pandas-stubs/json")) + .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({ + "info": { "version": "3.0.0.260204" } + }))) + .mount(&server) + .await; + + let client = PypiClient::with_base_url(&server.uri()).unwrap(); + let dep = crate::parsers::Dependency { + name: "pandas-stubs".to_string(), + constraint: ">=3.0.0".to_string(), + }; + let update = check_dep(&client, &dep).await.unwrap().unwrap(); + assert_eq!(update.latest, "3.0.0.260204"); + assert_eq!(update.bump_kind, BumpKind::Patch); + assert_eq!(update.updated_constraint, ">=3.0.0.260204"); + } + + #[tokio::test] + async fn test_check_dep_four_component_to_four_component() { + // pandas-stubs: current >=3.0.0.250204, latest 3.0.0.260204 + use wiremock::matchers::{method, path}; + use wiremock::{Mock, MockServer, ResponseTemplate}; + + let server = MockServer::start().await; + Mock::given(method("GET")) + .and(path("/pypi/pandas-stubs/json")) + .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({ + "info": { "version": "3.0.0.260204" } + }))) + .mount(&server) + .await; + + let client = PypiClient::with_base_url(&server.uri()).unwrap(); + let dep = crate::parsers::Dependency { + name: "pandas-stubs".to_string(), + constraint: ">=3.0.0.250204".to_string(), + }; + let update = check_dep(&client, &dep).await.unwrap().unwrap(); + assert_eq!(update.latest, "3.0.0.260204"); + assert_eq!(update.bump_kind, BumpKind::Patch); + assert_eq!(update.updated_constraint, ">=3.0.0.260204"); } // check_dep and find_updates - network paths covered via wiremock. diff --git a/src/version/constraint.rs b/src/version/constraint.rs index dec830f..31cb1f7 100644 --- a/src/version/constraint.rs +++ b/src/version/constraint.rs @@ -176,6 +176,15 @@ mod tests { assert_eq!(extract_base_version(""), None); assert_eq!(extract_base_version(">=1.0,<2.0"), Some("1.0".to_string())); assert_eq!(extract_base_version("~7.3.0"), Some("7.3.0".to_string())); + // 4-component (pandas-stubs style) + assert_eq!( + extract_base_version(">=3.0.0.250204"), + Some("3.0.0.250204".to_string()) + ); + assert_eq!( + extract_base_version("==3.0.0.260204"), + Some("3.0.0.260204".to_string()) + ); } #[test] @@ -194,6 +203,15 @@ mod tests { assert_eq!(update_constraint("^1.10.0", "2.6.0"), "^2.6.0"); assert_eq!(update_constraint("0.1.6", "0.3.0"), "0.3.0"); assert_eq!(update_constraint("*", "1.0.0"), "*"); + // 4-component new version (pandas-stubs style) + assert_eq!( + update_constraint(">=3.0.0", "3.0.0.260204"), + ">=3.0.0.260204" + ); + assert_eq!( + update_constraint(">=3.0.0.250204", "3.0.0.260204"), + ">=3.0.0.260204" + ); } #[test]