| 1 | use super::*; |
| 2 | |
| 3 | fn report(error: &anyhow::Error) -> String { |
| 4 | report_compaction_failure("Auto-compaction failed", "compact_fixture", true, error) |
| 5 | } |
| 6 | |
| 7 | #[test] |
| 8 | fn untyped_usage_limit_text_never_becomes_quota_exhaustion() { |
| 9 | let error = anyhow::anyhow!( |
| 10 | "[auth] Authorization failed: You've reached your usage limit for this billing cycle" |
| 11 | ); |
| 12 | let message = report(&error); |
| 13 | assert!(message.contains("provider rate limit blocked compaction")); |
| 14 | assert!(!message.contains("quota exhausted")); |
| 15 | } |
| 16 | |
| 17 | #[test] |
| 18 | fn typed_quota_renders_quota_and_is_not_transient() { |
| 19 | let error = anyhow::Error::new(crate::llm_client::LlmError::from_http_response( |
| 20 | 429, |
| 21 | r#"{"error":{"code":"insufficient_quota"}}"#, |
| 22 | )) |
| 23 | .context("summary request failed"); |
| 24 | assert_eq!( |
| 25 | report(&error), |
| 26 | "Auto-compaction failed: provider plan quota exhausted — switch provider/model or renew the provider plan" |
| 27 | ); |
| 28 | assert!(!is_transient_error(&error)); |
| 29 | assert!(!should_retry_cache_aligned_with_formatted(&error)); |
| 30 | } |
| 31 | |
| 32 | #[test] |
| 33 | fn typed_rate_limit_stays_transient_and_does_not_become_quota() { |
| 34 | let error = anyhow::Error::new(crate::llm_client::LlmError::RateLimited { |
| 35 | message: "Too Many Requests".into(), |
| 36 | retry_after: None, |
| 37 | }); |
| 38 | assert!(report(&error).contains("provider rate limit blocked compaction")); |
| 39 | assert!(is_transient_error(&error)); |
| 40 | assert!(should_retry_cache_aligned_with_formatted(&error)); |
| 41 | } |
| 42 | |
| 43 | #[test] |
| 44 | fn unknown_diagnostic_is_preserved_safely() { |
| 45 | let error = anyhow::anyhow!("summary response was structurally empty"); |
| 46 | assert_eq!( |
| 47 | report(&error), |
| 48 | "Auto-compaction failed: summary response was structurally empty" |
| 49 | ); |
| 50 | } |
| 51 | |
| 52 | #[test] |
| 53 | fn untyped_transient_and_deterministic_classification_remains_compatible() { |
| 54 | for message in [ |
| 55 | "Connection timeout", |
| 56 | "429 Too Many Requests", |
| 57 | "503 Service Unavailable", |
| 58 | "network error: connection refused", |
| 59 | ] { |
| 60 | assert!(is_transient_error(&anyhow::anyhow!(message)), "{message}"); |
| 61 | } |
| 62 | for message in [ |
| 63 | "401 Unauthorized: Invalid API key", |
| 64 | "Failed to parse JSON response", |
| 65 | "Invalid request: missing required field", |
| 66 | ] { |
| 67 | assert!(!is_transient_error(&anyhow::anyhow!(message)), "{message}"); |
| 68 | } |
| 69 | assert_eq!( |
| 70 | classify_compaction_failure(&anyhow::anyhow!( |
| 71 | "prompt is too long for this model's context window" |
| 72 | )), |
| 73 | CompactionFailureKind::ContextOverflow |
| 74 | ); |
| 75 | } |
| 76 |