api.tinyhumans.ai
...Powered by Cloudflare..."#;
+ assert_eq!(
+ expected_error_kind(msg),
+ Some(ExpectedErrorKind::ProviderUserState),
+ "backend-wrapped Cloudflare anti-bot interstitial must demote to ProviderUserState"
+ );
+ }
+
+ #[test]
+ fn classifies_minimal_cloudflare_antibot_body_as_provider_user_state() {
+ // Strip the wire shape down to just the two anchors — the
+ // matcher should still fire so future renderings (different
+ // line breaks, stripped HTML, alternate caller wrappers) still
+ // demote.
+ let msg = "Just a moment...\ncloudflare\n";
+ assert_eq!(
+ expected_error_kind(msg),
+ Some(ExpectedErrorKind::ProviderUserState),
+ "minimal `Just a moment...` + `cloudflare` body must demote"
+ );
+ }
+
+ #[test]
+ fn does_not_classify_half_anchor_cloudflare_messages_as_user_state() {
+ // Discrimination test for the double-anchor: either half on its
+ // own must NOT match. This guards against unrelated bodies that
+ // happen to use either phrase out of context.
+
+ // Half-anchor 1: `just a moment` without `cloudflare` — e.g.
+ // a daemon restart spinner blurb.
+ let half_a = "Just a moment, while we restart the daemon";
+ assert_ne!(
+ expected_error_kind(half_a),
+ Some(ExpectedErrorKind::ProviderUserState),
+ "`Just a moment` without `cloudflare` must NOT match the CF anti-bot arm"
+ );
+
+ // Half-anchor 2: `cloudflare` without `just a moment...` — e.g.
+ // a CF Workers footer mention elsewhere.
+ let half_b = "Powered by Cloudflare";
+ assert_ne!(
+ expected_error_kind(half_b),
+ Some(ExpectedErrorKind::ProviderUserState),
+ "`cloudflare` without `Just a moment...` must NOT match the CF anti-bot arm"
+ );
+ }
+
+ #[test]
+ fn does_not_classify_genuine_backend_500_without_cloudflare_body() {
+ // Real bug shape — a 500 from the same backend endpoint with no
+ // Cloudflare interstitial body — must still fall through so
+ // Sentry sees it. Without this guard the arm could be too
+ // permissive and silence genuine database / handler faults.
+ let msg = "Backend returned 500 Internal Server Error for GET \
+ https://api.tinyhumans.ai/agent-integrations/composio/connections: \
+ database connection pool exhausted";
+ assert_eq!(
+ expected_error_kind(msg),
+ None,
+ "genuine backend 500 without Cloudflare body must NOT demote — it is a real bug"
+ );
+ }
+
#[test]
fn classifies_local_ai_binary_missing_errors() {
// OPENHUMAN-TAURI-9N: `local_ai_tts` returns this exact string