//! M7.1 — integration tests for `require_tenant_status`. //! //! Exercises the middleware end-to-end through an Axum router so we //! catch wiring bugs (extension propagation, method matching) that pure //! unit tests would miss. #![allow(clippy::expect_used, clippy::unwrap_used)] use axum::{ body::Body, extract::Request, http::{Method, StatusCode}, middleware::{from_fn, Next}, response::Response, routing::{get, post}, Router, }; use compliance_agent::api::auth_middleware::require_tenant_status; use compliance_core::{TenantContext, TenantStatus}; use tower::ServiceExt; fn ctx_with(status: TenantStatus) -> TenantContext { TenantContext { tenant_id: "t-1".to_string(), tenant_slug: "acme".to_string(), org_roles: vec![], products: vec![], plan: "starter".to_string(), status, user_id: "u-1".to_string(), user_name: None, } } fn router_with_ctx(ctx: Option) -> Router { let injector = move |mut req: Request, next: Next| { let ctx = ctx.clone(); async move { if let Some(c) = ctx { req.extensions_mut().insert(c); } next.run(req).await } }; Router::new() .route("/r", get(|| async { "read" })) .route("/w", post(|| async { "write" })) .layer(from_fn(require_tenant_status)) .layer(from_fn(injector)) } async fn call(router: Router, method: Method, path: &str) -> Response { let req = Request::builder() .method(method) .uri(path) .body(Body::empty()) .expect("request build"); router.oneshot(req).await.expect("oneshot") } #[tokio::test] async fn active_tenant_can_read_and_write() { let r = router_with_ctx(Some(ctx_with(TenantStatus::Active))); assert_eq!( call(r.clone(), Method::GET, "/r").await.status(), StatusCode::OK ); assert_eq!(call(r, Method::POST, "/w").await.status(), StatusCode::OK); } #[tokio::test] async fn trial_tenant_can_read_and_write() { let r = router_with_ctx(Some(ctx_with(TenantStatus::Trial))); assert_eq!( call(r.clone(), Method::GET, "/r").await.status(), StatusCode::OK ); assert_eq!(call(r, Method::POST, "/w").await.status(), StatusCode::OK); } #[tokio::test] async fn demo_tenant_can_read_and_write() { let r = router_with_ctx(Some(ctx_with(TenantStatus::Demo))); assert_eq!( call(r.clone(), Method::GET, "/r").await.status(), StatusCode::OK ); assert_eq!(call(r, Method::POST, "/w").await.status(), StatusCode::OK); } #[tokio::test] async fn frozen_tenant_can_read_but_not_write() { let r = router_with_ctx(Some(ctx_with(TenantStatus::Frozen))); assert_eq!( call(r.clone(), Method::GET, "/r").await.status(), StatusCode::OK ); assert_eq!( call(r, Method::POST, "/w").await.status(), StatusCode::PAYMENT_REQUIRED ); } #[tokio::test] async fn archived_tenant_is_gone_on_every_method() { let r = router_with_ctx(Some(ctx_with(TenantStatus::Archived))); assert_eq!( call(r.clone(), Method::GET, "/r").await.status(), StatusCode::GONE ); assert_eq!(call(r, Method::POST, "/w").await.status(), StatusCode::GONE); } #[tokio::test] async fn no_context_passes_through() { let r = router_with_ctx(None); assert_eq!( call(r.clone(), Method::GET, "/r").await.status(), StatusCode::OK ); assert_eq!(call(r, Method::POST, "/w").await.status(), StatusCode::OK); }