diff --git a/src/tests/krate/yanking.rs b/src/tests/krate/yanking.rs index 441980ea026..2aeccee7a21 100644 --- a/src/tests/krate/yanking.rs +++ b/src/tests/krate/yanking.rs @@ -220,3 +220,66 @@ async fn publish_after_yank_max_version() { let json = anon.show_crate("fyk_max").await; assert_eq!(json.krate.max_version, "2.0.0"); } + +#[tokio::test(flavor = "multi_thread")] +async fn patch_version_yank_unyank() { + let (_, anon, _, token) = TestApp::full().with_token(); + + // Upload a new crate + let crate_to_publish = PublishBuilder::new("patchable", "1.0.0"); + token.publish_crate(crate_to_publish).await.good(); + + // Check initial state + let json = anon.show_version("patchable", "1.0.0").await; + assert!(!json.version.yanked); + assert_eq!(json.version.yank_message, None); + + // Yank with message + token + .update_yank_status("patchable", "1.0.0", Some(true), Some("Yanking reason")) + .await + .good(); + + let json = anon.show_version("patchable", "1.0.0").await; + assert!(json.version.yanked); + assert_eq!( + json.version.yank_message, + Some("Yanking reason".to_string()) + ); + + // Update yank message + token + .update_yank_status("patchable", "1.0.0", None, Some("Updated reason")) + .await + .good(); + + let json = anon.show_version("patchable", "1.0.0").await; + assert!(json.version.yanked); + assert_eq!( + json.version.yank_message, + Some("Updated reason".to_string()) + ); + + // Unyank + token + .update_yank_status("patchable", "1.0.0", Some(false), None) + .await + .good(); + + let json = anon.show_version("patchable", "1.0.0").await; + assert!(!json.version.yanked); + assert_eq!(json.version.yank_message, None); + + // Attempt to set yank message on unyanked version (should fail) + token + .update_yank_status("patchable", "1.0.0", None, Some("Invalid message")) + .await + .status() + .is_client_error(); + // Attempt to unyank with message (should fail) + token + .update_yank_status("patchable", "1.0.0", Some(false), Some("Invalid message")) + .await + .status() + .is_client_error(); +} diff --git a/src/tests/routes/crates/versions/yank_unyank.rs b/src/tests/routes/crates/versions/yank_unyank.rs index 451c4bb8adf..69af66c84ce 100644 --- a/src/tests/routes/crates/versions/yank_unyank.rs +++ b/src/tests/routes/crates/versions/yank_unyank.rs @@ -1,7 +1,8 @@ use crate::builders::{CrateBuilder, PublishBuilder}; use crate::util::{RequestHelper, Response, TestApp}; -use crate::OkBool; +use crate::{OkBool, VersionResponse}; use http::StatusCode; +use serde_json::json; pub trait YankRequestHelper { /// Yank the specified version of the specified crate and run all pending background jobs @@ -9,6 +10,15 @@ pub trait YankRequestHelper { /// Unyank the specified version of the specified crate and run all pending background jobs async fn unyank(&self, krate_name: &str, version: &str) -> Response; + + /// Update the yank status of the specified version of the specified crate with a patch request and run all pending background jobs + async fn update_yank_status( + &self, + krate_name: &str, + version: &str, + yanked: Option, + yank_message: Option<&str>, + ) -> Response; } impl YankRequestHelper for T { @@ -25,6 +35,26 @@ impl YankRequestHelper for T { self.app().run_pending_background_jobs().await; response } + + async fn update_yank_status( + &self, + krate_name: &str, + version: &str, + yanked: Option, + yank_message: Option<&str>, + ) -> Response { + let url = format!("/api/v1/crates/{krate_name}/{version}"); + + let json_body = json!({ + "yanked": yanked, + "yank_message": yank_message + }); + let body = serde_json::to_string(&json_body).expect("Failed to serialize JSON body"); + + let response = self.patch(&url, body).await; + self.app().run_pending_background_jobs().await; + response + } } #[tokio::test(flavor = "multi_thread")] diff --git a/src/tests/util.rs b/src/tests/util.rs index 9438b9ccb84..7506bfccaf1 100644 --- a/src/tests/util.rs +++ b/src/tests/util.rs @@ -146,6 +146,20 @@ pub trait RequestHelper { self.run(request).await } + /// Issue a PATCH request + async fn patch(&self, path: &str, body: impl Into) -> Response { + let body = body.into(); + let is_json = body.starts_with(b"{") && body.ends_with(b"}"); + + let mut request = self.request_builder(Method::PATCH, path); + *request.body_mut() = body; + if is_json { + request.header(header::CONTENT_TYPE, "application/json"); + } + + self.run(request).await + } + /// Issue a DELETE request async fn delete(&self, path: &str) -> Response { let request = self.request_builder(Method::DELETE, path);