#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
#![allow(clippy::multiple_crate_versions)]
use switchy_web_server::test_client::{ConcreteTestClient, TestClient, TestResponseExt};
#[test]
fn test_client_basic_interface() {
let client = ConcreteTestClient::new_with_test_routes();
let get_response = client.get("/test").send().expect("GET should work");
get_response.assert_status(200);
let post_response = client.post("/test").send().expect("POST should work");
post_response.assert_status(404);
let put_response = client.put("/test").send().expect("PUT should work");
put_response.assert_status(404);
let delete_response = client.delete("/test").send().expect("DELETE should work");
delete_response.assert_status(404);
}
#[test]
fn test_client_headers() {
let client = ConcreteTestClient::new_with_test_routes();
let response = client
.get("/test")
.header("X-Custom-Header", "test-value")
.header("Authorization", "Bearer token123")
.send()
.expect("Request with headers should work");
response.assert_status(200);
response.assert_header("content-type", "application/json");
}
#[test]
fn test_client_body() {
let client = ConcreteTestClient::new_with_api_routes();
let response = client
.post("/api/echo")
.header("Content-Type", "application/json")
.body_bytes(b"{\"message\": \"hello\"}".to_vec())
.send()
.expect("POST with body should work");
response.assert_status(200);
response.assert_header("content-type", "application/json");
}
#[test]
fn test_client_error_handling() {
let client = ConcreteTestClient::new_with_test_routes();
let response = client
.get("/nonexistent")
.send()
.expect("Request should succeed");
response.assert_status(404);
let _response1 = client.get("/").send().expect("Root path should work");
let _response2 = client
.get("/api/v1/test")
.send()
.expect("Nested path should work");
let _response3 = client
.get("/test?param=value")
.send()
.expect("Query params should work");
}
#[test]
fn test_client_url_handling() {
let client = ConcreteTestClient::default();
let _response1 = client.get("/").send().expect("Root should work");
let _response2 = client.get("/test").send().expect("Simple path should work");
let _response3 = client
.get("/test/nested/path")
.send()
.expect("Nested path should work");
let custom_client = ConcreteTestClient::default();
let _response4 = custom_client
.get("/")
.send()
.expect("Custom client should work");
}
#[test]
fn test_client_response_assertions() {
let client = ConcreteTestClient::new_with_test_routes();
let response = client.get("/test").send().expect("Request should succeed");
response.assert_status(200);
response.assert_header("content-type", "application/json");
response.assert_text_contains("message");
}
#[test]
fn test_client_concrete_type_usage() {
let client = ConcreteTestClient::new_with_test_routes();
let response = client
.get("/test")
.send()
.expect("Concrete request should work");
response.assert_status(200);
}
#[test]
fn test_client_concurrent_usage() {
let client = ConcreteTestClient::new_with_test_routes();
let response1 = client
.get("/test")
.send()
.expect("First request should work");
let response2 = client
.get("/health")
.send()
.expect("Second request should work");
response1.assert_status(200);
response2.assert_status(200);
}
#[test]
fn test_client_runtime_management() {
let client1 = ConcreteTestClient::new_with_test_routes();
let client2 = ConcreteTestClient::new_with_test_routes();
let response1 = client1.get("/test").send().expect("Client 1 should work");
let response2 = client2.get("/test").send().expect("Client 2 should work");
response1.assert_status(200);
response2.assert_status(200);
}
#[test]
fn test_parallel_basic_get_requests() {
let client = ConcreteTestClient::new_with_test_routes();
let response = client.get("/test").send().expect("GET should work");
response.assert_status(200);
response.assert_header("content-type", "application/json");
response.assert_text_contains("message");
}
#[test]
fn test_parallel_post_json_requests() {
let client = ConcreteTestClient::new_with_api_routes();
let response = client
.post("/api/echo")
.header("Content-Type", "application/json")
.body_bytes(b"{\"test\": \"data\"}".to_vec())
.send()
.expect("POST should work");
response.assert_status(200);
response.assert_header("content-type", "application/json");
}
#[test]
fn test_parallel_404_responses() {
let client = ConcreteTestClient::default();
let response = client
.get("/nonexistent/path")
.send()
.expect("Request should succeed");
response.assert_status(404);
}
#[test]
fn test_parallel_custom_headers() {
let client = ConcreteTestClient::new_with_test_routes();
let response = client
.get("/test")
.header("X-Custom-Header", "test-value")
.header("User-Agent", "test-client/1.0")
.send()
.expect("Request with headers should work");
response.assert_status(200);
}
#[test]
fn test_concrete_client_usage() {
let client = ConcreteTestClient::new_with_test_routes();
let response = client
.get("/test")
.send()
.expect("Concrete client should work");
response.assert_status(200);
}
#[test]
fn test_parallel_empty_responses() {
let client = ConcreteTestClient::default();
let response = client.get("/empty").send().expect("Request should succeed");
response.assert_status(404);
}
#[test]
fn test_nested_scope_data_structure_is_supported() {
use switchy_web_server::{HttpResponse, HttpResponseBody, Method, Scope};
let users_scope = Scope::new("/users").route(Method::Get, "", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"users":["alice","bob"]}"#)))
})
});
let v1_scope = Scope::new("/v1").with_scope(users_scope);
let api_scope = Scope::new("/api")
.with_scope(v1_scope)
.route(Method::Get, "/status", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"status":"ok"}"#)))
})
});
assert_eq!(api_scope.path, "/api");
assert_eq!(api_scope.routes.len(), 1); assert_eq!(api_scope.scopes.len(), 1);
let v1 = &api_scope.scopes[0];
assert_eq!(v1.path, "/v1");
assert_eq!(v1.scopes.len(), 1);
let users = &v1.scopes[0];
assert_eq!(users.path, "/users");
assert_eq!(users.routes.len(), 1);
#[cfg(feature = "simulator")]
{
let _simulator_server =
switchy_web_server::simulator::SimulatorWebServer::new(vec![api_scope.clone()]);
}
#[cfg(all(feature = "actix", not(feature = "simulator")))]
{
assert!(
!api_scope.scopes.is_empty(),
"Should be able to detect nested scopes in data structure"
);
}
}
#[test]
#[cfg(all(feature = "actix", not(feature = "simulator")))]
fn test_actix_nested_scopes_now_work() {
use switchy_web_server::test_client::actix_impl::ActixWebServer;
use switchy_web_server::{HttpResponse, HttpResponseBody, Method, Scope};
let users_scope = Scope::new("/users").route(Method::Get, "", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"users":["alice","bob"]}"#)))
})
});
let v1_scope = Scope::new("/v1").with_scope(users_scope);
let api_scope = Scope::new("/api")
.with_scope(v1_scope)
.route(Method::Get, "/status", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"status":"ok"}"#)))
})
});
assert!(
!api_scope.scopes.is_empty(),
"API scope should have nested scopes"
);
assert_eq!(
api_scope.scopes.len(),
1,
"API scope should have exactly one nested scope"
);
let v1 = &api_scope.scopes[0];
assert!(!v1.scopes.is_empty(), "V1 scope should have nested scopes");
assert_eq!(
v1.scopes.len(),
1,
"V1 scope should have exactly one nested scope"
);
let _server = ActixWebServer::new(vec![api_scope]);
}
#[test]
#[cfg(all(feature = "actix", not(feature = "simulator")))]
fn test_flatten_single_level_scopes() {
use switchy_web_server::{HttpResponse, HttpResponseBody, Method, Scope};
let scopes = vec![
Scope::new("/api").route(Method::Get, "/health", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"status":"healthy"}"#)))
})
}),
Scope::new("/admin").route(Method::Post, "/users", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"created":true}"#)))
})
}),
];
let flattened = switchy_web_server::test_client::actix_impl::flatten_scope_tree(&scopes);
assert_eq!(flattened.len(), 2);
assert_eq!(flattened[0].full_path, "/api/health");
assert_eq!(flattened[0].method, Method::Get);
assert_eq!(flattened[1].full_path, "/admin/users");
assert_eq!(flattened[1].method, Method::Post);
}
#[test]
#[cfg(all(feature = "actix", not(feature = "simulator")))]
fn test_flatten_two_level_nesting() {
use switchy_web_server::{HttpResponse, HttpResponseBody, Method, Scope};
let api_scope =
Scope::new("/api").with_scope(Scope::new("/v1").route(Method::Get, "/users", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"users":[]}"#)))
})
}));
let flattened = switchy_web_server::test_client::actix_impl::flatten_scope_tree(&[api_scope]);
assert_eq!(flattened.len(), 1);
assert_eq!(flattened[0].full_path, "/api/v1/users");
assert_eq!(flattened[0].method, Method::Get);
}
#[test]
#[cfg(all(feature = "actix", not(feature = "simulator")))]
fn test_flatten_deep_nesting() {
use switchy_web_server::{HttpResponse, HttpResponseBody, Method, Scope};
let deep_scope = Scope::new("/api").with_scope(Scope::new("/v1").with_scope(
Scope::new("/admin").route(Method::Delete, "/users/{id}", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"deleted":true}"#)))
})
}),
));
let flattened = switchy_web_server::test_client::actix_impl::flatten_scope_tree(&[deep_scope]);
assert_eq!(flattened.len(), 1);
assert_eq!(flattened[0].full_path, "/api/v1/admin/users/{id}");
assert_eq!(flattened[0].method, Method::Delete);
}
#[test]
#[cfg(all(feature = "actix", not(feature = "simulator")))]
fn test_flatten_mixed_routes_and_scopes() {
use switchy_web_server::{HttpResponse, HttpResponseBody, Method, Scope};
let mixed_scope = Scope::new("/api")
.route(Method::Get, "/status", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"status":"ok"}"#)))
})
})
.with_scope(
Scope::new("/v1")
.route(Method::Get, "/health", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"health":"ok"}"#)))
})
})
.with_scope(
Scope::new("/users")
.route(Method::Get, "", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"users":[]}"#)))
})
})
.route(Method::Post, "", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"created":true}"#)))
})
}),
),
);
let flattened = switchy_web_server::test_client::actix_impl::flatten_scope_tree(&[mixed_scope]);
assert_eq!(flattened.len(), 4);
assert_eq!(flattened[0].full_path, "/api/status");
assert_eq!(flattened[0].method, Method::Get);
assert_eq!(flattened[1].full_path, "/api/v1/health");
assert_eq!(flattened[1].method, Method::Get);
assert_eq!(flattened[2].full_path, "/api/v1/users");
assert_eq!(flattened[2].method, Method::Get);
assert_eq!(flattened[3].full_path, "/api/v1/users");
assert_eq!(flattened[3].method, Method::Post);
}
#[test]
#[cfg(all(feature = "actix", not(feature = "simulator")))]
fn test_flatten_empty_path_edge_cases() {
use switchy_web_server::{HttpResponse, HttpResponseBody, Method, Scope};
let edge_cases = vec![
Scope::new("/users").route(Method::Get, "", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"users":[]}"#)))
})
}),
Scope::new("").route(Method::Get, "/health", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"health":"ok"}"#)))
})
}),
Scope::new("").route(Method::Get, "", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"root":true}"#)))
})
}),
];
let flattened = switchy_web_server::test_client::actix_impl::flatten_scope_tree(&edge_cases);
assert_eq!(flattened.len(), 3);
assert_eq!(flattened[0].full_path, "/users");
assert_eq!(flattened[0].method, Method::Get);
assert_eq!(flattened[1].full_path, "/health");
assert_eq!(flattened[1].method, Method::Get);
assert_eq!(flattened[2].full_path, "");
assert_eq!(flattened[2].method, Method::Get);
}
#[test]
#[cfg(all(feature = "actix", not(feature = "simulator")))]
fn test_flatten_parallel_scopes() {
use switchy_web_server::{HttpResponse, HttpResponseBody, Method, Scope};
let parallel_scopes = Scope::new("/api")
.with_scope(Scope::new("/v1").route(Method::Get, "/users", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"version":"v1"}"#)))
})
}))
.with_scope(Scope::new("/v2").route(Method::Get, "/users", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"version":"v2"}"#)))
})
}));
let flattened =
switchy_web_server::test_client::actix_impl::flatten_scope_tree(&[parallel_scopes]);
assert_eq!(flattened.len(), 2);
assert_eq!(flattened[0].full_path, "/api/v1/users");
assert_eq!(flattened[0].method, Method::Get);
assert_eq!(flattened[1].full_path, "/api/v2/users");
assert_eq!(flattened[1].method, Method::Get);
}
#[test]
#[cfg(all(feature = "actix", not(feature = "simulator")))]
fn test_flatten_container_scopes() {
use switchy_web_server::{HttpResponse, HttpResponseBody, Method, Scope};
let container = Scope::new("/api") .with_scope(
Scope::new("/v1") .with_scope(Scope::new("/users").route(Method::Get, "", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"users":[]}"#)))
})
})),
);
let flattened = switchy_web_server::test_client::actix_impl::flatten_scope_tree(&[container]);
assert_eq!(flattened.len(), 1);
assert_eq!(flattened[0].full_path, "/api/v1/users");
assert_eq!(flattened[0].method, Method::Get);
}
#[test]
#[cfg(all(feature = "actix", not(feature = "simulator")))]
fn test_flatten_path_parameters() {
use switchy_web_server::{HttpResponse, HttpResponseBody, Method, Scope};
let params_scope = Scope::new("/api").with_scope(
Scope::new("/v1")
.route(Method::Get, "/users/{id}", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"user":"found"}"#)))
})
})
.route(Method::Put, "/users/{id}/profile", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"updated":true}"#)))
})
}),
);
let flattened =
switchy_web_server::test_client::actix_impl::flatten_scope_tree(&[params_scope]);
assert_eq!(flattened.len(), 2);
assert_eq!(flattened[0].full_path, "/api/v1/users/{id}");
assert_eq!(flattened[0].method, Method::Get);
assert_eq!(flattened[1].full_path, "/api/v1/users/{id}/profile");
assert_eq!(flattened[1].method, Method::Put);
}
#[test]
#[cfg(all(feature = "actix", not(feature = "simulator")))]
fn test_deeply_nested_scopes_four_levels() {
use switchy_web_server::{HttpResponse, HttpResponseBody, Method, Scope};
let deep_scope = Scope::new("/api").with_scope(
Scope::new("/v1").with_scope(
Scope::new("/admin").with_scope(
Scope::new("/users")
.route(Method::Get, "/list", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"users":[]}"#)))
})
})
.route(Method::Post, "/create", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"created":true}"#)))
})
}),
),
),
);
let flattened = switchy_web_server::test_client::actix_impl::flatten_scope_tree(&[deep_scope]);
assert_eq!(flattened.len(), 2);
assert_eq!(flattened[0].full_path, "/api/v1/admin/users/list");
assert_eq!(flattened[0].method, Method::Get);
assert_eq!(flattened[1].full_path, "/api/v1/admin/users/create");
assert_eq!(flattened[1].method, Method::Post);
}
#[test]
#[cfg(all(feature = "actix", not(feature = "simulator")))]
fn test_deeply_nested_scopes_five_levels() {
use switchy_web_server::{HttpResponse, HttpResponseBody, Method, Scope};
let very_deep_scope = Scope::new("/api").with_scope(Scope::new("/v2").with_scope(
Scope::new("/enterprise").with_scope(Scope::new("/admin").with_scope(
Scope::new("/users").route(Method::Delete, "/purge", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"purged":true}"#)))
})
}),
)),
));
let flattened =
switchy_web_server::test_client::actix_impl::flatten_scope_tree(&[very_deep_scope]);
assert_eq!(flattened.len(), 1);
assert_eq!(
flattened[0].full_path,
"/api/v2/enterprise/admin/users/purge"
);
assert_eq!(flattened[0].method, Method::Delete);
}
#[test]
#[cfg(all(feature = "actix", not(feature = "simulator")))]
fn test_empty_scopes_no_routes() {
use switchy_web_server::{HttpResponse, HttpResponseBody, Method, Scope};
let empty_scopes = vec![
Scope::new("/empty"),
Scope::new("/api").with_scope(Scope::new("/v1")),
Scope::new("/mixed")
.route(Method::Get, "/health", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("text/plain")
.with_body(HttpResponseBody::from("OK")))
})
})
.with_scope(Scope::new("/empty_nested")),
];
let flattened = switchy_web_server::test_client::actix_impl::flatten_scope_tree(&empty_scopes);
assert_eq!(flattened.len(), 1);
assert_eq!(flattened[0].full_path, "/mixed/health");
assert_eq!(flattened[0].method, Method::Get);
}
#[test]
#[cfg(all(feature = "actix", not(feature = "simulator")))]
fn test_duplicate_path_segments() {
use switchy_web_server::{HttpResponse, HttpResponseBody, Method, Scope};
let duplicate_segments = vec![
Scope::new("/api").with_scope(Scope::new("/api").route(Method::Get, "/users", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"duplicate":"api"}"#)))
})
})),
Scope::new("/v1").with_scope(Scope::new("/v1").with_scope(Scope::new("/v1").route(
Method::Post,
"/data",
|_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"triple":"v1"}"#)))
})
},
))),
];
let flattened =
switchy_web_server::test_client::actix_impl::flatten_scope_tree(&duplicate_segments);
assert_eq!(flattened.len(), 2);
assert_eq!(flattened[0].full_path, "/api/api/users");
assert_eq!(flattened[0].method, Method::Get);
assert_eq!(flattened[1].full_path, "/v1/v1/v1/data");
assert_eq!(flattened[1].method, Method::Post);
}
#[test]
#[cfg(all(feature = "actix", not(feature = "simulator")))]
fn test_path_concatenation_edge_cases() {
use switchy_web_server::{HttpResponse, HttpResponseBody, Method, Scope};
let edge_cases = vec![
Scope::new("").with_scope(Scope::new("/api").route(Method::Get, "/test", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("text/plain")
.with_body(HttpResponseBody::from("empty_root")))
})
})),
Scope::new("//double").with_scope(Scope::new("//slash").route(
Method::Get,
"//route",
|_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("text/plain")
.with_body(HttpResponseBody::from("multiple_slashes")))
})
},
)),
Scope::new("/").with_scope(Scope::new("api").route(Method::Get, "status", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("text/plain")
.with_body(HttpResponseBody::from("root_nested")))
})
})),
];
let flattened = switchy_web_server::test_client::actix_impl::flatten_scope_tree(&edge_cases);
assert_eq!(flattened.len(), 3);
assert_eq!(flattened[0].full_path, "/api/test");
assert_eq!(flattened[1].full_path, "//double//slash//route");
assert_eq!(flattened[2].full_path, "/api/status");
}
#[test]
#[cfg(all(feature = "actix", not(feature = "simulator")))]
fn test_complex_mixed_nesting_patterns() {
use switchy_web_server::{HttpResponse, HttpResponseBody, Method, Scope};
let complex_scope = Scope::new("/api")
.route(Method::Get, "/health", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("text/plain")
.with_body(HttpResponseBody::from("healthy")))
})
})
.with_scope(
Scope::new("/v1")
.route(Method::Get, "/info", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"version":"1.0"}"#)))
})
})
.with_scope(
Scope::new("/users")
.route(Method::Get, "/", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"users":[]}"#)))
})
})
.with_scope(
Scope::new("/{id}")
.route(Method::Get, "/profile", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"profile":{}}"#)))
})
})
.with_scope(Scope::new("/settings").route(
Method::Put,
"/theme",
|_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(
r#"{"updated":true}"#,
)))
})
},
)),
),
),
)
.with_scope(Scope::new("/v2").route(Method::Post, "/migrate", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"migrated":true}"#)))
})
}));
let flattened =
switchy_web_server::test_client::actix_impl::flatten_scope_tree(&[complex_scope]);
assert_eq!(flattened.len(), 6);
let mut paths: Vec<_> = flattened.iter().map(|r| &r.full_path).collect();
paths.sort();
assert_eq!(paths[0], "/api/health");
assert_eq!(paths[1], "/api/v1/info");
assert_eq!(paths[2], "/api/v1/users/");
assert_eq!(paths[3], "/api/v1/users/{id}/profile");
assert_eq!(paths[4], "/api/v1/users/{id}/settings/theme");
assert_eq!(paths[5], "/api/v2/migrate");
}
#[test]
#[cfg(all(feature = "actix", not(feature = "simulator")))]
fn test_comprehensive_edge_case_validation() {
use switchy_web_server::{HttpResponse, HttpResponseBody, Method, Scope};
let deep_scope = Scope::new("/api").with_scope(Scope::new("/v2").with_scope(
Scope::new("/enterprise").with_scope(Scope::new("/admin").with_scope(
Scope::new("/users").route(Method::Delete, "/purge", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"purged":true}"#)))
})
}),
)),
));
let empty_mixed = vec![
Scope::new("/empty"),
Scope::new("/api").with_scope(Scope::new("/v1")),
Scope::new("/working").route(Method::Get, "/test", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("text/plain")
.with_body(HttpResponseBody::from("working")))
})
}),
];
let duplicate_scope = Scope::new("/api").with_scope(Scope::new("/api").with_scope(
Scope::new("/api").route(Method::Get, "/test", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"triple":"api"}"#)))
})
}),
));
let _server1 =
switchy_web_server::test_client::actix_impl::ActixWebServer::new(vec![deep_scope]);
let _server2 = switchy_web_server::test_client::actix_impl::ActixWebServer::new(empty_mixed);
let _server3 =
switchy_web_server::test_client::actix_impl::ActixWebServer::new(vec![duplicate_scope]);
}
#[test]
#[cfg(all(feature = "actix", not(feature = "simulator")))]
fn test_native_nesting_vs_flattening_basic() {
use switchy_web_server::test_client::actix_impl::ActixWebServer;
use switchy_web_server::{HttpResponse, HttpResponseBody, Method, Scope};
let nested_scope =
Scope::new("/api").with_scope(Scope::new("/v1").route(Method::Get, "/test", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"method":"native"}"#)))
})
}));
let _flattening_server = ActixWebServer::new_with_flattening(vec![nested_scope.clone()]);
let _native_server = ActixWebServer::new_with_native_nesting(vec![nested_scope]);
}
#[test]
#[cfg(all(feature = "actix", not(feature = "simulator")))]
fn test_native_nesting_complex_structure() {
use switchy_web_server::test_client::actix_impl::ActixWebServer;
use switchy_web_server::{HttpResponse, HttpResponseBody, Method, Scope};
let complex_scope = Scope::new("/api")
.route(Method::Get, "/health", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("text/plain")
.with_body(HttpResponseBody::from("healthy")))
})
})
.with_scope(
Scope::new("/v1")
.route(Method::Get, "/info", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"version":"1.0"}"#)))
})
})
.with_scope(Scope::new("/users").with_scope(Scope::new("/admin").route(
Method::Get,
"/list",
|_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"admin_users":[]}"#)))
})
},
))),
);
let _native_server = ActixWebServer::new_with_native_nesting(vec![complex_scope]);
}
#[test]
#[cfg(all(feature = "actix", not(feature = "simulator")))]
#[allow(clippy::cast_precision_loss)]
fn test_performance_comparison_setup_time() {
use std::time::Instant;
use switchy_web_server::test_client::actix_impl::ActixWebServer;
use switchy_web_server::{HttpResponse, HttpResponseBody, Method, Scope};
let create_test_scopes = || {
vec![
Scope::new("/api")
.route(Method::Get, "/health", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("text/plain")
.with_body(HttpResponseBody::from("healthy")))
})
})
.with_scope(
Scope::new("/v1")
.route(Method::Get, "/info", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"version":"1.0"}"#)))
})
})
.with_scope(
Scope::new("/users")
.route(Method::Get, "/", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"users":[]}"#)))
})
})
.with_scope(Scope::new("/admin").route(
Method::Get,
"/list",
|_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(
r#"{"admin_users":[]}"#,
)))
})
},
)),
),
),
Scope::new("/public").route(Method::Get, "/status", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("text/plain")
.with_body(HttpResponseBody::from("ok")))
})
}),
]
};
let flattening_start = Instant::now();
let _flattening_server = ActixWebServer::new_with_flattening(create_test_scopes());
let flattening_duration = flattening_start.elapsed();
let native_start = Instant::now();
let _native_server = ActixWebServer::new_with_native_nesting(create_test_scopes());
let native_duration = native_start.elapsed();
println!("Performance Comparison (Server Setup Time):");
println!(" Flattening approach: {flattening_duration:?}");
println!(" Native nesting: {native_duration:?}");
if native_duration < flattening_duration {
let improvement = flattening_duration.as_nanos() as f64 / native_duration.as_nanos() as f64;
println!(" Native nesting is {improvement:.2}x faster");
} else {
let slowdown = native_duration.as_nanos() as f64 / flattening_duration.as_nanos() as f64;
println!(" Flattening is {slowdown:.2}x faster");
}
assert!(
flattening_duration.as_millis() < 1000,
"Flattening setup should be fast"
);
assert!(
native_duration.as_millis() < 1000,
"Native nesting setup should be fast"
);
}
#[test]
#[cfg(all(feature = "actix", not(feature = "simulator")))]
fn test_native_nesting_root_path_edge_cases() {
use switchy_web_server::test_client::actix_impl::ActixWebServer;
use switchy_web_server::{HttpResponse, HttpResponseBody, Method, Scope};
let root_scope =
Scope::new("/").with_scope(Scope::new("api").route(Method::Get, "status", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("text/plain")
.with_body(HttpResponseBody::from("root_api_status")))
})
}));
let root_with_slash =
Scope::new("/").with_scope(Scope::new("/api").route(Method::Get, "/test", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("text/plain")
.with_body(HttpResponseBody::from("root_slash_test")))
})
}));
let _server1 = ActixWebServer::new_with_native_nesting(vec![root_scope]);
let _server2 = ActixWebServer::new_with_native_nesting(vec![root_with_slash]);
}
#[test]
#[cfg(all(feature = "actix", not(feature = "simulator")))]
fn test_native_nesting_empty_path_edge_cases() {
use switchy_web_server::test_client::actix_impl::ActixWebServer;
use switchy_web_server::{HttpResponse, HttpResponseBody, Method, Scope};
let empty_scope =
Scope::new("").with_scope(Scope::new("/api").route(Method::Get, "/test", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("text/plain")
.with_body(HttpResponseBody::from("empty_scope_test")))
})
}));
let empty_route = Scope::new("/api").route(Method::Get, "", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("text/plain")
.with_body(HttpResponseBody::from("empty_route")))
})
});
let multiple_empty = Scope::new("").with_scope(Scope::new("").route(Method::Get, "", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("text/plain")
.with_body(HttpResponseBody::from("multiple_empty")))
})
}));
let _server1 = ActixWebServer::new_with_native_nesting(vec![empty_scope]);
let _server2 = ActixWebServer::new_with_native_nesting(vec![empty_route]);
let _server3 = ActixWebServer::new_with_native_nesting(vec![multiple_empty]);
}
#[test]
#[cfg(all(feature = "actix", not(feature = "simulator")))]
fn test_native_nesting_multiple_slash_edge_cases() {
use switchy_web_server::test_client::actix_impl::ActixWebServer;
use switchy_web_server::{HttpResponse, HttpResponseBody, Method, Scope};
let double_slash_scope =
Scope::new("//api").with_scope(Scope::new("//v1").route(Method::Get, "//test", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("text/plain")
.with_body(HttpResponseBody::from("double_slash")))
})
}));
let mixed_slashes =
Scope::new("/api/").with_scope(Scope::new("v1/").route(Method::Get, "test/", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("text/plain")
.with_body(HttpResponseBody::from("mixed_slashes")))
})
}));
let _server1 = ActixWebServer::new_with_native_nesting(vec![double_slash_scope]);
let _server2 = ActixWebServer::new_with_native_nesting(vec![mixed_slashes]);
}
#[test]
#[cfg(all(feature = "actix", not(feature = "simulator")))]
fn test_native_vs_flattening_edge_case_parity() {
use switchy_web_server::test_client::actix_impl::ActixWebServer;
use switchy_web_server::{HttpResponse, HttpResponseBody, Method, Scope};
let edge_case_scopes = vec![
Scope::new("").with_scope(Scope::new("/api").route(Method::Get, "/test", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("application/json")
.with_body(HttpResponseBody::from(r#"{"source":"empty_scope"}"#)))
})
})),
Scope::new("//double").with_scope(Scope::new("//slash").route(
Method::Get,
"//route",
|_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("text/plain")
.with_body(HttpResponseBody::from("multiple_slashes")))
})
},
)),
Scope::new("/").with_scope(Scope::new("api").route(Method::Get, "status", |_req| {
Box::pin(async {
Ok(HttpResponse::ok()
.with_content_type("text/plain")
.with_body(HttpResponseBody::from("root_nested")))
})
})),
];
let _native_server = ActixWebServer::new_with_native_nesting(edge_case_scopes.clone());
let _flattening_server = ActixWebServer::new_with_flattening(edge_case_scopes);
}