use axum::body::Body; use axum::http::{Request, StatusCode}; use iam_service::application::services::{ AppService, AuthService, AuthorizationService, ClientService, PermissionService, RoleService, TenantService, UserService, }; use iam_service::constants::CANONICAL_BASE; use iam_service::infrastructure::repositories::tenant_config_repo::TenantConfigRepoPg; use iam_service::models::CreateTenantRequest; use iam_service::models::CreateUserRequest; use iam_service::presentation::http::api; use iam_service::presentation::http::state::AppState; use redis::aio::ConnectionManager; use sqlx::PgPool; use tower::ServiceExt; use uuid::Uuid; #[tokio::test] async fn code2token_modes_requirements() -> Result<(), Box> { let database_url = match std::env::var("DATABASE_URL") { Ok(v) if !v.trim().is_empty() => v, _ => return Ok(()), }; let redis_url = match std::env::var("REDIS_URL") { Ok(v) if !v.trim().is_empty() => v, _ => return Ok(()), }; let auth_code_jwt_secret = match std::env::var("AUTH_CODE_JWT_SECRET") { Ok(v) if !v.trim().is_empty() => v, _ => return Ok(()), }; let internal_psk = match std::env::var("INTERNAL_EXCHANGE_PSK") { Ok(v) if !v.trim().is_empty() => v, _ => return Ok(()), }; let pool = PgPool::connect(&database_url).await?; let redis = redis::Client::open(redis_url)?; let redis: ConnectionManager = ConnectionManager::new(redis).await?; let auth_service = AuthService::new(pool.clone(), "test".to_string()); let client_service = ClientService::new(pool.clone(), 30); let user_service = UserService::new(pool.clone()); let role_service = RoleService::new(pool.clone()); let tenant_service = TenantService::new(pool.clone()); let authorization_service = AuthorizationService::new(pool.clone()); let app_service = AppService::new(pool.clone()); let permission_service = PermissionService::new(pool.clone()); let tenant_config_repo = std::sync::Arc::new(TenantConfigRepoPg::new(pool.clone())); let state = AppState { auth_service: auth_service.clone(), client_service: client_service.clone(), user_service, role_service, tenant_service: tenant_service.clone(), authorization_service, app_service, permission_service, redis, auth_code_jwt_secret: auth_code_jwt_secret.clone(), tenant_config_repo, }; let tenant = tenant_service .create_tenant(CreateTenantRequest { name: format!("t-{}", Uuid::new_v4()), config: None, }) .await?; let email = format!("u{}@example.com", Uuid::new_v4()); let password = "P@ssw0rd123!".to_string(); let _user = auth_service .register( tenant.id, CreateUserRequest { email: email.clone(), password: password.clone(), }, ) .await?; let client_id = format!("cms{}", Uuid::new_v4().to_string().replace('-', "")); let client_id = &client_id[..std::cmp::min(24, client_id.len())]; let redirect_uri = "http://localhost:5031/auth/callback".to_string(); let client_secret = client_service .create_client( client_id.to_string(), Some("CMS".to_string()), Some(vec![redirect_uri.clone()]), ) .await?; let app = api::build_app(state); let login_code_req = serde_json::json!({ "clientId": client_id, "redirectUri": redirect_uri, "email": email, "password": password }); let resp = app .clone() .oneshot( Request::builder() .method("POST") .uri(format!("{}/auth/login-code", CANONICAL_BASE)) .header("Content-Type", "application/json") .header("X-Tenant-ID", tenant.id.to_string()) .body(Body::from(login_code_req.to_string()))?, ) .await?; assert_eq!(resp.status(), StatusCode::OK); let body = axum::body::to_bytes(resp.into_body(), usize::MAX).await?; let v: serde_json::Value = serde_json::from_slice(&body)?; let redirect_to = v["data"]["redirectTo"].as_str().unwrap_or_default(); let url = url::Url::parse(redirect_to)?; let code = url .query_pairs() .find(|(k, _)| k == "code") .map(|(_, v)| v.to_string()) .unwrap_or_default(); assert!(!code.is_empty()); let code2token_req = serde_json::json!({ "code": code, "clientId": client_id, "clientSecret": "wrong" }); let resp = app .clone() .oneshot( Request::builder() .method("POST") .uri(format!("{}/auth/code2token", CANONICAL_BASE)) .header("Content-Type", "application/json") .header("X-Tenant-ID", tenant.id.to_string()) .body(Body::from(code2token_req.to_string()))?, ) .await?; assert_eq!(resp.status(), StatusCode::UNAUTHORIZED); let code2token_req_missing_tenant = serde_json::json!({ "code": url.query_pairs().find(|(k, _)| k == "code").unwrap().1, "clientId": client_id, "clientSecret": client_secret }); let resp = app .clone() .oneshot( Request::builder() .method("POST") .uri(format!("{}/auth/code2token", CANONICAL_BASE)) .header("Content-Type", "application/json") .body(Body::from(code2token_req_missing_tenant.to_string()))?, ) .await?; assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let code2token_req_internal = serde_json::json!({ "code": url.query_pairs().find(|(k, _)| k == "code").unwrap().1, "clientId": client_id, "clientSecret": client_secret }); let resp = app .oneshot( Request::builder() .method("POST") .uri(format!("{}/internal/auth/code2token", CANONICAL_BASE)) .header("Content-Type", "application/json") .header("X-Internal-Token", internal_psk) .body(Body::from(code2token_req_internal.to_string()))?, ) .await?; assert_eq!(resp.status(), StatusCode::OK); Ok(()) }