use church_core::{ ChurchApiClient, ChurchCoreConfig, error::ChurchApiError, }; use mockito::{self, mock}; use serde_json::json; use std::time::Duration; #[cfg(test)] mod error_handling_tests { use super::*; fn create_test_client() -> ChurchApiClient { let config = ChurchCoreConfig::new() .with_base_url(&mockito::server_url()) .with_timeout(Duration::from_millis(100)) .with_retry_attempts(1) .with_offline_mode(false); ChurchApiClient::new(config).unwrap() } #[tokio::test] async fn test_http_404_error() { let _m = mock("GET", "/events/nonexistent") .with_status(404) .with_header("content-type", "application/json") .with_body(json!({ "success": false, "error": "Not found" }).to_string()) .create(); let client = create_test_client(); let result = client.get_event("nonexistent").await; // For get_event, 404 should return None, not an error assert!(result.is_ok()); assert!(result.unwrap().is_none()); } #[tokio::test] async fn test_http_401_unauthorized() { let _m = mock("POST", "/events") .with_status(401) .with_header("content-type", "application/json") .with_body(json!({ "success": false, "error": "Unauthorized access" }).to_string()) .create(); let client = create_test_client(); let new_event = church_core::models::NewEvent { title: "Protected Event".to_string(), description: "Requires authentication".to_string(), start_time: chrono::Utc::now(), end_time: chrono::Utc::now() + chrono::Duration::hours(1), location: "Secure Location".to_string(), location_url: None, image: None, category: church_core::models::EventCategory::Other, is_featured: false, recurring_type: None, tags: None, contact_email: None, contact_phone: None, registration_url: None, max_attendees: None, }; let result = client.create_event(new_event).await; assert!(result.is_err()); if let Err(error) = result { assert!(matches!(error, ChurchApiError::Auth(_))); assert!(error.is_auth_error()); } } #[tokio::test] async fn test_http_403_forbidden() { let _m = mock("PUT", "/config") .with_status(403) .with_header("content-type", "application/json") .with_body(json!({ "success": false, "error": "Forbidden - insufficient permissions" }).to_string()) .create(); let client = create_test_client(); let config = church_core::models::ChurchConfig { church_name: Some("Test Church".to_string()), church_address: None, contact_phone: None, contact_email: None, website_url: None, google_maps_url: None, facebook_url: None, youtube_url: None, instagram_url: None, about_text: None, mission_statement: None, service_times: None, pastoral_staff: None, ministries: None, app_settings: None, emergency_contacts: None, }; let result = client.update_config(config).await; assert!(result.is_err()); if let Err(error) = result { assert!(matches!(error, ChurchApiError::PermissionDenied)); assert!(error.is_auth_error()); } } #[tokio::test] async fn test_http_500_server_error() { let _m = mock("GET", "/events/upcoming") .with_status(500) .with_header("content-type", "application/json") .with_body(json!({ "success": false, "error": "Internal server error" }).to_string()) .create(); let client = create_test_client(); let result = client.get_upcoming_events(None).await; assert!(result.is_err()); if let Err(error) = result { assert!(matches!(error, ChurchApiError::Http(_))); assert!(error.is_network_error()); } } #[tokio::test] async fn test_invalid_json_response() { let _m = mock("GET", "/events/upcoming") .with_status(200) .with_header("content-type", "application/json") .with_body("invalid json {") .create(); let client = create_test_client(); let result = client.get_upcoming_events(None).await; assert!(result.is_err()); // Invalid JSON will likely cause an HTTP parsing error instead of JSON error // since the response can't be properly parsed as JSON if let Err(error) = result { // Could be either JSON or HTTP error depending on how reqwest handles it assert!(matches!(error, ChurchApiError::Json(_)) || matches!(error, ChurchApiError::Http(_))); } } #[tokio::test] async fn test_api_error_response() { let _m = mock("GET", "/events/upcoming") .with_status(200) .with_header("content-type", "application/json") .with_body(json!({ "success": false, "data": null, "error": "Custom API error message" }).to_string()) .create(); let client = create_test_client(); let result = client.get_upcoming_events(None).await; assert!(result.is_err()); if let Err(error) = result { assert!(matches!(error, ChurchApiError::Api(_))); if let ChurchApiError::Api(message) = error { assert_eq!(message, "Custom API error message"); } } } #[test] fn test_error_classification() { let auth_error = ChurchApiError::Auth("Invalid token".to_string()); assert!(!auth_error.is_network_error()); assert!(!auth_error.is_temporary()); assert!(auth_error.is_auth_error()); let rate_limit_error = ChurchApiError::RateLimit; assert!(!rate_limit_error.is_network_error()); assert!(rate_limit_error.is_temporary()); assert!(!rate_limit_error.is_auth_error()); let permission_error = ChurchApiError::PermissionDenied; assert!(!permission_error.is_network_error()); assert!(!permission_error.is_temporary()); assert!(permission_error.is_auth_error()); let not_found_error = ChurchApiError::NotFound; assert!(!not_found_error.is_network_error()); assert!(!not_found_error.is_temporary()); assert!(!not_found_error.is_auth_error()); } }