diff --git a/crates/cli/src/output/formatter.rs b/crates/cli/src/output/formatter.rs index a4b1849..a10bbfc 100644 --- a/crates/cli/src/output/formatter.rs +++ b/crates/cli/src/output/formatter.rs @@ -121,6 +121,15 @@ fn infer_error_metadata(message: &str) -> (&'static str, bool, Option) { return ("not_found", false, Some(NOT_FOUND_SUGGESTION.to_string())); } + if normalized.contains("access denied") + || normalized.contains("unauthorized") + || normalized.contains("forbidden") + || normalized.contains("authentication") + || normalized.contains("credentials") + { + return ("auth_error", false, Some(AUTH_SUGGESTION.to_string())); + } + if normalized.contains("invalid") || normalized.contains("cannot be empty") || normalized.contains("must be") @@ -131,15 +140,6 @@ fn infer_error_metadata(message: &str) -> (&'static str, bool, Option) { return ("usage_error", false, Some(USAGE_SUGGESTION.to_string())); } - if normalized.contains("access denied") - || normalized.contains("unauthorized") - || normalized.contains("forbidden") - || normalized.contains("authentication") - || normalized.contains("credentials") - { - return ("auth_error", false, Some(AUTH_SUGGESTION.to_string())); - } - if normalized.contains("already exists") || normalized.contains("conflict") || normalized.contains("precondition") @@ -509,6 +509,38 @@ mod tests { assert_eq!(details.suggestion.as_deref(), Some(USAGE_SUGGESTION)); } + #[test] + fn test_error_descriptor_from_message_prefers_auth_for_invalid_credentials() { + let descriptor = ErrorDescriptor::from_message("Invalid credentials for alias 'local'"); + let json = descriptor.to_json_output(); + + let details = json.details.expect("details should be present"); + assert_eq!(details.error_type, "auth_error"); + assert!(!details.retryable); + assert_eq!(details.suggestion.as_deref(), Some(AUTH_SUGGESTION)); + } + + #[test] + fn test_error_descriptor_from_message_classifies_other_auth_failures() { + for message in [ + "Unauthorized request for alias 'local'", + "Forbidden: bucket access denied", + "Authentication failed for alias 'local'", + ] { + let descriptor = ErrorDescriptor::from_message(message); + let json = descriptor.to_json_output(); + let details = json.details.expect("details should be present"); + + assert_eq!(details.error_type, "auth_error", "message: {message}"); + assert!(!details.retryable, "message: {message}"); + assert_eq!( + details.suggestion.as_deref(), + Some(AUTH_SUGGESTION), + "message: {message}" + ); + } + } + #[test] fn test_error_with_suggestion_overrides_default_hint() { let descriptor = ErrorDescriptor::from_code(