316 lines
8.4 KiB
Rust
316 lines
8.4 KiB
Rust
//! Integration tests for the Servme HTTP framework.
|
|
//!
|
|
//! These tests verify the end-to-end functionality of the server
|
|
//! including middleware chains and request handling.
|
|
|
|
use http_body_util::Full;
|
|
use hyper::{body::Bytes, Request, Response};
|
|
use servme::{
|
|
Responder, Server, ServerBuilder, ServerConfig, ServerError, UrlExtract,
|
|
middleware::{Claims, Middleware, MiddlewareFuture, MiddlewareResult},
|
|
};
|
|
use std::net::IpAddr;
|
|
use tokio::time::{timeout, Duration};
|
|
|
|
// Helper to create a simple in-memory test
|
|
mod helpers {
|
|
use super::*;
|
|
|
|
/// A simple middleware that adds a custom header
|
|
pub struct TestMiddleware;
|
|
|
|
impl TestMiddleware {
|
|
pub fn new() -> Self {
|
|
Self
|
|
}
|
|
}
|
|
|
|
impl Middleware for TestMiddleware {
|
|
fn run(&self, req: Request<Incoming>) -> MiddlewareFuture<'_> {
|
|
Box::pin(async move {
|
|
MiddlewareResult::Continue(req)
|
|
})
|
|
}
|
|
}
|
|
|
|
use http::Request;
|
|
use hyper::body::Incoming;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Server Configuration Tests
|
|
// ============================================================================
|
|
|
|
#[test]
|
|
fn test_server_builder_default_config() {
|
|
let builder = ServerBuilder::new();
|
|
|
|
// Verify default values
|
|
assert_eq!(builder.config.ip, "127.0.0.1");
|
|
assert_eq!(builder.config.port, 8080);
|
|
assert!(builder.middlewares.is_empty());
|
|
assert!(builder.data.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_server_builder_with_address() {
|
|
let builder = ServerBuilder::new()
|
|
.address("0.0.0.0", 3000);
|
|
|
|
assert_eq!(builder.config.ip, "0.0.0.0");
|
|
assert_eq!(builder.config.port, 3000);
|
|
}
|
|
|
|
#[test]
|
|
fn test_server_builder_chaining() {
|
|
let server = ServerBuilder::new()
|
|
.address("127.0.0.1", 8080)
|
|
.add_api_key_middleware("test-key")
|
|
.build();
|
|
|
|
assert_eq!(server.config.ip, "127.0.0.1");
|
|
assert_eq!(server.config.port, 8080);
|
|
assert_eq!(server.middlewares.len(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn test_server_config_default() {
|
|
let config = ServerConfig::default();
|
|
assert_eq!(config.ip, "127.0.0.1");
|
|
assert_eq!(config.port, 8080);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Responder Tests
|
|
// ============================================================================
|
|
|
|
#[test]
|
|
fn test_responder_ok() {
|
|
let result = Responder::ok("Hello");
|
|
assert!(result.is_ok());
|
|
|
|
let response = result.unwrap();
|
|
assert_eq!(response.status(), http::StatusCode::OK);
|
|
}
|
|
|
|
#[test]
|
|
fn test_responder_json() {
|
|
#[derive(serde::Serialize)]
|
|
struct TestData {
|
|
name: String,
|
|
value: i32,
|
|
}
|
|
|
|
let data = TestData {
|
|
name: "test".to_string(),
|
|
value: 42,
|
|
};
|
|
|
|
let result = Responder::json(&data);
|
|
assert!(result.is_ok());
|
|
|
|
let response = result.unwrap();
|
|
assert_eq!(response.status(), http::StatusCode::OK);
|
|
assert_eq!(
|
|
response.headers().get("content-type").unwrap(),
|
|
"application/json"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_responder_redirect() {
|
|
let result = Responder::redirect("/new-location");
|
|
assert!(result.is_ok());
|
|
|
|
let response = result.unwrap();
|
|
assert_eq!(response.status(), http::StatusCode::SEE_OTHER);
|
|
assert_eq!(
|
|
response.headers().get("location").unwrap(),
|
|
"/new-location"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_responder_redirect_empty_fails() {
|
|
let result = Responder::redirect("");
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_responder_status_codes() {
|
|
assert!(Responder::not_found().is_ok());
|
|
assert!(Responder::unauthorized().is_ok());
|
|
assert!(Responder::forbidden().is_ok());
|
|
assert!(Responder::bad_request("bad").is_ok());
|
|
assert!(Responder::no_content().is_ok());
|
|
assert!(Responder::internal_error("error").is_ok());
|
|
|
|
let response = Responder::not_found().unwrap();
|
|
assert_eq!(response.status(), http::StatusCode::NOT_FOUND);
|
|
|
|
let response = Responder::unauthorized().unwrap();
|
|
assert_eq!(response.status(), http::StatusCode::UNAUTHORIZED);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Middleware Tests
|
|
// ============================================================================
|
|
|
|
#[test]
|
|
fn test_api_key_middleware_creation() {
|
|
use servme::middleware::ApiKeyMiddleware;
|
|
|
|
let middleware = ApiKeyMiddleware::new("test-key");
|
|
assert_eq!(middleware.api_key, "test-key");
|
|
}
|
|
|
|
#[test]
|
|
fn test_ip_filter_middleware_validation() {
|
|
use servme::middleware::IpFilterMiddleware;
|
|
|
|
// Valid IPs should work
|
|
let result = IpFilterMiddleware::new(vec![], false);
|
|
assert!(result.is_ok());
|
|
|
|
let result = IpFilterMiddleware::new(
|
|
vec!["192.168.1.1".to_string(), "10.0.0.1".to_string()],
|
|
false
|
|
);
|
|
assert!(result.is_ok());
|
|
|
|
// Invalid IP should fail
|
|
let result = IpFilterMiddleware::new(
|
|
vec!["not-an-ip".to_string()],
|
|
false
|
|
);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_ip_filter_authorization() {
|
|
use servme::middleware::IpFilterMiddleware;
|
|
|
|
// Test with unchecked for simpler testing
|
|
let middleware = IpFilterMiddleware::new_unchecked(
|
|
vec!["192.168.1.100".to_string()],
|
|
false
|
|
);
|
|
|
|
let allowed_ip: IpAddr = "192.168.1.100".parse().unwrap();
|
|
let denied_ip: IpAddr = "192.168.1.200".parse().unwrap();
|
|
|
|
assert!(middleware.is_authorized(&allowed_ip));
|
|
assert!(!middleware.is_authorized(&denied_ip));
|
|
}
|
|
|
|
#[test]
|
|
fn test_ip_filter_ipv6() {
|
|
use servme::middleware::IpFilterMiddleware;
|
|
|
|
let middleware = IpFilterMiddleware::new_unchecked(
|
|
vec!["::1".to_string()],
|
|
false,
|
|
);
|
|
|
|
let ipv6_local: IpAddr = "::1".parse().unwrap();
|
|
let ipv6_other: IpAddr = "::2".parse().unwrap();
|
|
|
|
assert!(middleware.is_authorized(&ipv6_local));
|
|
assert!(!middleware.is_authorized(&ipv6_other));
|
|
}
|
|
|
|
// ============================================================================
|
|
// URL Extract Tests
|
|
// ============================================================================
|
|
|
|
#[test]
|
|
fn test_url_extract_params() {
|
|
use http::Uri;
|
|
|
|
let uri: Uri = "/api?name=test&value=42".parse().unwrap();
|
|
let extractor = UrlExtract::new(&uri);
|
|
|
|
assert_eq!(extractor.param_str("name"), Some("test".to_string()));
|
|
assert_eq!(extractor.param_i64("value"), Some(42));
|
|
}
|
|
|
|
#[test]
|
|
fn test_url_extract_missing_param() {
|
|
use http::Uri;
|
|
|
|
let uri: Uri = "/api".parse().unwrap();
|
|
let extractor = UrlExtract::new(&uri);
|
|
|
|
assert_eq!(extractor.param_str("missing"), None);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Claims Tests
|
|
// ============================================================================
|
|
|
|
#[test]
|
|
fn test_claims_is_expired() {
|
|
use servme::middleware::auth_types::Claims;
|
|
|
|
let claims = Claims {
|
|
sub: "user123".to_string(),
|
|
exp: 1000, // Very old timestamp
|
|
};
|
|
|
|
assert!(claims.is_expired(2000)); // Current time > exp
|
|
assert!(!claims.is_expired(500)); // Current time < exp
|
|
}
|
|
|
|
#[test]
|
|
fn test_claims_username() {
|
|
use servme::middleware::auth_types::Claims;
|
|
|
|
let claims = Claims {
|
|
sub: "testuser".to_string(),
|
|
exp: 9999999999,
|
|
};
|
|
|
|
assert_eq!(claims.username(), "testuser");
|
|
}
|
|
|
|
// ============================================================================
|
|
// Error Handling Tests
|
|
// ============================================================================
|
|
|
|
#[test]
|
|
fn test_server_error_display() {
|
|
let error = ServerError::bind("127.0.0.1:8080", std::io::Error::new(
|
|
std::io::ErrorKind::AddrInUse,
|
|
"Address already in use"
|
|
));
|
|
|
|
let display = format!("{}", error);
|
|
assert!(display.contains("Failed to bind"));
|
|
assert!(display.contains("127.0.0.1:8080"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_server_error_validation() {
|
|
let error = ServerError::validation("field", "must not be empty");
|
|
|
|
let display = format!("{}", error);
|
|
assert!(display.contains("Validation failed"));
|
|
assert!(display.contains("field"));
|
|
}
|
|
|
|
// ============================================================================
|
|
// Constants Tests
|
|
// ============================================================================
|
|
|
|
#[test]
|
|
fn test_constants_values() {
|
|
use servme::constants::*;
|
|
|
|
assert_eq!(DEFAULT_HOST, "127.0.0.1");
|
|
assert_eq!(DEFAULT_PORT, 8080);
|
|
assert_eq!(JWT_COOKIE_NAME, "access_token");
|
|
assert_eq!(BEARER_PREFIX, "Bearer ");
|
|
assert!(FILE_EXTENSIONS.contains(&".json"));
|
|
assert!(FILE_EXTENSIONS.contains(&".html"));
|
|
}
|