use actix_web::{test, web, App, HttpResponse};
use chrono::Utc;
use corteq::{CorteqApp, TenantExtractor};
use uuid::Uuid;
async fn protected_handler(tenant: TenantExtractor) -> HttpResponse {
HttpResponse::Ok().json(serde_json::json!({
"tenant_id": tenant.0.tenant_id.to_string(),
"tenant_slug": tenant.0.tenant_slug,
}))
}
fn get_database_url() -> String {
std::env::var("DATABASE_URL")
.unwrap_or_else(|_| "postgres://postgres:postgres@172.24.0.3:5432/corteq_test".to_string())
}
async fn create_test_app() -> CorteqApp {
CorteqApp::builder()
.with_database(&get_database_url())
.with_jwt_secret("test-secret-key")
.build()
.await
.expect("Failed to create test app")
}
fn create_test_token(app: &CorteqApp, tenant_id: Uuid, user_id: Uuid) -> String {
let claims = corteq::auth::Claims {
sub: user_id,
tenant_id,
roles: vec!["user".to_string()],
exp: (Utc::now() + chrono::Duration::hours(1)).timestamp(),
iat: Utc::now().timestamp(),
};
app.jwt_service
.encode(&claims)
.expect("Failed to encode token")
}
#[actix_web::test]
async fn test_middleware_with_authorization_header() {
let app_config = create_test_app().await;
let tenant_id = Uuid::parse_str("11111111-1111-1111-1111-111111111111").unwrap();
let user_id = Uuid::new_v4();
let token = create_test_token(&app_config, tenant_id, user_id);
let app = test::init_service(
App::new()
.app_data(web::Data::new(app_config.clone()))
.wrap(corteq::middleware::TenantContextMiddleware)
.route("/protected", web::get().to(protected_handler)),
)
.await;
let req = test::TestRequest::get()
.uri("/protected")
.insert_header(("Authorization", format!("Bearer {token}")))
.to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(resp.status(), 200);
let body: serde_json::Value = test::read_body_json(resp).await;
assert_eq!(body["tenant_id"], tenant_id.to_string());
assert_eq!(body["tenant_slug"], "acme-corp");
}
#[actix_web::test]
async fn test_middleware_with_cookie() {
let app_config = create_test_app().await;
let tenant_id = Uuid::parse_str("11111111-1111-1111-1111-111111111111").unwrap();
let user_id = Uuid::new_v4();
let token = create_test_token(&app_config, tenant_id, user_id);
let app = test::init_service(
App::new()
.app_data(web::Data::new(app_config.clone()))
.wrap(corteq::middleware::TenantContextMiddleware)
.route("/protected", web::get().to(protected_handler)),
)
.await;
let req = test::TestRequest::get()
.uri("/protected")
.insert_header(("Cookie", format!("token={token}")))
.to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(resp.status(), 200);
let body: serde_json::Value = test::read_body_json(resp).await;
assert_eq!(body["tenant_id"], tenant_id.to_string());
}
#[actix_web::test]
async fn test_middleware_cookie_priority_over_header() {
let app_config = create_test_app().await;
let tenant_id_1 = Uuid::parse_str("11111111-1111-1111-1111-111111111111").unwrap();
let tenant_id_2 = Uuid::parse_str("22222222-2222-2222-2222-222222222222").unwrap();
let user_id = Uuid::new_v4();
let token_cookie = create_test_token(&app_config, tenant_id_1, user_id);
let token_header = create_test_token(&app_config, tenant_id_2, user_id);
let app = test::init_service(
App::new()
.app_data(web::Data::new(app_config.clone()))
.wrap(corteq::middleware::TenantContextMiddleware)
.route("/protected", web::get().to(protected_handler)),
)
.await;
let req = test::TestRequest::get()
.uri("/protected")
.insert_header(("Cookie", format!("token={token_cookie}")))
.insert_header(("Authorization", format!("Bearer {token_header}")))
.to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(resp.status(), 200);
let body: serde_json::Value = test::read_body_json(resp).await;
assert_eq!(body["tenant_id"], tenant_id_1.to_string());
assert_eq!(body["tenant_slug"], "acme-corp");
}
#[actix_web::test]
async fn test_middleware_rejects_missing_token() {
let app_config = create_test_app().await;
let app = test::init_service(
App::new()
.app_data(web::Data::new(app_config.clone()))
.wrap(corteq::middleware::TenantContextMiddleware)
.route("/protected", web::get().to(protected_handler)),
)
.await;
let req = test::TestRequest::get().uri("/protected").to_request();
let result = test::try_call_service(&app, req).await;
assert!(result.is_err());
let err_msg = format!("{}", result.unwrap_err());
assert!(err_msg.contains("Authentication required"));
}
#[actix_web::test]
async fn test_middleware_rejects_invalid_token() {
let app_config = create_test_app().await;
let app = test::init_service(
App::new()
.app_data(web::Data::new(app_config.clone()))
.wrap(corteq::middleware::TenantContextMiddleware)
.route("/protected", web::get().to(protected_handler)),
)
.await;
let req = test::TestRequest::get()
.uri("/protected")
.insert_header(("Authorization", "Bearer invalid-token-xyz"))
.to_request();
let result = test::try_call_service(&app, req).await;
assert!(result.is_err());
let err_msg = format!("{}", result.unwrap_err());
assert!(err_msg.contains("Invalid or expired token"));
}
#[actix_web::test]
async fn test_middleware_rejects_expired_token() {
let app_config = create_test_app().await;
let tenant_id = Uuid::parse_str("11111111-1111-1111-1111-111111111111").unwrap();
let user_id = Uuid::new_v4();
let claims = corteq::auth::Claims {
sub: user_id,
tenant_id,
roles: vec!["user".to_string()],
exp: (Utc::now() - chrono::Duration::hours(1)).timestamp(),
iat: (Utc::now() - chrono::Duration::hours(2)).timestamp(),
};
let expired_token = app_config
.jwt_service
.encode(&claims)
.expect("Failed to encode token");
let app = test::init_service(
App::new()
.app_data(web::Data::new(app_config.clone()))
.wrap(corteq::middleware::TenantContextMiddleware)
.route("/protected", web::get().to(protected_handler)),
)
.await;
let req = test::TestRequest::get()
.uri("/protected")
.insert_header(("Authorization", format!("Bearer {expired_token}")))
.to_request();
let result = test::try_call_service(&app, req).await;
assert!(result.is_err());
let err_msg = format!("{}", result.unwrap_err());
assert!(err_msg.contains("Invalid or expired token"));
}
#[actix_web::test]
async fn test_middleware_rejects_nonexistent_tenant() {
let app_config = create_test_app().await;
let nonexistent_tenant_id = Uuid::new_v4(); let user_id = Uuid::new_v4();
let token = create_test_token(&app_config, nonexistent_tenant_id, user_id);
let app = test::init_service(
App::new()
.app_data(web::Data::new(app_config.clone()))
.wrap(corteq::middleware::TenantContextMiddleware)
.route("/protected", web::get().to(protected_handler)),
)
.await;
let req = test::TestRequest::get()
.uri("/protected")
.insert_header(("Authorization", format!("Bearer {token}")))
.to_request();
let result = test::try_call_service(&app, req).await;
assert!(result.is_err());
let err_msg = format!("{}", result.unwrap_err());
assert!(err_msg.contains("Tenant not found"));
}
#[actix_web::test]
async fn test_middleware_caches_tenant_context() {
let app_config = create_test_app().await;
let tenant_id = Uuid::parse_str("11111111-1111-1111-1111-111111111111").unwrap();
let user_id = Uuid::new_v4();
let token = create_test_token(&app_config, tenant_id, user_id);
assert!(app_config.tenant_cache.get(&tenant_id).await.is_none());
let app = test::init_service(
App::new()
.app_data(web::Data::new(app_config.clone()))
.wrap(corteq::middleware::TenantContextMiddleware)
.route("/protected", web::get().to(protected_handler)),
)
.await;
let req = test::TestRequest::get()
.uri("/protected")
.insert_header(("Authorization", format!("Bearer {token}")))
.to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(resp.status(), 200);
let cached = app_config.tenant_cache.get(&tenant_id).await;
assert!(cached.is_some());
assert_eq!(cached.unwrap().tenant_id, tenant_id);
}