//! Integration tests for cdk-http-client using mockito use cdk_http_client::{HttpClient, HttpError}; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize, PartialEq)] struct TestPayload { name: String, value: i32, } #[derive(Debug, Serialize, Deserialize, PartialEq)] struct TestResponse { success: bool, data: String, } // === HttpClient::fetch tests === #[tokio::test] async fn test_fetch_success() { let mut server = mockito::Server::new_async().await; let mock = server .mock("GET", "/api/data") .with_status(200) .with_header("content-type", "application/json") .with_body(r#"{"success": true, "data": "hello"}"#) .create_async() .await; let client = HttpClient::new(); let url = format!("{}/api/data", server.url()); let result: Result = client.fetch(&url).await; assert!(result.is_ok()); let response = result.expect("Fetch should succeed"); assert!(response.success); assert_eq!(response.data, "hello"); mock.assert_async().await; } #[tokio::test] async fn test_fetch_error_status() { let mut server = mockito::Server::new_async().await; let mock = server .mock("GET", "/api/error") .with_status(404) .with_body("Not Found") .create_async() .await; let client = HttpClient::new(); let url = format!("{}/api/error", server.url()); let result: Result = client.fetch(&url).await; assert!(result.is_err()); if let Err(HttpError::Status { status, message }) = result { assert_eq!(status, 404); assert_eq!(message, "Not Found"); } else { panic!("Expected HttpError::Status"); } mock.assert_async().await; } #[tokio::test] async fn test_fetch_server_error() { let mut server = mockito::Server::new_async().await; let mock = server .mock("GET", "/api/server-error") .with_status(500) .with_body("Internal Server Error") .create_async() .await; let client = HttpClient::new(); let url = format!("{}/api/server-error", server.url()); let result: Result = client.fetch(&url).await; assert!(result.is_err()); if let Err(HttpError::Status { status, .. }) = result { assert_eq!(status, 500); } else { panic!("Expected HttpError::Status"); } mock.assert_async().await; } // === HttpClient::post_json tests === #[tokio::test] async fn test_post_json_success() { let mut server = mockito::Server::new_async().await; let mock = server .mock("POST", "/api/submit") .match_header("content-type", "application/json") .match_body(mockito::Matcher::Json(serde_json::json!({ "name": "test", "value": 42 }))) .with_status(200) .with_header("content-type", "application/json") .with_body(r#"{"success": true, "data": "received"}"#) .create_async() .await; let client = HttpClient::new(); let url = format!("{}/api/submit", server.url()); let payload = TestPayload { name: "test".to_string(), value: 42, }; let result: Result = client.post_json(&url, &payload).await; assert!(result.is_ok()); let response = result.expect("POST JSON should succeed"); assert!(response.success); assert_eq!(response.data, "received"); mock.assert_async().await; } #[tokio::test] async fn test_post_json_error_status() { let mut server = mockito::Server::new_async().await; let mock = server .mock("POST", "/api/submit") .with_status(400) .with_body("Bad Request") .create_async() .await; let client = HttpClient::new(); let url = format!("{}/api/submit", server.url()); let payload = TestPayload { name: "test".to_string(), value: 42, }; let result: Result = client.post_json(&url, &payload).await; assert!(result.is_err()); if let Err(HttpError::Status { status, message }) = result { assert_eq!(status, 400); assert_eq!(message, "Bad Request"); } else { panic!("Expected HttpError::Status"); } mock.assert_async().await; } // === HttpClient::get_raw tests === #[tokio::test] async fn test_get_raw_success() { let mut server = mockito::Server::new_async().await; let mock = server .mock("GET", "/api/raw") .with_status(200) .with_body("raw content") .create_async() .await; let client = HttpClient::new(); let url = format!("{}/api/raw", server.url()); let result = client.get_raw(&url).await; assert!(result.is_ok()); let response = result.expect("GET raw should succeed"); assert_eq!(response.status(), 200); assert!(response.is_success()); mock.assert_async().await; } // === RawResponse tests === #[tokio::test] async fn test_raw_response_is_success_with_200() { let mut server = mockito::Server::new_async().await; let mock = server .mock("GET", "/") .with_status(200) .create_async() .await; let client = HttpClient::new(); let response = client .get_raw(&server.url()) .await .expect("Request should succeed"); assert!(response.is_success()); assert!(!response.is_client_error()); assert!(!response.is_server_error()); assert_eq!(response.status(), 200); mock.assert_async().await; } #[tokio::test] async fn test_raw_response_is_success_with_201() { let mut server = mockito::Server::new_async().await; let mock = server .mock("GET", "/") .with_status(201) .create_async() .await; let client = HttpClient::new(); let response = client .get_raw(&server.url()) .await .expect("Request should succeed"); assert!(response.is_success()); assert_eq!(response.status(), 201); mock.assert_async().await; } #[tokio::test] async fn test_raw_response_is_success_with_299() { let mut server = mockito::Server::new_async().await; let mock = server .mock("GET", "/") .with_status(299) .create_async() .await; let client = HttpClient::new(); let response = client .get_raw(&server.url()) .await .expect("Request should succeed"); assert!(response.is_success()); assert_eq!(response.status(), 299); mock.assert_async().await; } #[tokio::test] async fn test_raw_response_is_not_success_with_100() { // Note: HTTP 1xx informational responses are special and may not be // fully supported by all HTTP libraries. We test with 100 Continue. // mockito may convert some 1xx codes to 500, so we just verify the // logic works with the boundary check (200..300). let mut server = mockito::Server::new_async().await; // Use 301 redirect as a more reliable "not success" boundary test let mock = server .mock("GET", "/") .with_status(301) .create_async() .await; let client = HttpClient::new(); let response = client .get_raw(&server.url()) .await .expect("Request should succeed"); // 301 is a redirect, not success assert!(!response.is_success()); assert_eq!(response.status(), 301); mock.assert_async().await; } #[tokio::test] async fn test_raw_response_is_not_success_with_300() { let mut server = mockito::Server::new_async().await; let mock = server .mock("GET", "/") .with_status(300) .create_async() .await; let client = HttpClient::new(); let response = client .get_raw(&server.url()) .await .expect("Request should succeed"); assert!(!response.is_success()); assert_eq!(response.status(), 300); mock.assert_async().await; } #[tokio::test] async fn test_raw_response_is_client_error_with_400() { let mut server = mockito::Server::new_async().await; let mock = server .mock("GET", "/") .with_status(400) .create_async() .await; let client = HttpClient::new(); let response = client .get_raw(&server.url()) .await .expect("Request should succeed"); assert!(response.is_client_error()); assert!(!response.is_success()); assert!(!response.is_server_error()); assert_eq!(response.status(), 400); mock.assert_async().await; } #[tokio::test] async fn test_raw_response_is_client_error_with_499() { let mut server = mockito::Server::new_async().await; let mock = server .mock("GET", "/") .with_status(499) .create_async() .await; let client = HttpClient::new(); let response = client .get_raw(&server.url()) .await .expect("Request should succeed"); assert!(response.is_client_error()); assert_eq!(response.status(), 499); mock.assert_async().await; } #[tokio::test] async fn test_raw_response_is_not_client_error_with_399() { let mut server = mockito::Server::new_async().await; let mock = server .mock("GET", "/") .with_status(399) .create_async() .await; let client = HttpClient::new(); let response = client .get_raw(&server.url()) .await .expect("Request should succeed"); assert!(!response.is_client_error()); assert_eq!(response.status(), 399); mock.assert_async().await; } #[tokio::test] async fn test_raw_response_is_server_error_with_500() { let mut server = mockito::Server::new_async().await; let mock = server .mock("GET", "/") .with_status(500) .create_async() .await; let client = HttpClient::new(); let response = client .get_raw(&server.url()) .await .expect("Request should succeed"); assert!(response.is_server_error()); assert!(!response.is_success()); assert!(!response.is_client_error()); assert_eq!(response.status(), 500); mock.assert_async().await; } #[tokio::test] async fn test_raw_response_is_server_error_with_599() { let mut server = mockito::Server::new_async().await; let mock = server .mock("GET", "/") .with_status(599) .create_async() .await; let client = HttpClient::new(); let response = client .get_raw(&server.url()) .await .expect("Request should succeed"); assert!(response.is_server_error()); assert_eq!(response.status(), 599); mock.assert_async().await; } #[tokio::test] async fn test_raw_response_is_not_server_error_with_499() { let mut server = mockito::Server::new_async().await; let mock = server .mock("GET", "/") .with_status(499) .create_async() .await; let client = HttpClient::new(); let response = client .get_raw(&server.url()) .await .expect("Request should succeed"); assert!(!response.is_server_error()); assert_eq!(response.status(), 499); mock.assert_async().await; } #[tokio::test] async fn test_raw_response_text() { let mut server = mockito::Server::new_async().await; let mock = server .mock("GET", "/") .with_status(200) .with_body("Hello, World!") .create_async() .await; let client = HttpClient::new(); let response = client .get_raw(&server.url()) .await .expect("Request should succeed"); let text = response.text().await.expect("Text extraction should succeed"); assert_eq!(text, "Hello, World!"); mock.assert_async().await; } #[tokio::test] async fn test_raw_response_json() { let mut server = mockito::Server::new_async().await; let mock = server .mock("GET", "/") .with_status(200) .with_header("content-type", "application/json") .with_body(r#"{"success": true, "data": "json_test"}"#) .create_async() .await; let client = HttpClient::new(); let response = client .get_raw(&server.url()) .await .expect("Request should succeed"); let json: TestResponse = response.json().await.expect("JSON parsing should succeed"); assert!(json.success); assert_eq!(json.data, "json_test"); mock.assert_async().await; } #[tokio::test] async fn test_raw_response_bytes() { let mut server = mockito::Server::new_async().await; let mock = server .mock("GET", "/") .with_status(200) .with_body(vec![0x01, 0x02, 0x03, 0x04]) .create_async() .await; let client = HttpClient::new(); let response = client .get_raw(&server.url()) .await .expect("Request should succeed"); let bytes = response.bytes().await.expect("Bytes extraction should succeed"); assert_eq!(bytes, vec![0x01, 0x02, 0x03, 0x04]); mock.assert_async().await; } // === RequestBuilder tests === #[tokio::test] async fn test_request_builder_send() { let mut server = mockito::Server::new_async().await; let mock = server .mock("GET", "/api/builder") .with_status(200) .with_body("builder response") .create_async() .await; let client = HttpClient::new(); let url = format!("{}/api/builder", server.url()); let response = client .get(&url) .send() .await .expect("Request should succeed"); assert_eq!(response.status(), 200); assert_eq!( response.text().await.expect("Text extraction should succeed"), "builder response" ); mock.assert_async().await; } #[tokio::test] async fn test_request_builder_send_json() { let mut server = mockito::Server::new_async().await; let mock = server .mock("POST", "/api/json") .with_status(200) .with_header("content-type", "application/json") .with_body(r#"{"success": true, "data": "builder_json"}"#) .create_async() .await; let client = HttpClient::new(); let url = format!("{}/api/json", server.url()); let payload = TestPayload { name: "builder".to_string(), value: 100, }; let result: TestResponse = client .post(&url) .json(&payload) .send_json() .await .expect("Request should succeed"); assert!(result.success); assert_eq!(result.data, "builder_json"); mock.assert_async().await; } #[tokio::test] async fn test_request_builder_with_headers() { let mut server = mockito::Server::new_async().await; let mock = server .mock("GET", "/api/headers") .match_header("X-Custom-Header", "custom-value") .match_header("Authorization", "Bearer token123") .with_status(200) .with_body("headers received") .create_async() .await; let client = HttpClient::new(); let url = format!("{}/api/headers", server.url()); let response = client .get(&url) .header("X-Custom-Header", "custom-value") .header("Authorization", "Bearer token123") .send() .await .expect("Request should succeed"); assert_eq!(response.status(), 200); assert_eq!( response.text().await.expect("Text extraction should succeed"), "headers received" ); mock.assert_async().await; } #[tokio::test] async fn test_request_builder_post_with_form() { let mut server = mockito::Server::new_async().await; let mock = server .mock("POST", "/api/form") .match_header( "content-type", mockito::Matcher::Regex("application/x-www-form-urlencoded.*".to_string()), ) .with_status(200) .with_header("content-type", "application/json") .with_body(r#"{"success": true, "data": "form_received"}"#) .create_async() .await; let client = HttpClient::new(); let url = format!("{}/api/form", server.url()); let form_data = [("field1", "value1"), ("field2", "value2")]; let response: TestResponse = client .post(&url) .form(&form_data) .send_json() .await .expect("Request should succeed"); assert!(response.success); assert_eq!(response.data, "form_received"); mock.assert_async().await; } #[tokio::test] async fn test_request_builder_patch() { let mut server = mockito::Server::new_async().await; let mock = server .mock("PATCH", "/api/resource") .with_status(200) .with_header("content-type", "application/json") .with_body(r#"{"success": true, "data": "patched"}"#) .create_async() .await; let client = HttpClient::new(); let url = format!("{}/api/resource", server.url()); let payload = TestPayload { name: "update".to_string(), value: 99, }; let result: TestResponse = client .patch(&url) .json(&payload) .send_json() .await .expect("Request should succeed"); assert!(result.success); assert_eq!(result.data, "patched"); mock.assert_async().await; } // === Convenience function test === #[tokio::test] async fn test_fetch_convenience_function() { let mut server = mockito::Server::new_async().await; let mock = server .mock("GET", "/api/convenience") .with_status(200) .with_header("content-type", "application/json") .with_body(r#"{"success": true, "data": "convenience"}"#) .create_async() .await; let url = format!("{}/api/convenience", server.url()); let result: Result = cdk_http_client::fetch(&url).await; assert!(result.is_ok()); let response = result.expect("Fetch should succeed"); assert!(response.success); assert_eq!(response.data, "convenience"); mock.assert_async().await; } // === Error handling tests === #[tokio::test] async fn test_json_deserialization_error() { let mut server = mockito::Server::new_async().await; let mock = server .mock("GET", "/api/invalid-json") .with_status(200) .with_header("content-type", "application/json") .with_body("not valid json") .create_async() .await; let client = HttpClient::new(); let url = format!("{}/api/invalid-json", server.url()); let result: Result = client.fetch(&url).await; assert!(result.is_err()); // The error should be about JSON parsing, which becomes HttpError::Other from reqwest let err = result.expect_err("Should be a deserialization error"); let err_str = format!("{}", err); assert!( err_str.contains("expected") || err_str.contains("JSON") || err_str.contains("error"), "Error should mention parsing issue: {}", err_str ); mock.assert_async().await; } #[tokio::test] async fn test_raw_response_json_deserialization_error() { let mut server = mockito::Server::new_async().await; let mock = server .mock("GET", "/") .with_status(200) .with_body("invalid json") .create_async() .await; let client = HttpClient::new(); let response = client .get_raw(&server.url()) .await .expect("Request should succeed"); let result: Result = response.json().await; assert!(result.is_err()); mock.assert_async().await; }