corteq 0.1.0

Enterprise-grade multi-tenant SaaS framework for Rust with security-first design
Documentation
//! Integration tests for tenant context extraction and JWT validation
//!
//! Following TDD: These tests are written FIRST, then we implement to make them pass.

use actix_web::{test, web, App, HttpResponse};
use chrono::Utc;
use corteq::{Claims, JwtService, TenantContext, TenantExtractor};
use uuid::Uuid;

#[actix_web::test]
async fn test_jwt_token_encode_decode() {
    // Test that JWT service can encode and decode tokens correctly
    let jwt_service = JwtService::new(b"test-secret");
    let tenant_id = Uuid::new_v4();
    let user_id = Uuid::new_v4();

    let claims = Claims {
        sub: user_id,
        tenant_id,
        roles: vec!["admin".to_string()],
        exp: (Utc::now() + chrono::Duration::hours(1)).timestamp(),
        iat: Utc::now().timestamp(),
    };

    let token = jwt_service.encode(&claims).expect("Should encode token");
    let decoded = jwt_service.decode(&token).expect("Should decode token");

    assert_eq!(decoded.sub, user_id);
    assert_eq!(decoded.tenant_id, tenant_id);
    assert_eq!(decoded.roles, vec!["admin"]);
}

#[actix_web::test]
async fn test_jwt_tenant_validation() {
    // Test that JWT service validates tenant ID correctly
    let jwt_service = JwtService::new(b"test-secret");
    let tenant_id = Uuid::new_v4();
    let user_id = Uuid::new_v4();

    let claims = Claims {
        sub: user_id,
        tenant_id,
        roles: vec!["user".to_string()],
        exp: (Utc::now() + chrono::Duration::hours(1)).timestamp(),
        iat: Utc::now().timestamp(),
    };

    let token = jwt_service.encode(&claims).expect("Should encode token");

    // Should succeed with correct tenant
    assert!(jwt_service.validate_tenant(&token, tenant_id).is_ok());

    // Should fail with wrong tenant
    let wrong_tenant = Uuid::new_v4();
    assert!(jwt_service.validate_tenant(&token, wrong_tenant).is_err());
}

#[actix_web::test]
async fn test_expired_token_rejected() {
    // Test that expired tokens are rejected
    let jwt_service = JwtService::new(b"test-secret");

    let claims = Claims {
        sub: Uuid::new_v4(),
        tenant_id: Uuid::new_v4(),
        roles: vec!["user".to_string()],
        exp: (Utc::now() - chrono::Duration::hours(1)).timestamp(), // Expired 1 hour ago
        iat: (Utc::now() - chrono::Duration::hours(2)).timestamp(),
    };

    let token = jwt_service.encode(&claims).expect("Should encode token");

    // Decode should fail for expired token
    assert!(jwt_service.decode(&token).is_err());
}

#[actix_web::test]
async fn test_tenant_extractor_requires_context() {
    // Test that TenantExtractor fails when no context is present
    async fn handler(tenant: TenantExtractor) -> HttpResponse {
        HttpResponse::Ok().json(serde_json::json!({
            "tenant_id": tenant.0.tenant_id.to_string()
        }))
    }

    let app = test::init_service(App::new().route("/test", web::get().to(handler))).await;

    let req = test::TestRequest::get().uri("/test").to_request();
    let resp = test::call_service(&app, req).await;

    // Should return 400 Bad Request when tenant context is missing
    assert_eq!(resp.status(), 400);
}

#[actix_web::test]
async fn test_tenant_extractor_with_context() {
    // Test that TenantExtractor works when context is injected
    async fn handler(tenant: TenantExtractor) -> HttpResponse {
        HttpResponse::Ok().json(serde_json::json!({
            "tenant_id": tenant.0.tenant_id.to_string(),
            "tenant_slug": tenant.0.tenant_slug
        }))
    }

    let tenant_id = Uuid::new_v4();
    let tenant_context = TenantContext::new(
        tenant_id,
        "test-tenant".to_string(),
        "test-key-id".to_string(),
    );

    let _app = test::init_service(
        App::new()
            .app_data(web::Data::new(tenant_context.clone()))
            .route("/test", web::get().to(handler)),
    )
    .await;

    // Manually inject tenant context into request extensions
    let _req = test::TestRequest::get()
        .uri("/test")
        .data(tenant_context.clone())
        .to_request();

    // For now this will fail because we haven't implemented the middleware
    // This test drives our implementation
    // TODO: Uncomment when middleware is implemented
    // let resp = test::call_service(&app, req).await;
    // assert_eq!(resp.status(), 200);
}

#[tokio::test]
async fn test_tenant_context_creation_sync() {
    // Test TenantContext can be created and holds correct data
    let tenant_id = Uuid::new_v4();
    let ctx = TenantContext::new(
        tenant_id,
        "acme-corp".to_string(),
        "encryption-key-123".to_string(),
    );

    assert_eq!(ctx.tenant_id, tenant_id);
    assert_eq!(ctx.tenant_slug, "acme-corp");
    assert_eq!(ctx.encryption_key_id, "encryption-key-123");
}

// Additional middleware and JWT validation tests are in test_middleware_integration.rs