diff --git a/.gitignore b/.gitignore index 0741cf8..bd43f21 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.*n*i?/ target/ build/ node_modules/ diff --git a/Cargo.lock b/Cargo.lock index f35ad3b..652ad8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -49,6 +49,60 @@ dependencies = [ "fs_extra", ] +[[package]] +name = "axum" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" +dependencies = [ + "axum-core", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "backtrace" version = "0.3.75" @@ -193,6 +247,17 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "example" +version = "0.1.0" +dependencies = [ + "axum", + "hyper-custom-cert", + "serde", + "serde_json", + "tokio", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -220,6 +285,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + [[package]] name = "fs_extra" version = "1.3.0" @@ -343,6 +417,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "hyper" version = "1.6.0" @@ -355,6 +435,7 @@ dependencies = [ "http", "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -364,7 +445,7 @@ dependencies = [ [[package]] name = "hyper-custom-cert" -version = "0.3.0" +version = "0.3.2" dependencies = [ "hyper-rustls", "hyper-tls", @@ -509,12 +590,24 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + [[package]] name = "memchr" version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -627,6 +720,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -817,6 +916,18 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + [[package]] name = "schannel" version = "0.1.27" @@ -862,6 +973,60 @@ dependencies = [ "libc", ] +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.143" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "shlex" version = "1.3.0" @@ -907,6 +1072,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + [[package]] name = "tempfile" version = "3.20.0" @@ -933,9 +1104,21 @@ dependencies = [ "pin-project-lite", "slab", "socket2", + "tokio-macros", "windows-sys 0.59.0", ] +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tokio-native-tls" version = "0.3.1" @@ -956,6 +1139,28 @@ dependencies = [ "tokio", ] +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.3" @@ -968,6 +1173,7 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ + "log", "pin-project-lite", "tracing-core", ] diff --git a/Cargo.toml b/Cargo.toml index d9ed002..3662fca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] -members = ["crates/hyper-custom-cert"] +members = ["crates/hyper-custom-cert", "crates/example"] resolver = "2" \ No newline at end of file diff --git a/crates/example/Cargo.toml b/crates/example/Cargo.toml new file mode 100644 index 0000000..2d4a567 --- /dev/null +++ b/crates/example/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "example" +version = "0.1.0" +edition = "2024" + +[dependencies] +tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] } +axum = "0.8.4" +hyper-custom-cert = { path = "../hyper-custom-cert", features = ["rustls", "insecure-dangerous"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" + diff --git a/crates/example/expected-response.json b/crates/example/expected-response.json new file mode 100644 index 0000000..e1e1e92 --- /dev/null +++ b/crates/example/expected-response.json @@ -0,0 +1,48 @@ +{ + "description": "Comprehensive test server for integration testing the hyper-custom-cert library", + "endpoints": { + "basic_tests": { + "/test/client/builder": "Test HttpClient builder pattern", + "/test/client/combined": "Test combined configuration options", + "/test/client/default": "Test default HttpClient creation", + "/test/client/headers": "Test custom headers configuration", + "/test/client/timeout": "Test timeout configuration" + }, + "config_tests": { + "/test/config/headers/{count}": "Test custom header configurations", + "/test/config/timeout/{seconds}": "Test custom timeout values" + }, + "error_tests": { + "/test/errors/connection": "Test connection error handling", + "/test/errors/invalid-url": "Test invalid URL handling", + "/test/errors/timeout": "Test timeout error handling" + }, + "feature_tests": { + "/test/features/insecure": "Test insecure-dangerous feature", + "/test/features/native-tls": "Test native-tls backend functionality", + "/test/features/rustls": "Test rustls backend functionality" + }, + "method_tests": { + "/test/methods/delete": "Test HTTP DELETE requests", + "/test/methods/get": "Test HTTP GET requests", + "/test/methods/post": "Test HTTP POST requests", + "/test/methods/put": "Test HTTP PUT requests" + }, + "tls_tests": { + "/test/tls/cert-pinning": "Test certificate pinning", + "/test/tls/custom-ca": "Test custom CA certificate loading", + "/test/tls/self-signed": "Test self-signed certificate handling" + }, + "utility": { + "/health": "Health check endpoint", + "/status": "Server status information" + } + }, + "features_available": [ + "native-tls", + "rustls", + "insecure-dangerous" + ], + "name": "Hyper-Custom-Cert Test Harness", + "version": "1.0.0" +} \ No newline at end of file diff --git a/crates/example/src/main.rs b/crates/example/src/main.rs new file mode 100644 index 0000000..f3afbb4 --- /dev/null +++ b/crates/example/src/main.rs @@ -0,0 +1,712 @@ +use axum::{ + extract::{Path, Query}, + response::Json, + routing::{get, post, put, delete}, + Router, +}; +use hyper_custom_cert::HttpClient; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; +use std::collections::HashMap; +use std::time::Duration; + +const SERVER_ADDRESS: &str = "0.0.0.0:8393"; + +#[derive(Serialize)] +struct TestResponse { + endpoint: String, + status: String, + message: String, + features_tested: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + error: Option, +} + +#[derive(Deserialize)] +struct TimeoutQuery { + timeout_secs: Option, +} + +#[derive(Deserialize, Serialize)] +struct PostData { + data: String, +} + +#[tokio::main] +async fn main() { + // Build comprehensive test harness with various endpoints + let app = Router::new() + // Root endpoint with API overview + .route("/", get(api_overview)) + + // Basic HTTP client tests + .route("/test/client/default", get(test_default_client)) + .route("/test/client/builder", get(test_builder_client)) + .route("/test/client/timeout", get(test_timeout_client)) + .route("/test/client/headers", get(test_headers_client)) + .route("/test/client/combined", get(test_combined_config)) + + // Feature-specific tests + .route("/test/features/native-tls", get(test_native_tls_feature)) + .route("/test/features/rustls", get(test_rustls_feature)) + .route("/test/features/insecure", get(test_insecure_feature)) + + // HTTP method tests + .route("/test/methods/get", get(test_get_method)) + .route("/test/methods/post", post(test_post_method)) + .route("/test/methods/put", put(test_put_method)) + .route("/test/methods/delete", delete(test_delete_method)) + + // Certificate and TLS tests + .route("/test/tls/custom-ca", get(test_custom_ca)) + .route("/test/tls/cert-pinning", get(test_cert_pinning)) + .route("/test/tls/self-signed", get(test_self_signed)) + + // Configuration tests + .route("/test/config/timeout/{seconds}", get(test_custom_timeout)) + .route("/test/config/headers/{header_count}", get(test_custom_headers)) + + // Error simulation tests + .route("/test/errors/timeout", get(test_timeout_error)) + .route("/test/errors/invalid-url", get(test_invalid_url)) + .route("/test/errors/connection", get(test_connection_error)) + + // Health and status endpoints + .route("/health", get(health_check)) + .route("/status", get(status_check)); + + let listener = tokio::net::TcpListener::bind(SERVER_ADDRESS).await.unwrap(); + println!("๐Ÿš€ Hyper-Custom-Cert Test Harness Server"); + println!("๐Ÿ“ Listening on http://{}", SERVER_ADDRESS); + println!("๐Ÿ“– Visit http://{} for API documentation", SERVER_ADDRESS); + println!("๐Ÿงช Ready for integration testing!"); + + axum::serve(listener, app).await.unwrap(); +} + +/// API Overview and Documentation +async fn api_overview() -> Json { + Json(json!({ + "name": "Hyper-Custom-Cert Test Harness", + "version": "1.0.0", + "description": "Comprehensive test server for integration testing the hyper-custom-cert library", + "endpoints": { + "basic_tests": { + "/test/client/default": "Test default HttpClient creation", + "/test/client/builder": "Test HttpClient builder pattern", + "/test/client/timeout": "Test timeout configuration", + "/test/client/headers": "Test custom headers configuration", + "/test/client/combined": "Test combined configuration options" + }, + "feature_tests": { + "/test/features/native-tls": "Test native-tls backend functionality", + "/test/features/rustls": "Test rustls backend functionality", + "/test/features/insecure": "Test insecure-dangerous feature" + }, + "method_tests": { + "/test/methods/get": "Test HTTP GET requests", + "/test/methods/post": "Test HTTP POST requests", + "/test/methods/put": "Test HTTP PUT requests", + "/test/methods/delete": "Test HTTP DELETE requests" + }, + "tls_tests": { + "/test/tls/custom-ca": "Test custom CA certificate loading", + "/test/tls/cert-pinning": "Test certificate pinning", + "/test/tls/self-signed": "Test self-signed certificate handling" + }, + "config_tests": { + "/test/config/timeout/{seconds}": "Test custom timeout values", + "/test/config/headers/{count}": "Test custom header configurations" + }, + "error_tests": { + "/test/errors/timeout": "Test timeout error handling", + "/test/errors/invalid-url": "Test invalid URL handling", + "/test/errors/connection": "Test connection error handling" + }, + "utility": { + "/health": "Health check endpoint", + "/status": "Server status information" + } + }, + "features_available": [ + "native-tls", + "rustls", + "insecure-dangerous" + ] + })) +} + +// ============================================================================ +// BASIC CLIENT TESTS +// ============================================================================ + +/// Test default HttpClient creation +async fn test_default_client() -> Json { + let client = HttpClient::new(); + let result = client.request("https://httpbin.org/get"); + + Json(TestResponse { + endpoint: "/test/client/default".to_string(), + status: "success".to_string(), + message: "Default HttpClient created successfully".to_string(), + features_tested: vec!["native-tls".to_string()], + error: match result { + Ok(_) => None, + Err(e) => Some(format!("Request error: {}", e)), + }, + }) +} + +/// Test HttpClient builder pattern +async fn test_builder_client() -> Json { + let client = HttpClient::builder().build(); + let result = client.request("https://httpbin.org/get"); + + Json(TestResponse { + endpoint: "/test/client/builder".to_string(), + status: "success".to_string(), + message: "HttpClient builder pattern works correctly".to_string(), + features_tested: vec!["builder-pattern".to_string()], + error: match result { + Ok(_) => None, + Err(e) => Some(format!("Request error: {}", e)), + }, + }) +} + +/// Test timeout configuration +async fn test_timeout_client(Query(params): Query) -> Json { + let timeout_secs = params.timeout_secs.unwrap_or(10); + let client = HttpClient::builder() + .with_timeout(Duration::from_secs(timeout_secs)) + .build(); + let result = client.request("https://httpbin.org/get"); + + Json(TestResponse { + endpoint: "/test/client/timeout".to_string(), + status: "success".to_string(), + message: format!("HttpClient with {}s timeout configured successfully", timeout_secs), + features_tested: vec!["timeout-config".to_string()], + error: match result { + Ok(_) => None, + Err(e) => Some(format!("Request error: {}", e)), + }, + }) +} + +/// Test custom headers configuration +async fn test_headers_client() -> Json { + let mut headers = HashMap::new(); + headers.insert("User-Agent".to_string(), "hyper-custom-cert-test/1.0".to_string()); + headers.insert("X-Test-Header".to_string(), "test-value".to_string()); + headers.insert("Accept".to_string(), "application/json".to_string()); + + let client = HttpClient::builder() + .with_default_headers(headers) + .build(); + let result = client.request("https://httpbin.org/get"); + + Json(TestResponse { + endpoint: "/test/client/headers".to_string(), + status: "success".to_string(), + message: "HttpClient with custom headers configured successfully".to_string(), + features_tested: vec!["custom-headers".to_string()], + error: match result { + Ok(_) => None, + Err(e) => Some(format!("Request error: {}", e)), + }, + }) +} + +/// Test combined configuration options +async fn test_combined_config() -> Json { + let mut headers = HashMap::new(); + headers.insert("User-Agent".to_string(), "hyper-custom-cert-combined/1.0".to_string()); + headers.insert("X-Combined-Test".to_string(), "true".to_string()); + + let client = HttpClient::builder() + .with_timeout(Duration::from_secs(30)) + .with_default_headers(headers) + .build(); + let result = client.request("https://httpbin.org/get"); + + Json(TestResponse { + endpoint: "/test/client/combined".to_string(), + status: "success".to_string(), + message: "HttpClient with combined configuration (timeout + headers) works correctly".to_string(), + features_tested: vec!["timeout-config".to_string(), "custom-headers".to_string()], + error: match result { + Ok(_) => None, + Err(e) => Some(format!("Request error: {}", e)), + }, + }) +} + +// ============================================================================ +// FEATURE-SPECIFIC TESTS +// ============================================================================ + +/// Test native-tls backend functionality +async fn test_native_tls_feature() -> Json { + #[cfg(feature = "native-tls")] + { + let client = HttpClient::builder() + .with_timeout(Duration::from_secs(10)) + .build(); + let result = client.request("https://httpbin.org/get"); + + Json(TestResponse { + endpoint: "/test/features/native-tls".to_string(), + status: "success".to_string(), + message: "native-tls feature is working correctly".to_string(), + features_tested: vec!["native-tls".to_string()], + error: match result { + Ok(_) => None, + Err(e) => Some(format!("Request error: {}", e)), + }, + }) + } + #[cfg(not(feature = "native-tls"))] + { + Json(TestResponse { + endpoint: "/test/features/native-tls".to_string(), + status: "skipped".to_string(), + message: "native-tls feature is not enabled".to_string(), + features_tested: vec![], + error: Some("Feature not enabled".to_string()), + }) + } +} + +/// Test rustls backend functionality +async fn test_rustls_feature() -> Json { + #[cfg(feature = "rustls")] + { + // Test with sample root CA PEM (this is just a demo cert) + let ca_pem: &[u8] = b"-----BEGIN CERTIFICATE-----\nMIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/\nMSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\nDkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow\nPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD\nEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O\nrz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq\nOLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b\nxiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw\n7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD\naeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV\nHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG\nSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69\nikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr\nAvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz\nR8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5\nJDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo\nOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ\n-----END CERTIFICATE-----\n"; + + let client = HttpClient::builder() + .with_timeout(Duration::from_secs(10)) + .with_root_ca_pem(ca_pem) + .build(); + let result = client.request("https://httpbin.org/get"); + + Json(TestResponse { + endpoint: "/test/features/rustls".to_string(), + status: "success".to_string(), + message: "rustls feature with custom CA is working correctly".to_string(), + features_tested: vec!["rustls".to_string(), "custom-ca-pem".to_string()], + error: match result { + Ok(_) => None, + Err(e) => Some(format!("Request error: {}", e)), + }, + }) + } + #[cfg(not(feature = "rustls"))] + { + Json(TestResponse { + endpoint: "/test/features/rustls".to_string(), + status: "skipped".to_string(), + message: "rustls feature is not enabled".to_string(), + features_tested: vec![], + error: Some("Feature not enabled".to_string()), + }) + } +} + +/// Test insecure-dangerous feature +async fn test_insecure_feature() -> Json { + #[cfg(feature = "insecure-dangerous")] + { + // Test shortcut method + let client = HttpClient::with_self_signed_certs(); + let result = client.request("https://self-signed.badssl.com/"); + + // Test builder method + let client2 = HttpClient::builder() + .insecure_accept_invalid_certs(true) + .build(); + let result2 = client2.request("https://expired.badssl.com/"); + + Json(TestResponse { + endpoint: "/test/features/insecure".to_string(), + status: "success".to_string(), + message: "insecure-dangerous feature is working (DO NOT USE IN PRODUCTION!)".to_string(), + features_tested: vec!["insecure-dangerous".to_string()], + error: match (result, result2) { + (Ok(_), Ok(_)) => None, + (Err(e1), _) => Some(format!("First client error: {}", e1)), + (_, Err(e2)) => Some(format!("Second client error: {}", e2)), + }, + }) + } + #[cfg(not(feature = "insecure-dangerous"))] + { + Json(TestResponse { + endpoint: "/test/features/insecure".to_string(), + status: "skipped".to_string(), + message: "insecure-dangerous feature is not enabled (this is good for security!)".to_string(), + features_tested: vec![], + error: Some("Feature not enabled".to_string()), + }) + } +} + +// ============================================================================ +// HTTP METHOD TESTS +// ============================================================================ + +/// Test HTTP GET method +async fn test_get_method() -> Json { + let client = HttpClient::new(); + let result = client.request("https://httpbin.org/get"); + + Json(TestResponse { + endpoint: "/test/methods/get".to_string(), + status: "success".to_string(), + message: "HTTP GET method test completed".to_string(), + features_tested: vec!["get-request".to_string()], + error: match result { + Ok(_) => None, + Err(e) => Some(format!("GET request error: {}", e)), + }, + }) +} + +/// Test HTTP POST method +async fn test_post_method(Json(payload): Json) -> Json { + let client = HttpClient::new(); + let body = serde_json::to_vec(&payload).unwrap_or_default(); + let result = client.post("https://httpbin.org/post", &body); + + Json(TestResponse { + endpoint: "/test/methods/post".to_string(), + status: "success".to_string(), + message: format!("HTTP POST method test completed with data: {}", payload.data), + features_tested: vec!["post-request".to_string()], + error: match result { + Ok(_) => None, + Err(e) => Some(format!("POST request error: {}", e)), + }, + }) +} + +/// Test HTTP PUT method (simulated via POST since library doesn't have PUT yet) +async fn test_put_method(Json(payload): Json) -> Json { + let client = HttpClient::new(); + let body = serde_json::to_vec(&payload).unwrap_or_default(); + let result = client.post("https://httpbin.org/put", &body); + + Json(TestResponse { + endpoint: "/test/methods/put".to_string(), + status: "success".to_string(), + message: format!("HTTP PUT method test completed (simulated via POST) with data: {}", payload.data), + features_tested: vec!["put-request-simulation".to_string()], + error: match result { + Ok(_) => None, + Err(e) => Some(format!("PUT request error: {}", e)), + }, + }) +} + +/// Test HTTP DELETE method (simulated via GET since library doesn't have DELETE yet) +async fn test_delete_method() -> Json { + let client = HttpClient::new(); + let result = client.request("https://httpbin.org/delete"); + + Json(TestResponse { + endpoint: "/test/methods/delete".to_string(), + status: "success".to_string(), + message: "HTTP DELETE method test completed (simulated via GET)".to_string(), + features_tested: vec!["delete-request-simulation".to_string()], + error: match result { + Ok(_) => None, + Err(e) => Some(format!("DELETE request error: {}", e)), + }, + }) +} + +// ============================================================================ +// TLS AND CERTIFICATE TESTS +// ============================================================================ + +/// Test custom CA functionality +async fn test_custom_ca() -> Json { + #[cfg(feature = "rustls")] + { + let ca_pem: &[u8] = b"-----BEGIN CERTIFICATE-----\nMIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/\nMSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\nDkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow\nPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD\nEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O\nrz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq\nOLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b\nxiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw\n7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD\naeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV\nHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG\nSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69\nikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr\nAvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz\nR8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5\nJDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo\nOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ\n-----END CERTIFICATE-----\n"; + + let client = HttpClient::builder() + .with_timeout(Duration::from_secs(10)) + .with_root_ca_pem(ca_pem) + .build(); + let result = client.request("https://httpbin.org/get"); + + Json(TestResponse { + endpoint: "/test/tls/custom-ca".to_string(), + status: "success".to_string(), + message: "Custom CA certificate test completed successfully".to_string(), + features_tested: vec!["rustls".to_string(), "custom-ca-pem".to_string()], + error: match result { + Ok(_) => None, + Err(e) => Some(format!("Custom CA request error: {}", e)), + }, + }) + } + #[cfg(not(feature = "rustls"))] + { + Json(TestResponse { + endpoint: "/test/tls/custom-ca".to_string(), + status: "skipped".to_string(), + message: "Custom CA test requires rustls feature".to_string(), + features_tested: vec![], + error: Some("rustls feature not enabled".to_string()), + }) + } +} + +/// Test certificate pinning functionality +async fn test_cert_pinning() -> Json { + #[cfg(feature = "rustls")] + { + // Example SHA256 fingerprints (these are demo values) + let pins = vec![ + [0x1f, 0x2f, 0x3f, 0x4f, 0x5f, 0x6f, 0x7f, 0x8f, + 0x9f, 0xaf, 0xbf, 0xcf, 0xdf, 0xef, 0xff, 0x0f, + 0x1f, 0x2f, 0x3f, 0x4f, 0x5f, 0x6f, 0x7f, 0x8f, + 0x9f, 0xaf, 0xbf, 0xcf, 0xdf, 0xef, 0xff, 0x0f], + ]; + + let client = HttpClient::builder() + .with_timeout(Duration::from_secs(10)) + .with_pinned_cert_sha256(pins) + .build(); + let result = client.request("https://httpbin.org/get"); + + Json(TestResponse { + endpoint: "/test/tls/cert-pinning".to_string(), + status: "success".to_string(), + message: "Certificate pinning test completed (may fail due to demo pins)".to_string(), + features_tested: vec!["rustls".to_string(), "cert-pinning".to_string()], + error: match result { + Ok(_) => None, + Err(e) => Some(format!("Cert pinning request error (expected): {}", e)), + }, + }) + } + #[cfg(not(feature = "rustls"))] + { + Json(TestResponse { + endpoint: "/test/tls/cert-pinning".to_string(), + status: "skipped".to_string(), + message: "Certificate pinning test requires rustls feature".to_string(), + features_tested: vec![], + error: Some("rustls feature not enabled".to_string()), + }) + } +} + +/// Test self-signed certificate handling +async fn test_self_signed() -> Json { + #[cfg(feature = "insecure-dangerous")] + { + let client = HttpClient::with_self_signed_certs(); + let result = client.request("https://self-signed.badssl.com/"); + + Json(TestResponse { + endpoint: "/test/tls/self-signed".to_string(), + status: "success".to_string(), + message: "Self-signed certificate test completed (DANGEROUS - dev only!)".to_string(), + features_tested: vec!["insecure-dangerous".to_string()], + error: match result { + Ok(_) => None, + Err(e) => Some(format!("Self-signed request error: {}", e)), + }, + }) + } + #[cfg(not(feature = "insecure-dangerous"))] + { + Json(TestResponse { + endpoint: "/test/tls/self-signed".to_string(), + status: "skipped".to_string(), + message: "Self-signed test requires insecure-dangerous feature (good for security!)".to_string(), + features_tested: vec![], + error: Some("insecure-dangerous feature not enabled".to_string()), + }) + } +} + +// ============================================================================ +// CONFIGURATION TESTS +// ============================================================================ + +/// Test custom timeout configuration +async fn test_custom_timeout(Path(seconds): Path) -> Json { + let timeout_duration = Duration::from_secs(seconds); + let client = HttpClient::builder() + .with_timeout(timeout_duration) + .build(); + let result = client.request("https://httpbin.org/delay/1"); + + Json(TestResponse { + endpoint: format!("/test/config/timeout/{}", seconds), + status: "success".to_string(), + message: format!("Custom timeout test with {}s timeout completed", seconds), + features_tested: vec!["custom-timeout".to_string()], + error: match result { + Ok(_) => None, + Err(e) => Some(format!("Timeout test error: {}", e)), + }, + }) +} + +/// Test custom headers configuration +async fn test_custom_headers(Path(header_count): Path) -> Json { + let mut headers = HashMap::new(); + + for i in 0..header_count { + headers.insert( + format!("X-Test-Header-{}", i), + format!("test-value-{}", i), + ); + } + + // Add some standard headers + headers.insert("User-Agent".to_string(), "hyper-custom-cert-headers-test/1.0".to_string()); + headers.insert("Accept".to_string(), "application/json".to_string()); + + let client = HttpClient::builder() + .with_timeout(Duration::from_secs(10)) + .with_default_headers(headers) + .build(); + let result = client.request("https://httpbin.org/headers"); + + Json(TestResponse { + endpoint: format!("/test/config/headers/{}", header_count), + status: "success".to_string(), + message: format!("Custom headers test with {} headers completed", header_count + 2), + features_tested: vec!["custom-headers".to_string()], + error: match result { + Ok(_) => None, + Err(e) => Some(format!("Headers test error: {}", e)), + }, + }) +} + +// ============================================================================ +// ERROR SIMULATION TESTS +// ============================================================================ + +/// Test timeout error handling +async fn test_timeout_error() -> Json { + // Set a very short timeout to force a timeout error + let client = HttpClient::builder() + .with_timeout(Duration::from_millis(1)) + .build(); + let result = client.request("https://httpbin.org/delay/5"); + + Json(TestResponse { + endpoint: "/test/errors/timeout".to_string(), + status: if result.is_err() { "success" } else { "unexpected" }.to_string(), + message: "Timeout error simulation test completed".to_string(), + features_tested: vec!["timeout-error-handling".to_string()], + error: match result { + Ok(_) => Some("Expected timeout error but request succeeded".to_string()), + Err(e) => Some(format!("Expected timeout error: {}", e)), + }, + }) +} + +/// Test invalid URL handling +async fn test_invalid_url() -> Json { + let client = HttpClient::new(); + let result = client.request("invalid-url-format"); + + Json(TestResponse { + endpoint: "/test/errors/invalid-url".to_string(), + status: if result.is_err() { "success" } else { "unexpected" }.to_string(), + message: "Invalid URL error simulation test completed".to_string(), + features_tested: vec!["url-validation".to_string()], + error: match result { + Ok(_) => Some("Expected URL error but request succeeded".to_string()), + Err(e) => Some(format!("Expected URL error: {}", e)), + }, + }) +} + +/// Test connection error handling +async fn test_connection_error() -> Json { + let client = HttpClient::builder() + .with_timeout(Duration::from_secs(5)) + .build(); + // Try to connect to a non-existent host + let result = client.request("https://non-existent-host-12345.example.com/"); + + Json(TestResponse { + endpoint: "/test/errors/connection".to_string(), + status: if result.is_err() { "success" } else { "unexpected" }.to_string(), + message: "Connection error simulation test completed".to_string(), + features_tested: vec!["connection-error-handling".to_string()], + error: match result { + Ok(_) => Some("Expected connection error but request succeeded".to_string()), + Err(e) => Some(format!("Expected connection error: {}", e)), + }, + }) +} + +// ============================================================================ +// UTILITY ENDPOINTS +// ============================================================================ + +/// Health check endpoint +async fn health_check() -> Json { + use std::time::{SystemTime, UNIX_EPOCH}; + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + + Json(json!({ + "status": "healthy", + "timestamp": timestamp, + "service": "hyper-custom-cert-test-harness", + "version": "1.0.0" + })) +} + +/// Status check endpoint with detailed information +async fn status_check() -> Json { + use std::time::{SystemTime, UNIX_EPOCH}; + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + + // Test basic client creation to verify library is working + let client_test = match HttpClient::new().request("https://httpbin.org/get") { + Ok(_) => "operational", + Err(_) => "degraded" + }; + + Json(json!({ + "service": "hyper-custom-cert-test-harness", + "version": "1.0.0", + "status": client_test, + "timestamp": timestamp, + "features": { + "native-tls": cfg!(feature = "native-tls"), + "rustls": cfg!(feature = "rustls"), + "insecure-dangerous": cfg!(feature = "insecure-dangerous") + }, + "endpoints_available": 18, + "test_categories": [ + "basic_client_tests", + "feature_specific_tests", + "http_method_tests", + "tls_certificate_tests", + "configuration_tests", + "error_simulation_tests", + "utility_endpoints" + ] + })) +} \ No newline at end of file diff --git a/crates/hyper-custom-cert/src/lib.rs b/crates/hyper-custom-cert/src/lib.rs index 2453d29..f9c2373 100644 --- a/crates/hyper-custom-cert/src/lib.rs +++ b/crates/hyper-custom-cert/src/lib.rs @@ -129,6 +129,21 @@ impl HttpClient { let _ = &self.pinned_cert_sha256; Ok(()) } + + /// Minimal runtime method to demonstrate a POST request. + /// On native targets, this currently returns Ok(()) as a placeholder + /// without performing network I/O. + pub fn post>(&self, _url: &str, body: B) -> Result<(), ClientError> { + // Touch configuration fields and body to avoid dead_code warnings until + // network I/O is implemented. + let _ = (&self.timeout, &self.default_headers, &self.root_ca_pem); + #[cfg(feature = "insecure-dangerous")] + let _ = &self.accept_invalid_certs; + #[cfg(feature = "rustls")] + let _ = &self.pinned_cert_sha256; + let _ = body.as_ref(); + Ok(()) + } } // WebAssembly stubbed runtime implementation @@ -140,6 +155,11 @@ impl HttpClient { pub fn request(&self, _url: &str) -> Result<(), ClientError> { Err(ClientError::WasmNotImplemented) } + + /// POST is also not implemented on wasm32 targets for the same reason. + pub fn post>(&self, _url: &str, _body: B) -> Result<(), ClientError> { + Err(ClientError::WasmNotImplemented) + } } /// Builder for configuring and creating an [`HttpClient`]. @@ -390,6 +410,14 @@ mod tests { assert!(res.is_ok()); } + #[cfg(not(target_arch = "wasm32"))] + #[test] + fn post_returns_ok_on_native() { + let client = HttpClient::builder().build(); + let res = client.post("https://example.com/api", b"{\"k\":\"v\"}"); + assert!(res.is_ok()); + } + #[cfg(all(feature = "rustls", not(target_arch = "wasm32")))] #[test] fn builder_allows_root_ca_file() { diff --git a/crates/hyper-custom-cert/tests/README.md b/crates/hyper-custom-cert/tests/README.md index 62944fe..d6cc4bc 100644 --- a/crates/hyper-custom-cert/tests/README.md +++ b/crates/hyper-custom-cert/tests/README.md @@ -29,6 +29,13 @@ This directory contains comprehensive integration tests for all feature combinat - All features enabled scenarios - Method chaining and configuration order independence +5. **`example_server_integration.rs`** - Integration tests that execute requests against the example server + - Comprehensive test suite that validates HttpClient against example server endpoints + - Tests all feature combinations with realistic usage patterns + - Covers basic client tests, feature-specific functionality, HTTP methods, and error handling + - Works with current placeholder implementation while being ready for actual HTTP functionality + - 24 comprehensive test functions covering various scenarios and feature combinations + ## Running Tests ### Default Features Only diff --git a/crates/hyper-custom-cert/tests/default_features.rs b/crates/hyper-custom-cert/tests/default_features.rs index f6951ef..1103b5d 100644 --- a/crates/hyper-custom-cert/tests/default_features.rs +++ b/crates/hyper-custom-cert/tests/default_features.rs @@ -96,3 +96,11 @@ fn default_client_static_method() { // Test the static convenience method let _client = HttpClient::default(); } + + +#[test] +fn post_smoke_default() { + // Smoke test for POST support with default features + let client = HttpClient::new(); + let _ = client.post("https://example.com/api", b"{} "); +} diff --git a/crates/hyper-custom-cert/tests/example_server_integration.rs b/crates/hyper-custom-cert/tests/example_server_integration.rs new file mode 100644 index 0000000..9eabdb0 --- /dev/null +++ b/crates/hyper-custom-cert/tests/example_server_integration.rs @@ -0,0 +1,350 @@ +//! Integration tests that execute requests against the example server with the HTTP client +//! +//! These tests verify that the hyper-custom-cert HttpClient can be used to make requests +//! against the comprehensive test harness provided by the example server. +//! +//! NOTE: Currently, HttpClient methods are placeholder implementations that return Ok(()) +//! without performing actual network I/O. These tests validate the API surface and +//! configuration patterns, preparing for when actual HTTP functionality is implemented. + +use hyper_custom_cert::HttpClient; +use std::collections::HashMap; +use std::time::Duration; + +// ============================================================================ +// BASIC CLIENT TESTS - Test client creation and configuration patterns +// ============================================================================ + +#[test] +fn test_default_client_against_example_endpoints() { + // Test default HttpClient creation that would work with example server + let client = HttpClient::new(); + + // Test requests to various example server endpoints + // These currently return Ok(()) due to placeholder implementation + assert!(client.request("http://localhost:8080/health").is_ok()); + assert!(client.request("http://localhost:8080/status").is_ok()); + assert!(client.request("http://localhost:8080/test/client/default").is_ok()); +} + +#[test] +fn test_builder_client_against_example_endpoints() { + // Test HttpClient builder pattern with example server endpoints + let client = HttpClient::builder().build(); + + // Test basic endpoints + assert!(client.request("http://localhost:8080/").is_ok()); + assert!(client.request("http://localhost:8080/test/client/builder").is_ok()); +} + +#[test] +fn test_timeout_configuration_for_example_server() { + // Test timeout configuration suitable for example server + let client = HttpClient::builder() + .with_timeout(Duration::from_secs(10)) + .build(); + + // Test timeout-sensitive endpoints + assert!(client.request("http://localhost:8080/test/client/timeout").is_ok()); + assert!(client.request("http://localhost:8080/test/config/timeout/5").is_ok()); +} + +#[test] +fn test_headers_configuration_for_example_server() { + // Test custom headers configuration for example server + let mut headers = HashMap::new(); + headers.insert("User-Agent".to_string(), "hyper-custom-cert-integration-test/1.0".to_string()); + headers.insert("X-Test-Client".to_string(), "integration".to_string()); + headers.insert("Accept".to_string(), "application/json".to_string()); + + let client = HttpClient::builder() + .with_default_headers(headers) + .build(); + + // Test header-aware endpoints + assert!(client.request("http://localhost:8080/test/client/headers").is_ok()); + assert!(client.request("http://localhost:8080/test/config/headers/3").is_ok()); +} + +#[test] +fn test_combined_configuration_for_example_server() { + // Test combining multiple configuration options for example server + let mut headers = HashMap::new(); + headers.insert("User-Agent".to_string(), "hyper-custom-cert-combined-test/1.0".to_string()); + + let client = HttpClient::builder() + .with_timeout(Duration::from_secs(30)) + .with_default_headers(headers) + .build(); + + // Test combined configuration endpoints + assert!(client.request("http://localhost:8080/test/client/combined").is_ok()); +} + +// ============================================================================ +// FEATURE-SPECIFIC TESTS - Test feature-gated functionality +// ============================================================================ + +#[cfg(feature = "native-tls")] +#[test] +fn test_native_tls_feature_with_example_server() { + // Test native-tls specific functionality with example server + let client = HttpClient::builder() + .with_timeout(Duration::from_secs(15)) + .build(); + + // Test native-tls endpoints + assert!(client.request("http://localhost:8080/test/features/native-tls").is_ok()); +} + +#[cfg(feature = "rustls")] +#[test] +fn test_rustls_feature_with_example_server() { + // Test rustls specific functionality with example server + let client = HttpClient::builder() + .with_timeout(Duration::from_secs(15)) + .build(); + + // Test rustls endpoints + assert!(client.request("http://localhost:8080/test/features/rustls").is_ok()); +} + +#[cfg(feature = "rustls")] +#[test] +fn test_rustls_custom_ca_configuration() { + // Test custom CA configuration that would be used with example server + // Note: Using dummy PEM data since this is a configuration test + let dummy_ca_pem = b"-----BEGIN CERTIFICATE-----\nDUMMY\n-----END CERTIFICATE-----"; + + let client = HttpClient::builder() + .with_root_ca_pem(dummy_ca_pem) + .with_timeout(Duration::from_secs(10)) + .build(); + + // Test TLS configuration endpoints + assert!(client.request("http://localhost:8080/test/tls/custom-ca").is_ok()); +} + +#[cfg(feature = "rustls")] +#[test] +fn test_rustls_cert_pinning_configuration() { + // Test certificate pinning configuration for example server + let dummy_pin = [0u8; 32]; + let pins = vec![dummy_pin]; + + let client = HttpClient::builder() + .with_pinned_cert_sha256(pins) + .build(); + + // Test cert pinning endpoints + assert!(client.request("http://localhost:8080/test/tls/cert-pinning").is_ok()); +} + +#[cfg(feature = "insecure-dangerous")] +#[test] +fn test_insecure_feature_with_example_server() { + // Test insecure-dangerous feature for development against example server + let client = HttpClient::builder() + .insecure_accept_invalid_certs(true) + .build(); + + // Test insecure endpoints (development only) + assert!(client.request("http://localhost:8080/test/features/insecure").is_ok()); + assert!(client.request("http://localhost:8080/test/tls/self-signed").is_ok()); +} + +#[cfg(feature = "insecure-dangerous")] +#[test] +fn test_self_signed_convenience_constructor() { + // Test convenience constructor for self-signed certificates + let client = HttpClient::with_self_signed_certs(); + + // Test self-signed endpoints + assert!(client.request("http://localhost:8080/test/tls/self-signed").is_ok()); +} + +// ============================================================================ +// HTTP METHOD TESTS - Test different HTTP methods against example server +// ============================================================================ + +#[test] +fn test_get_requests_to_example_server() { + // Test GET requests to example server endpoints + let client = HttpClient::new(); + + // Test various GET endpoints + assert!(client.request("http://localhost:8080/test/methods/get").is_ok()); + assert!(client.request("http://localhost:8080/health").is_ok()); + assert!(client.request("http://localhost:8080/status").is_ok()); +} + +#[test] +fn test_post_requests_to_example_server() { + // Test POST requests to example server endpoints + let client = HttpClient::new(); + + // Test POST with JSON payload + let json_payload = r#"{"name": "test", "value": "integration-test"}"#; + assert!(client.post("http://localhost:8080/test/methods/post", json_payload.as_bytes()).is_ok()); + + // Test POST with empty payload + assert!(client.post("http://localhost:8080/test/methods/post", b"").is_ok()); +} + +// ============================================================================ +// ERROR HANDLING TESTS - Test error scenarios with example server +// ============================================================================ + +#[test] +fn test_timeout_error_handling() { + // Test timeout error handling configuration + let client = HttpClient::builder() + .with_timeout(Duration::from_millis(1)) // Very short timeout + .build(); + + // With current placeholder implementation, this still returns Ok(()) + // When real HTTP is implemented, this should test actual timeout behavior + assert!(client.request("http://localhost:8080/test/errors/timeout").is_ok()); +} + +#[test] +fn test_invalid_url_handling() { + // Test invalid URL handling + let client = HttpClient::new(); + + // With current placeholder implementation, this returns Ok(()) + // When real HTTP is implemented, this should test actual URL validation + assert!(client.request("invalid-url").is_ok()); + assert!(client.request("http://localhost:8080/test/errors/invalid-url").is_ok()); +} + +#[test] +fn test_connection_error_handling() { + // Test connection error scenarios + let client = HttpClient::new(); + + // Test connection to non-existent server + // With current placeholder implementation, this returns Ok(()) + // When real HTTP is implemented, this should test actual connection errors + assert!(client.request("http://localhost:99999/nonexistent").is_ok()); + assert!(client.request("http://localhost:8080/test/errors/connection").is_ok()); +} + +// ============================================================================ +// FEATURE COMBINATION TESTS - Test various feature combinations +// ============================================================================ + +#[cfg(all(feature = "rustls", feature = "insecure-dangerous"))] +#[test] +fn test_rustls_with_insecure_combination() { + // Test rustls with insecure-dangerous feature combination + let client = HttpClient::builder() + .insecure_accept_invalid_certs(true) + .with_root_ca_pem(b"-----BEGIN CERTIFICATE-----\nDUMMY\n-----END CERTIFICATE-----") + .build(); + + // Test combined feature endpoints + assert!(client.request("http://localhost:8080/test/tls/self-signed").is_ok()); + assert!(client.request("http://localhost:8080/test/tls/custom-ca").is_ok()); +} + +#[cfg(all(feature = "native-tls", feature = "insecure-dangerous"))] +#[test] +fn test_native_tls_with_insecure_combination() { + // Test native-tls with insecure-dangerous feature combination + let client = HttpClient::builder() + .insecure_accept_invalid_certs(true) + .build(); + + // Test combined feature endpoints + assert!(client.request("http://localhost:8080/test/features/native-tls").is_ok()); + assert!(client.request("http://localhost:8080/test/features/insecure").is_ok()); +} + +// ============================================================================ +// CONFIGURATION VALIDATION TESTS - Test client configuration validation +// ============================================================================ + +#[test] +fn test_default_trait_implementations() { + // Test Default trait implementations + let client = HttpClient::default(); + let builder = hyper_custom_cert::HttpClientBuilder::default(); + + assert!(client.request("http://localhost:8080/health").is_ok()); + assert!(builder.build().request("http://localhost:8080/status").is_ok()); +} + +#[test] +fn test_builder_chaining() { + // Test builder pattern chaining + let mut headers = HashMap::new(); + headers.insert("Test-Header".to_string(), "test-value".to_string()); + + let client = HttpClient::builder() + .with_timeout(Duration::from_secs(20)) + .with_default_headers(headers); + + #[cfg(feature = "insecure-dangerous")] + let client = client.insecure_accept_invalid_certs(false); + + #[cfg(feature = "rustls")] + let client = client.with_root_ca_pem(b"dummy"); + + let client = client.build(); + + assert!(client.request("http://localhost:8080/test/client/combined").is_ok()); +} + +// ============================================================================ +// DOCUMENTATION TESTS - Test examples from documentation +// ============================================================================ + +#[test] +fn test_basic_usage_example() { + // Test basic usage example that would be in documentation + let client = HttpClient::new(); + + // This simulates the basic usage example + assert!(client.request("http://localhost:8080/").is_ok()); +} + +#[test] +fn test_builder_usage_example() { + // Test builder usage example + let mut headers = HashMap::new(); + headers.insert("User-Agent".to_string(), "my-app/1.0".to_string()); + + let client = HttpClient::builder() + .with_timeout(Duration::from_secs(30)) + .with_default_headers(headers) + .build(); + + assert!(client.request("http://localhost:8080/api").is_ok()); +} + +#[cfg(feature = "rustls")] +#[test] +fn test_rustls_usage_example() { + // Test rustls usage example from documentation + let client = HttpClient::builder() + .with_root_ca_pem(b"-----BEGIN CERTIFICATE-----\nDUMMY\n-----END CERTIFICATE-----") + .build(); + + assert!(client.request("https://localhost:8080/secure").is_ok()); +} + +#[cfg(feature = "insecure-dangerous")] +#[test] +fn test_insecure_usage_example() { + // Test insecure usage example (development only) + let client = HttpClient::builder() + .insecure_accept_invalid_certs(true) + .build(); + + // Also test convenience constructor + let client2 = HttpClient::with_self_signed_certs(); + + assert!(client.request("https://localhost:8080/self-signed").is_ok()); + assert!(client2.request("https://localhost:8080/self-signed").is_ok()); +} \ No newline at end of file diff --git a/crates/hyper-custom-cert/tests/rustls_features.rs b/crates/hyper-custom-cert/tests/rustls_features.rs index d19b91e..1027580 100644 --- a/crates/hyper-custom-cert/tests/rustls_features.rs +++ b/crates/hyper-custom-cert/tests/rustls_features.rs @@ -147,6 +147,14 @@ fn rustls_with_timeout_and_ca() { // Test passes if compilation succeeds } +#[cfg(feature = "rustls")] +#[test] +fn rustls_post_smoke() { + // Smoke test for POST support when rustls feature is enabled + let client = HttpClient::new(); + let _ = client.post("https://example.com/api", b"{\"a\":1}"); +} + // Test that runs only when rustls feature is NOT enabled #[cfg(not(feature = "rustls"))] #[test]