diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c4e2c3875..1dc49184bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ - (snapshots) Compress preprod snapshot manifest with zstd ([#3336](https://github.com/getsentry/sentry-cli/pull/3336)) +### Fixes + +- (snapshots) Show a clear "project was renamed" error instead of a cryptic JSON parse failure when uploading to a renamed project slug ([#3341](https://github.com/getsentry/sentry-cli/pull/3341)) + ## 3.5.1 ### Internal Changes 🔧 diff --git a/src/api/mod.rs b/src/api/mod.rs index 24a3741844..c45efd7a0c 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1015,7 +1015,7 @@ impl AuthenticatedApi<'_> { org: &str, project: &str, body: &S, - ) -> ApiResult { + ) -> ApiResult { let path = format!( "/projects/{}/{}/preprodartifacts/snapshots/", PathArg(org), @@ -1023,7 +1023,8 @@ impl AuthenticatedApi<'_> { ); self.request(Method::Post, &path)? .with_zstd_json_body(body)? - .send() + .send()? + .convert_rnf(ApiErrorKind::ProjectNotFound) } /// Fetches upload options for snapshots. @@ -1037,7 +1038,7 @@ impl AuthenticatedApi<'_> { PathArg(org), PathArg(project) ); - self.get(&path)?.convert() + self.get(&path)?.convert_rnf(ApiErrorKind::ProjectNotFound) } pub fn get_latest_base_snapshot( @@ -1605,20 +1606,15 @@ impl ApiResponse { fn convert_rnf(self, res_err: ApiErrorKind) -> ApiResult { match self.status() { 301 | 302 if res_err == ApiErrorKind::ProjectNotFound => { - #[derive(Deserialize, Debug)] - struct ErrorDetail { - slug: String, - } - #[derive(Deserialize, Debug)] struct ErrorInfo { - detail: ErrorDetail, + slug: String, } match self.convert::() { Ok(info) => Err(ApiError::with_source( res_err, - ProjectRenamedError(info.detail.slug), + ProjectRenamedError(info.slug), )), Err(_) => Err(res_err.into()), } @@ -2143,3 +2139,49 @@ pub struct ObjectstoreUploadOptions { pub auth_token: Option, pub expiration_policy: String, } + +#[cfg(test)] +mod tests { + use std::error::Error as _; + + use super::*; + + fn project_renamed_response() -> ApiResponse { + ApiResponse { + status: 302, + headers: vec!["content-type: application/json".to_owned()], + body: Some( + br#"{"slug":"new-project-slug","detail":{"extra":{"url":"/api/0/projects/wat-org/new-project-slug/preprodartifacts/snapshots/upload-options/","slug":"new-project-slug"}}}"# + .to_vec(), + ), + } + } + + #[test] + fn convert_rnf_reports_project_rename() { + let err = project_renamed_response() + .convert_rnf::(ApiErrorKind::ProjectNotFound) + .expect_err("expected a project-renamed error"); + + let source = err.source().map(|s| s.to_string()).unwrap_or_default(); + + assert!( + source.contains("project was renamed to 'new-project-slug'"), + "expected rename message in error source, got: {err:?} / source: {source}" + ); + } + + #[test] + fn convert_rnf_reports_project_rename_for_create_response() { + let err = project_renamed_response() + .convert_rnf::(ApiErrorKind::ProjectNotFound) + .expect_err("expected a project-renamed error"); + + let source = err.source().map(|s| s.to_string()).unwrap_or_default(); + + assert!( + source.contains("project was renamed to 'new-project-slug'"), + "expected rename message in error source, got: {err:?} / source: {source}" + ); + } +} diff --git a/src/commands/snapshots/upload.rs b/src/commands/snapshots/upload.rs index 370d268196..45681a8042 100644 --- a/src/commands/snapshots/upload.rs +++ b/src/commands/snapshots/upload.rs @@ -18,7 +18,7 @@ use serde_json::Value; use sha2::{Digest as _, Sha256}; use walkdir::WalkDir; -use crate::api::{Api, CreateSnapshotResponse, ImageMetadata, SnapshotsManifest}; +use crate::api::{Api, ImageMetadata, SnapshotsManifest}; use crate::config::Config; use crate::utils::args::ArgExt as _; use crate::utils::build_vcs::collect_git_metadata; @@ -207,10 +207,9 @@ pub fn execute(matches: &ArgMatches) -> Result<()> { // POST manifest to API println!("{} Creating snapshot...", style(">").dim()); let api = Api::current(); - let response: CreateSnapshotResponse = api + let response = api .authenticated()? - .create_preprod_snapshot(&org, &project, &manifest)? - .convert()?; + .create_preprod_snapshot(&org, &project, &manifest)?; println!( "{} Created snapshot {} with {} {}", diff --git a/tests/integration/_cases/snapshots/snapshots-upload-renamed-project.trycmd b/tests/integration/_cases/snapshots/snapshots-upload-renamed-project.trycmd new file mode 100644 index 0000000000..bb8ca98280 --- /dev/null +++ b/tests/integration/_cases/snapshots/snapshots-upload-renamed-project.trycmd @@ -0,0 +1,15 @@ +``` +$ sentry-cli snapshots upload tests/integration/_fixtures/snapshots --app-id test-app --no-git-metadata +? failed +> Found 1 image file +> Processing 1 image file +error: Project not found. Ensure that you configured the correct project and organization. + +Caused by: + project was renamed to 'new-project-slug' + Please use this slug in your .sentryclirc file, sentry.properties file or in the CLI --project parameter + +Add --log-level=[info|debug] or export SENTRY_LOG_LEVEL=[info|debug] to see more output. +Please attach the full debug log to all bug reports. + +``` diff --git a/tests/integration/_fixtures/snapshots/snapshot.png b/tests/integration/_fixtures/snapshots/snapshot.png new file mode 100644 index 0000000000..51c29bd304 Binary files /dev/null and b/tests/integration/_fixtures/snapshots/snapshot.png differ diff --git a/tests/integration/snapshots.rs b/tests/integration/snapshots.rs index 847502bbf1..e4c3462c32 100644 --- a/tests/integration/snapshots.rs +++ b/tests/integration/snapshots.rs @@ -1,4 +1,4 @@ -use crate::integration::TestManager; +use crate::integration::{MockEndpointBuilder, TestManager}; #[test] fn command_snapshots_diff_help() { @@ -19,3 +19,20 @@ fn command_snapshots_download_help() { fn command_snapshots_upload_help() { TestManager::new().register_trycmd_test("snapshots/snapshots-upload-help.trycmd"); } + +#[test] +fn command_snapshots_upload_renamed_project() { + TestManager::new() + .mock_endpoint( + MockEndpointBuilder::new( + "GET", + "/api/0/projects/wat-org/wat-project/preprodartifacts/snapshots/upload-options/", + ) + .with_status(302) + .with_response_body( + r#"{"slug":"new-project-slug","detail":{"extra":{"url":"/api/0/projects/wat-org/new-project-slug/preprodartifacts/snapshots/upload-options/","slug":"new-project-slug"}}}"#, + ), + ) + .register_trycmd_test("snapshots/snapshots-upload-renamed-project.trycmd") + .with_default_token(); +}