#[cfg(all(test, feature = "tower-middleware"))]
mod tests {
use crate::{Client, middleware::{FlagsLayer, RequestExt}};
use http::{Request, Response, StatusCode};
use http_body_util::{Empty, Full};
use std::convert::Infallible;
use tower::{ServiceBuilder, ServiceExt};
use wiremock::{MockServer, Mock, ResponseTemplate};
use wiremock::matchers::{method, path, header};
use bytes::Bytes;
async fn setup_mock_server() -> MockServer {
MockServer::start().await
}
async fn create_test_client(server: &MockServer) -> Client {
Client::builder()
.with_base_url(&server.uri())
.with_auth(crate::Auth {
project_id: "test-project".to_string(),
agent_id: "test-agent".to_string(),
environment_id: "test-env".to_string(),
})
.build()
.expect("Failed to build test client")
}
#[tokio::test]
async fn test_middleware_adds_client_to_request_extensions() {
let mock_server = setup_mock_server().await;
Mock::given(method("GET"))
.and(path("/flags"))
.and(header("x-project-id", "test-project"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"flags": []
})))
.mount(&mock_server)
.await;
let client = create_test_client(&mock_server).await;
let service = ServiceBuilder::new()
.layer(FlagsLayer::new(client))
.service_fn(|req: Request<Empty<Bytes>>| async move {
assert!(req.flags_client().is_some());
Ok::<_, Infallible>(Response::new(Full::new(Bytes::new())))
});
let request = Request::builder()
.uri("/")
.body(Empty::new())
.unwrap();
let _ = service.oneshot(request).await.unwrap();
}
#[tokio::test]
async fn test_middleware_processes_feature_flags_header() {
let mock_server = setup_mock_server().await;
Mock::given(method("GET"))
.and(path("/flags"))
.and(header("x-project-id", "test-project"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"flags": [
{
"enabled": true,
"details": {
"name": "feature-1",
"id": "1"
}
},
{
"enabled": false,
"details": {
"name": "feature-2",
"id": "2"
}
},
{
"enabled": true,
"details": {
"name": "feature-3",
"id": "3"
}
}
]
})))
.mount(&mock_server)
.await;
let client = create_test_client(&mock_server).await;
let _ = client.is("feature-1").enabled().await;
let service = ServiceBuilder::new()
.layer(FlagsLayer::new(client))
.service_fn(|_req: Request<Empty<Bytes>>| async move {
Ok::<_, Infallible>(Response::new(Full::new(Bytes::new())))
});
let request = Request::builder()
.uri("/")
.header("X-Feature-Flags", "feature-1,feature-2,feature-3")
.body(Empty::new())
.unwrap();
let response = service.oneshot(request).await.unwrap();
let enabled_flags = response.headers().get("X-Enabled-Flags");
assert!(enabled_flags.is_none());
}
#[tokio::test]
async fn test_middleware_with_custom_header_name() {
let mock_server = setup_mock_server().await;
Mock::given(method("GET"))
.and(path("/flags"))
.and(header("x-project-id", "test-project"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"flags": [
{
"enabled": true,
"details": {
"name": "test-flag",
"id": "1"
}
}
]
})))
.mount(&mock_server)
.await;
let client = create_test_client(&mock_server).await;
let _ = client.is("test-flag").enabled().await;
let service = ServiceBuilder::new()
.layer(FlagsLayer::new(client).with_header_name("Custom-Flags"))
.service_fn(|_req: Request<Empty<Bytes>>| async move {
Ok::<_, Infallible>(Response::new(Full::new(Bytes::new())))
});
let request = Request::builder()
.uri("/")
.header("Custom-Flags", "test-flag")
.body(Empty::new())
.unwrap();
let response = service.oneshot(request).await.unwrap();
let enabled_flags = response.headers().get("X-Enabled-Flags");
assert!(enabled_flags.is_none());
}
#[tokio::test]
async fn test_middleware_continues_on_flag_check_error() {
let client = Client::builder()
.with_base_url("http://invalid-url")
.with_auth(crate::Auth {
project_id: "test-project".to_string(),
agent_id: "test-agent".to_string(),
environment_id: "test-env".to_string(),
})
.build()
.expect("Failed to build test client");
let service = ServiceBuilder::new()
.layer(FlagsLayer::new(client))
.service_fn(|_req: Request<Empty<Bytes>>| async move {
Ok::<_, Infallible>(Response::builder()
.status(StatusCode::OK)
.body(Full::new(Bytes::new()))
.unwrap())
});
let request = Request::builder()
.uri("/")
.header("X-Feature-Flags", "some-flag")
.body(Empty::new())
.unwrap();
let response = service.oneshot(request).await.unwrap();
assert_eq!(response.status(), StatusCode::OK);
assert!(response.headers().get("X-Enabled-Flags").is_none());
}
#[tokio::test]
async fn test_request_without_flags_header() {
let mock_server = setup_mock_server().await;
Mock::given(method("GET"))
.and(path("/flags"))
.and(header("x-project-id", "test-project"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"flags": []
})))
.mount(&mock_server)
.await;
let client = create_test_client(&mock_server).await;
let service = ServiceBuilder::new()
.layer(FlagsLayer::new(client))
.service_fn(|req: Request<Empty<Bytes>>| async move {
assert!(req.flags_client().is_some());
Ok::<_, Infallible>(Response::new(Full::new(Bytes::new())))
});
let request = Request::builder()
.uri("/")
.body(Empty::new())
.unwrap();
let response = service.oneshot(request).await.unwrap();
assert!(response.headers().get("X-Enabled-Flags").is_none());
}
}