perf(struct): ddd

This commit is contained in:
2026-02-03 17:31:08 +08:00
parent 202b5eaad5
commit 4a071bd7c8
64 changed files with 1214 additions and 1189 deletions

View File

@@ -1,5 +1,5 @@
use iam_service::models::{CreateAppRequest, ListAppsQuery, RequestAppStatusChangeRequest, UpdateAppRequest};
use iam_service::services::AppService;
use iam_service::application::services::AppService;
use sqlx::PgPool;
use uuid::Uuid;
@@ -90,4 +90,3 @@ async fn app_lifecycle_create_update_disable_approve_delete()
Ok(())
}

180
tests/code2token_modes.rs Normal file
View File

@@ -0,0 +1,180 @@
use axum::body::Body;
use axum::http::{Request, StatusCode};
use iam_service::presentation::http::api;
use iam_service::presentation::http::state::AppState;
use iam_service::infrastructure::repositories::tenant_config_repo::TenantConfigRepoPg;
use iam_service::models::CreateTenantRequest;
use iam_service::models::CreateUserRequest;
use iam_service::application::services::{
AppService, AuthService, AuthorizationService, ClientService, PermissionService, RoleService,
TenantService, UserService,
};
use redis::aio::ConnectionManager;
use sqlx::PgPool;
use tower::ServiceExt;
use uuid::Uuid;
#[tokio::test]
async fn code2token_modes_requirements() -> Result<(), Box<dyn std::error::Error>> {
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("/api/v1/auth/login-code")
.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("/api/v1/auth/code2token")
.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("/api/v1/auth/code2token")
.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("/api/v1/internal/auth/code2token")
.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(())
}

View File

@@ -1,4 +1,4 @@
use iam_service::services::TenantService;
use iam_service::application::services::TenantService;
use sqlx::PgPool;
use uuid::Uuid;

View File

@@ -5,7 +5,7 @@ use uuid::Uuid;
async fn jwks_endpoint_allows_rs256_verification_via_auth_kit() {
let app = Router::new().route(
"/.well-known/jwks.json",
get(iam_service::handlers::jwks_handler),
get(iam_service::presentation::http::handlers::jwks::jwks_handler),
);
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();

View File

@@ -1,11 +1,10 @@
use iam_service::application::services::{AuthService, TenantService, UserService};
use iam_service::models::{CreateUserRequest, LoginRequest};
use iam_service::services::{AuthService, TenantService, UserService};
use sqlx::PgPool;
use uuid::Uuid;
#[tokio::test]
async fn password_reset_self_and_admin_flow()
-> Result<(), Box<dyn std::error::Error>> {
async fn password_reset_self_and_admin_flow() -> Result<(), Box<dyn std::error::Error>> {
let database_url = match std::env::var("DATABASE_URL") {
Ok(v) if !v.trim().is_empty() => v,
_ => return Ok(()),
@@ -63,11 +62,12 @@ async fn password_reset_self_and_admin_flow()
},
)
.await?;
let active_tokens: i64 =
sqlx::query_scalar("SELECT COUNT(1) FROM refresh_tokens WHERE user_id = $1 AND is_revoked = FALSE")
.bind(user.id)
.fetch_one(&pool)
.await?;
let active_tokens: i64 = sqlx::query_scalar(
"SELECT COUNT(1) FROM refresh_tokens WHERE user_id = $1 AND is_revoked = FALSE",
)
.bind(user.id)
.fetch_one(&pool)
.await?;
assert!(active_tokens >= 1);
user_service
@@ -79,11 +79,12 @@ async fn password_reset_self_and_admin_flow()
)
.await?;
let revoked_tokens: i64 =
sqlx::query_scalar("SELECT COUNT(1) FROM refresh_tokens WHERE user_id = $1 AND is_revoked = TRUE")
.bind(user.id)
.fetch_one(&pool)
.await?;
let revoked_tokens: i64 = sqlx::query_scalar(
"SELECT COUNT(1) FROM refresh_tokens WHERE user_id = $1 AND is_revoked = TRUE",
)
.bind(user.id)
.fetch_one(&pool)
.await?;
assert!(revoked_tokens >= 1);
let old_login = auth_service
@@ -136,4 +137,3 @@ async fn password_reset_self_and_admin_flow()
let _ = login1;
Ok(())
}

View File

@@ -1,6 +1,6 @@
use hmac::{Hmac, Mac};
use iam_service::application::services::{AuthService, TenantService};
use iam_service::models::{CreateTenantRequest, CreateUserRequest, LoginRequest};
use iam_service::services::{AuthService, TenantService};
use sha2::Sha256;
use sqlx::PgPool;
use uuid::Uuid;
@@ -12,8 +12,7 @@ fn fingerprint(pepper: &str, token: &str) -> String {
}
#[tokio::test]
async fn refresh_token_rotate_and_expire_cases()
-> Result<(), Box<dyn std::error::Error>> {
async fn refresh_token_rotate_and_expire_cases() -> Result<(), Box<dyn std::error::Error>> {
let database_url = match std::env::var("DATABASE_URL") {
Ok(v) if !v.trim().is_empty() => v,
_ => return Ok(()),
@@ -104,4 +103,3 @@ async fn refresh_token_rotate_and_expire_cases()
Ok(())
}

View File

@@ -1,11 +1,12 @@
use iam_service::application::services::{
AuthService, AuthorizationService, RoleService, TenantService,
};
use iam_service::models::{CreateRoleRequest, CreateTenantRequest, CreateUserRequest};
use iam_service::services::{AuthService, AuthorizationService, RoleService, TenantService};
use sqlx::PgPool;
use uuid::Uuid;
#[tokio::test]
async fn role_permission_grant_and_wildcard_match()
-> Result<(), Box<dyn std::error::Error>> {
async fn role_permission_grant_and_wildcard_match() -> Result<(), Box<dyn std::error::Error>> {
let database_url = match std::env::var("DATABASE_URL") {
Ok(v) if !v.trim().is_empty() => v,
_ => return Ok(()),
@@ -71,12 +72,7 @@ async fn role_permission_grant_and_wildcard_match()
.await?;
role_service
.grant_permissions_to_role(
tenant.id,
role.id,
vec!["cms:*:*".to_string()],
admin.id,
)
.grant_permissions_to_role(tenant.id, role.id, vec!["cms:*:*".to_string()], admin.id)
.await?;
role_service
@@ -89,4 +85,3 @@ async fn role_permission_grant_and_wildcard_match()
Ok(())
}

View File

@@ -1,5 +1,5 @@
use iam_service::models::CreateRoleRequest;
use iam_service::services::{RoleService, TenantService};
use iam_service::application::services::{RoleService, TenantService};
use sqlx::PgPool;
use uuid::Uuid;