use std::sync::{ Arc, atomic::{AtomicUsize, Ordering}, }; use std::time::Duration; use axum::{Json, Router, routing::post}; use axum::response::IntoResponse; use cms_service::infrastructure::iam_client::{IamClient, IamClientConfig}; use serde_json::Value; async fn start_mock_iam(call_count: Arc) -> (String, tokio::task::JoinHandle<()>) { let app = Router::new().route( "/api/v1/authorize/check-expr", post(move |Json(body): Json| { let call_count = call_count.clone(); async move { call_count.fetch_add(1, Ordering::SeqCst); let allowed = body.get("expr").is_some(); let resp = serde_json::json!({ "code": 0, "message": "ok", "data": { "allowed": allowed } }); (axum::http::StatusCode::OK, Json(resp)).into_response() } }), ); let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); let addr = listener.local_addr().unwrap(); let base_url = format!("http://{}", addr); let handle = tokio::spawn(async move { axum::serve(listener, app).await.unwrap(); }); (base_url, handle) } #[tokio::test] async fn iam_client_check_expr_hits_endpoint() { let call_count = Arc::new(AtomicUsize::new(0)); let (base_url, handle) = start_mock_iam(call_count.clone()).await; let client = IamClient::new(IamClientConfig { base_url, timeout: Duration::from_millis(500), cache_ttl: Duration::from_secs(5), cache_stale_if_error: Duration::from_secs(30), cache_max_entries: 1000, }); let tenant_id = uuid::Uuid::new_v4(); let user_id = uuid::Uuid::new_v4(); client .require_any_permissions( tenant_id, user_id, &["cms:article:edit", "cms:article:create"], "token", ) .await .unwrap(); assert_eq!(call_count.load(Ordering::SeqCst), 1); handle.abort(); }