Files
imphnen-backend-service/tests/src/rate_limiting_middleware_test.rs
T

296 lines
9.7 KiB
Rust

#[cfg(test)]
mod rate_limiting_middleware_tests {
use axum::{http::Request, middleware::Next, response::Response};
use imphnen_libs::{AppState, environment::Environment};
use imphnen_middleware::rate_limiting_middleware::{
RateLimitConfig, RateLimitStore, TokenBucket, create_rate_limiting_middleware,
auth_rate_limiting_middleware,
};
use std::{sync::Arc, time::Duration};
use tower::ServiceExt;
#[tokio::test]
async fn test_token_bucket_basic_functionality() {
let bucket = TokenBucket::new(5, 2); // Capacity 5, refill 2 per second
// Should have full tokens initially
assert_eq!(bucket.tokens, 5);
// Consume some tokens
assert!(bucket.try_consume());
assert_eq!(bucket.tokens, 4);
assert!(bucket.try_consume());
assert_eq!(bucket.tokens, 3);
assert!(bucket.try_consume());
assert_eq!(bucket.tokens, 2);
assert!(bucket.try_consume());
assert_eq!(bucket.tokens, 1);
assert!(bucket.try_consume());
assert_eq!(bucket.tokens, 0);
// Should not consume when empty
assert!(!bucket.try_consume());
assert_eq!(bucket.tokens, 0);
}
#[tokio::test]
async fn test_token_bucket_refill() {
let mut bucket = TokenBucket::new(3, 1); // Capacity 3, refill 1 per second
// Consume all tokens
for _ in 0..3 {
assert!(bucket.try_consume());
}
assert!(!bucket.try_consume());
assert_eq!(bucket.tokens, 0);
// Wait for 1 second to allow refill
tokio::time::sleep(Duration::from_secs(1)).await;
// Should have 1 token after refill
bucket.refill_tokens();
assert_eq!(bucket.tokens, 1);
// Consume the refilled token
assert!(bucket.try_consume());
assert_eq!(bucket.tokens, 0);
// Wait another second
tokio::time::sleep(Duration::from_secs(1)).await;
// Should have another token
bucket.refill_tokens();
assert_eq!(bucket.tokens, 1);
}
#[tokio::test]
async fn test_rate_limit_store_basic() {
let config = RateLimitConfig::test();
let store = Arc::new(RateLimitStore::new(config));
let client_ip = "127.0.0.1";
// First request should succeed
let result = store.check_limit(client_ip).await;
assert!(result.is_ok());
// Multiple requests should succeed within limits
for _ in 0..config.bucket_size {
let result = store.check_limit(client_ip).await;
assert!(result.is_ok());
}
// Next request should fail
let result = store.check_limit(client_ip).await;
assert!(result.is_err());
assert_eq!(result.unwrap_err(), axum::http::StatusCode::TOO_MANY_REQUESTS);
}
#[tokio::test]
async fn test_rate_limit_store_window_reset() {
let config = RateLimitConfig {
max_requests: 10,
window_duration: Duration::from_secs(2),
bucket_size: 2,
refill_rate: 1,
};
let store = Arc::new(RateLimitStore::new(config));
let client_ip = "127.0.0.1";
// Consume all tokens
assert!(store.check_limit(client_ip).await.is_ok());
assert!(store.check_limit(client_ip).await.is_ok());
assert!(store.check_limit(client_ip).await.is_err());
// Wait for window to reset
tokio::time::sleep(Duration::from_secs(3)).await;
// Should be able to make requests again
assert!(store.check_limit(client_ip).await.is_ok());
assert!(store.check_limit(client_ip).await.is_ok());
assert!(store.check_limit(client_ip).await.is_err());
}
#[tokio::test]
async fn test_different_clients_have_separate_limits() {
let config = RateLimitConfig::test();
let store = Arc::new(RateLimitStore::new(config));
let client_ip_1 = "127.0.0.1";
let client_ip_2 = "127.0.0.2";
// Client 1 should be able to make requests
for _ in 0..config.bucket_size {
assert!(store.check_limit(client_ip_1).await.is_ok());
}
assert!(store.check_limit(client_ip_1).await.is_err());
// Client 2 should still be able to make requests
for _ in 0..config.bucket_size {
assert!(store.check_limit(client_ip_2).await.is_ok());
}
assert!(store.check_limit(client_ip_2).await.is_err());
}
#[tokio::test]
async fn test_auth_rate_limiting_middleware_success() {
// Create a mock AppState with test environment
let state = AppState {
surrealdb_ws: Default::default(),
surrealdb_mem: Default::default(),
user_lookup_service: Default::default(),
auth_repository: Default::default(),
env: Environment::Test,
};
// Create a mock request to /auth/login
let mut request = Request::builder()
.uri("/v1/auth/login")
.header("x-forwarded-for", "127.0.0.1")
.body(())
.unwrap();
// Create a mock next service
let next = Next::new(|req| async move {
let response = Response::builder()
.status(200)
.body("Login successful")
.unwrap();
Ok::<_, axum::http::StatusCode>((req, response))
});
// Call the middleware
let result = auth_rate_limiting_middleware(
axum::Extension(state.clone()),
request,
next,
).await;
// Should succeed
assert!(result.is_ok());
let response = result.unwrap();
assert_eq!(response.status(), 200);
}
#[tokio::test]
async fn test_auth_rate_limiting_middleware_429() {
// Create test configuration with very low limits for testing
let config = RateLimitConfig {
max_requests: 1,
window_duration: Duration::from_secs(10),
bucket_size: 1,
refill_rate: 1,
};
// Create a mock AppState with test environment
let state = AppState {
surrealdb_ws: Default::default(),
surrealdb_mem: Default::default(),
user_lookup_service: Default::default(),
auth_repository: Default::default(),
env: Environment::Test,
};
// Create a mock request to /auth/login
let mut request = Request::builder()
.uri("/v1/auth/login")
.header("x-forwarded-for", "127.0.0.1")
.body(())
.unwrap();
// Create a mock next service
let next = Next::new(|req| async move {
let response = Response::builder()
.status(200)
.body("Login successful")
.unwrap();
Ok::<_, axum::http::StatusCode>((req, response))
});
// First request should succeed
let result = auth_rate_limiting_middleware(
axum::Extension(state.clone()),
request.clone(),
next.clone(),
).await;
assert!(result.is_ok());
// Second request should fail with 429
let result = auth_rate_limiting_middleware(
axum::Extension(state),
request,
next,
).await;
assert!(result.is_ok());
let response = result.unwrap();
assert_eq!(response.status(), 429);
assert_eq!(response.headers().get("Retry-After").unwrap(), "60");
}
#[tokio::test]
async fn test_non_auth_endpoints_not_rate_limited() {
// Create a mock AppState with test environment
let state = AppState {
surrealdb_ws: Default::default(),
surrealdb_mem: Default::default(),
user_lookup_service: Default::default(),
auth_repository: Default::default(),
env: Environment::Test,
};
// Create a mock request to a non-auth endpoint
let mut request = Request::builder()
.uri("/v1/users/me")
.header("x-forwarded-for", "127.0.0.1")
.body(())
.unwrap();
// Create a mock next service
let next = Next::new(|req| async move {
let response = Response::builder()
.status(200)
.body("User data")
.unwrap();
Ok::<_, axum::http::StatusCode>((req, response))
});
// Call the middleware - should not apply rate limiting
let result = auth_rate_limiting_middleware(
axum::Extension(state),
request,
next,
).await;
// Should succeed
assert!(result.is_ok());
let response = result.unwrap();
assert_eq!(response.status(), 200);
}
#[tokio::test]
async fn test_environment_specific_configurations() {
// Test development config
let dev_config = RateLimitConfig::development();
assert_eq!(dev_config.max_requests, 100);
assert_eq!(dev_config.bucket_size, 50);
assert_eq!(dev_config.refill_rate, 10);
// Test production config
let prod_config = RateLimitConfig::production();
assert_eq!(prod_config.max_requests, 10);
assert_eq!(prod_config.bucket_size, 5);
assert_eq!(prod_config.refill_rate, 1);
// Test test config
let test_config = RateLimitConfig::test();
assert_eq!(test_config.max_requests, 1000);
assert_eq!(test_config.bucket_size, 100);
assert_eq!(test_config.refill_rate, 20);
}
}