use axum::extract::Request;
use axum::middleware::Next;
use axum::response::{IntoResponse, Response};
use std::sync::Arc;
use crate::error::{BaseException, ErrorCode};
use crate::middleware::auth::{base_exception_to_response, AuthenticatedUser};
#[derive(Debug, Clone)]
pub struct GuardError {
pub code: ErrorCode,
pub msg: String,
}
impl GuardError {
pub fn new(code: ErrorCode, msg: impl Into<String>) -> Self {
Self {
code,
msg: msg.into(),
}
}
pub fn not_login(msg: impl Into<String>) -> Self {
Self::new(ErrorCode::NotLogin, msg)
}
pub fn forbidden(msg: impl Into<String>) -> Self {
Self::new(ErrorCode::Forbidden, msg)
}
pub fn user_disabled(msg: impl Into<String>) -> Self {
Self::new(ErrorCode::UserDisabled, msg)
}
}
impl IntoResponse for GuardError {
fn into_response(self) -> Response {
let exc = BaseException::new(self.code, self.msg);
base_exception_to_response(exc)
}
}
impl From<GuardError> for BaseException {
fn from(err: GuardError) -> Self {
BaseException::new(err.code, err.msg)
}
}
impl std::fmt::Display for GuardError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[{}] {}", self.code.as_i32(), self.msg)
}
}
impl std::error::Error for GuardError {}
pub struct UserContext {
pub user_id: i64,
pub is_super: bool,
pub roles: Vec<String>,
pub permissions: Vec<String>,
}
impl UserContext {
pub fn new(user_id: i64) -> Self {
Self {
user_id,
is_super: false,
roles: Vec::new(),
permissions: Vec::new(),
}
}
pub fn with_super(mut self, is_super: bool) -> Self {
self.is_super = is_super;
self
}
pub fn with_roles(mut self, roles: Vec<String>) -> Self {
self.roles = roles;
self
}
pub fn with_permissions(mut self, permissions: Vec<String>) -> Self {
self.permissions = permissions;
self
}
pub fn has_role(&self, role: &str) -> bool {
self.roles.iter().any(|r| r == role)
}
pub fn has_permission(&self, permission: &str) -> bool {
if self.permissions.iter().any(|p| p == permission) {
return true;
}
for perm in &self.permissions {
if perm.ends_with("/*") {
let prefix = &perm[..perm.len() - 1]; if permission.starts_with(prefix) {
return true;
}
}
}
false
}
}
impl std::fmt::Debug for UserContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("UserContext")
.field("user_id", &self.user_id)
.field("is_super", &self.is_super)
.field("roles", &self.roles)
.field("permissions", &self.permissions)
.finish()
}
}
impl Clone for UserContext {
fn clone(&self) -> Self {
Self {
user_id: self.user_id,
is_super: self.is_super,
roles: self.roles.clone(),
permissions: self.permissions.clone(),
}
}
}
impl Default for UserContext {
fn default() -> Self {
Self::new(0)
}
}
impl From<AuthenticatedUser> for UserContext {
fn from(user: AuthenticatedUser) -> Self {
Self::new(user.user_id)
}
}
pub trait Guard: Send + Sync {
fn check(&self, req: &Request) -> Result<(), GuardError>;
}
#[derive(Debug, Default)]
pub struct AuthGuard;
impl AuthGuard {
pub fn new() -> Self {
Self
}
}
impl Guard for AuthGuard {
fn check(&self, req: &Request) -> Result<(), GuardError> {
if req.extensions().get::<AuthenticatedUser>().is_some() {
Ok(())
} else {
Err(GuardError::not_login("not_login"))
}
}
}
#[derive(Debug, Default)]
pub struct AdminGuard;
impl AdminGuard {
pub fn new() -> Self {
Self
}
}
impl Guard for AdminGuard {
fn check(&self, req: &Request) -> Result<(), GuardError> {
let _user = req
.extensions()
.get::<AuthenticatedUser>()
.ok_or_else(|| GuardError::not_login("not_login"))?;
let user_ctx = req
.extensions()
.get::<UserContext>()
.ok_or_else(|| GuardError::forbidden("无权限访问"))?;
if user_ctx.is_super {
Ok(())
} else {
Err(GuardError::forbidden("无权限访问"))
}
}
}
#[derive(Debug)]
pub struct PermissionGuard {
pub permission: String,
}
impl PermissionGuard {
pub fn new(permission: impl Into<String>) -> Self {
Self {
permission: permission.into(),
}
}
}
impl Guard for PermissionGuard {
fn check(&self, req: &Request) -> Result<(), GuardError> {
let _user = req
.extensions()
.get::<AuthenticatedUser>()
.ok_or_else(|| GuardError::not_login("not_login"))?;
let user_ctx = req
.extensions()
.get::<UserContext>()
.ok_or_else(|| GuardError::forbidden("无权限访问"))?;
if user_ctx.is_super {
return Ok(());
}
if user_ctx.has_permission(&self.permission) {
Ok(())
} else {
Err(GuardError::forbidden("无权限访问"))
}
}
}
#[derive(Debug)]
pub struct RoleGuard {
pub role: String,
}
impl RoleGuard {
pub fn new(role: impl Into<String>) -> Self {
Self { role: role.into() }
}
}
impl Guard for RoleGuard {
fn check(&self, req: &Request) -> Result<(), GuardError> {
let _user = req
.extensions()
.get::<AuthenticatedUser>()
.ok_or_else(|| GuardError::not_login("not_login"))?;
let user_ctx = req
.extensions()
.get::<UserContext>()
.ok_or_else(|| GuardError::forbidden("无权限访问"))?;
if user_ctx.is_super {
return Ok(());
}
if user_ctx.has_role(&self.role) {
Ok(())
} else {
Err(GuardError::forbidden("无权限访问"))
}
}
}
pub struct GuardChain {
pub guards: Vec<Arc<dyn Guard>>,
}
impl GuardChain {
pub fn new() -> Self {
Self { guards: Vec::new() }
}
pub fn with_guard(mut self, guard: Arc<dyn Guard>) -> Self {
self.guards.push(guard);
self
}
pub fn from_guards(guards: Vec<Arc<dyn Guard>>) -> Self {
Self { guards }
}
}
impl Default for GuardChain {
fn default() -> Self {
Self::new()
}
}
impl Guard for GuardChain {
fn check(&self, req: &Request) -> Result<(), GuardError> {
for guard in &self.guards {
guard.check(req)?;
}
Ok(())
}
}
pub async fn guard_middleware(
axum::extract::State(guard): axum::extract::State<Arc<dyn Guard>>,
req: Request,
next: Next,
) -> Response {
match guard.check(&req) {
Ok(()) => next.run(req).await,
Err(err) => err.into_response(),
}
}
pub fn check_guards(req: &Request, guards: &[Arc<dyn Guard>]) -> Result<(), GuardError> {
for guard in guards {
guard.check(req)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use axum::body::Body;
use axum::http::StatusCode;
use axum::Router;
use http_body_util::BodyExt;
use tower::ServiceExt;
fn make_request() -> Request {
Request::builder()
.method("GET")
.uri("/test")
.body(Body::empty())
.unwrap()
}
fn make_request_with_user(user_id: i64) -> Request {
let mut req = make_request();
req.extensions_mut().insert(AuthenticatedUser { user_id });
req
}
fn make_request_with_context(user_ctx: UserContext) -> Request {
let mut req = make_request_with_user(user_ctx.user_id);
req.extensions_mut().insert(user_ctx);
req
}
async fn read_body(resp: Response) -> String {
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
String::from_utf8(bytes.to_vec()).unwrap()
}
fn build_app(guard: Arc<dyn Guard>) -> Router {
Router::new()
.route(
"/protected",
axum::routing::get(|| async { axum::http::StatusCode::OK }),
)
.layer(axum::middleware::from_fn_with_state(
guard,
guard_middleware,
))
}
#[test]
fn test_guard_error_new() {
let err = GuardError::new(ErrorCode::Forbidden, "无权限");
assert_eq!(err.code, ErrorCode::Forbidden);
assert_eq!(err.msg, "无权限");
}
#[test]
fn test_guard_error_not_login() {
let err = GuardError::not_login("not_login");
assert_eq!(err.code, ErrorCode::NotLogin);
assert_eq!(err.msg, "not_login");
assert_eq!(err.code.as_i32(), -1);
}
#[test]
fn test_guard_error_forbidden() {
let err = GuardError::forbidden("无权限访问");
assert_eq!(err.code, ErrorCode::Forbidden);
assert_eq!(err.msg, "无权限访问");
assert_eq!(err.code.as_i32(), 403);
}
#[test]
fn test_guard_error_user_disabled() {
let err = GuardError::user_disabled("您已离职");
assert_eq!(err.code, ErrorCode::UserDisabled);
assert_eq!(err.msg, "您已离职");
assert_eq!(err.code.as_i32(), -3);
}
#[test]
fn test_guard_error_display() {
let err = GuardError::not_login("not_login");
assert_eq!(format!("{}", err), "[-1] not_login");
}
#[test]
fn test_guard_error_clone() {
let err = GuardError::forbidden("无权限");
let cloned = err.clone();
assert_eq!(err.code, cloned.code);
assert_eq!(err.msg, cloned.msg);
}
#[test]
fn test_guard_error_into_response_not_login() {
let err = GuardError::not_login("not_login");
let resp = err.into_response();
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}
#[test]
fn test_guard_error_into_response_forbidden() {
let err = GuardError::forbidden("无权限访问");
let resp = err.into_response();
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
}
#[test]
fn test_guard_error_into_response_user_disabled() {
let err = GuardError::user_disabled("您已离职");
let resp = err.into_response();
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
}
#[test]
fn test_guard_error_into_base_exception() {
let err = GuardError::not_login("not_login");
let exc: BaseException = err.into();
assert_eq!(exc.code, -1);
assert_eq!(exc.msg, "not_login");
}
#[tokio::test]
async fn test_guard_error_response_body_format() {
let err = GuardError::not_login("not_login");
let resp = err.into_response();
let body = read_body(resp).await;
let json: serde_json::Value = serde_json::from_str(&body).unwrap();
assert_eq!(json["code"], -1);
assert_eq!(json["msg"], "not_login");
assert_eq!(json["data"], serde_json::json!({}));
}
#[test]
fn test_user_context_new() {
let ctx = UserContext::new(100);
assert_eq!(ctx.user_id, 100);
assert!(!ctx.is_super);
assert!(ctx.roles.is_empty());
assert!(ctx.permissions.is_empty());
}
#[test]
fn test_user_context_with_super() {
let ctx = UserContext::new(1).with_super(true);
assert!(ctx.is_super);
}
#[test]
fn test_user_context_with_roles() {
let ctx = UserContext::new(1).with_roles(vec!["admin".to_string(), "editor".to_string()]);
assert_eq!(ctx.roles, vec!["admin", "editor"]);
}
#[test]
fn test_user_context_with_permissions() {
let ctx = UserContext::new(1)
.with_permissions(vec!["user/list".to_string(), "user/save".to_string()]);
assert_eq!(ctx.permissions, vec!["user/list", "user/save"]);
}
#[test]
fn test_user_context_has_role() {
let ctx = UserContext::new(1).with_roles(vec!["admin".to_string(), "editor".to_string()]);
assert!(ctx.has_role("admin"));
assert!(ctx.has_role("editor"));
assert!(!ctx.has_role("guest"));
}
#[test]
fn test_user_context_has_role_empty() {
let ctx = UserContext::new(1);
assert!(!ctx.has_role("admin"));
}
#[test]
fn test_user_context_has_permission_exact() {
let ctx = UserContext::new(1).with_permissions(vec!["user/list".to_string()]);
assert!(ctx.has_permission("user/list"));
assert!(!ctx.has_permission("user/save"));
}
#[test]
fn test_user_context_has_permission_wildcard() {
let ctx = UserContext::new(1).with_permissions(vec!["user/*".to_string()]);
assert!(ctx.has_permission("user/list"));
assert!(ctx.has_permission("user/save"));
assert!(ctx.has_permission("user/delete"));
assert!(!ctx.has_permission("order/list"));
}
#[test]
fn test_user_context_has_permission_empty() {
let ctx = UserContext::new(1);
assert!(!ctx.has_permission("user/list"));
}
#[test]
fn test_user_context_has_permission_multiple() {
let ctx = UserContext::new(1).with_permissions(vec![
"user/list".to_string(),
"order/*".to_string(),
"system/config".to_string(),
]);
assert!(ctx.has_permission("user/list"));
assert!(ctx.has_permission("system/config"));
assert!(ctx.has_permission("order/list"));
assert!(ctx.has_permission("order/save"));
assert!(!ctx.has_permission("user/save"));
assert!(!ctx.has_permission("product/list"));
}
#[test]
fn test_user_context_from_authenticated_user() {
let user = AuthenticatedUser { user_id: 42 };
let ctx = UserContext::from(user);
assert_eq!(ctx.user_id, 42);
assert!(!ctx.is_super);
assert!(ctx.roles.is_empty());
assert!(ctx.permissions.is_empty());
}
#[test]
fn test_user_context_default() {
let ctx = UserContext::default();
assert_eq!(ctx.user_id, 0);
assert!(!ctx.is_super);
}
#[test]
fn test_user_context_clone() {
let ctx = UserContext::new(1)
.with_super(true)
.with_roles(vec!["admin".to_string()])
.with_permissions(vec!["user/list".to_string()]);
let cloned = ctx.clone();
assert_eq!(ctx.user_id, cloned.user_id);
assert_eq!(ctx.is_super, cloned.is_super);
assert_eq!(ctx.roles, cloned.roles);
assert_eq!(ctx.permissions, cloned.permissions);
}
#[test]
fn test_user_context_debug() {
let ctx = UserContext::new(1).with_super(true);
let debug_str = format!("{:?}", ctx);
assert!(debug_str.contains("UserContext"));
assert!(debug_str.contains("user_id"));
assert!(debug_str.contains("is_super"));
}
#[test]
fn test_user_context_builder_chain() {
let ctx = UserContext::new(1)
.with_super(false)
.with_roles(vec!["editor".to_string()])
.with_permissions(vec!["post/list".to_string(), "post/save".to_string()]);
assert_eq!(ctx.user_id, 1);
assert!(!ctx.is_super);
assert_eq!(ctx.roles, vec!["editor"]);
assert_eq!(ctx.permissions.len(), 2);
assert!(ctx.has_role("editor"));
assert!(ctx.has_permission("post/list"));
}
#[test]
fn test_auth_guard_new() {
let guard = AuthGuard::new();
let _ = format!("{:?}", guard);
}
#[test]
fn test_auth_guard_passes_when_authenticated() {
let guard = AuthGuard::new();
let req = make_request_with_user(1);
assert!(guard.check(&req).is_ok());
}
#[test]
fn test_auth_guard_fails_when_not_authenticated() {
let guard = AuthGuard::new();
let req = make_request();
let result = guard.check(&req);
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.code, ErrorCode::NotLogin);
assert_eq!(err.msg, "not_login");
}
#[test]
fn test_admin_guard_new() {
let _guard = AdminGuard::new();
}
#[test]
fn test_admin_guard_fails_when_not_logged_in() {
let guard = AdminGuard::new();
let req = make_request();
let result = guard.check(&req);
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.code, ErrorCode::NotLogin);
}
#[test]
fn test_admin_guard_fails_when_logged_in_but_no_user_context() {
let guard = AdminGuard::new();
let req = make_request_with_user(1);
let result = guard.check(&req);
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.code, ErrorCode::Forbidden);
}
#[test]
fn test_admin_guard_fails_when_not_super() {
let guard = AdminGuard::new();
let user_ctx = UserContext::new(1).with_super(false);
let req = make_request_with_context(user_ctx);
let result = guard.check(&req);
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.code, ErrorCode::Forbidden);
}
#[test]
fn test_admin_guard_passes_when_super() {
let guard = AdminGuard::new();
let user_ctx = UserContext::new(1).with_super(true);
let req = make_request_with_context(user_ctx);
assert!(guard.check(&req).is_ok());
}
#[test]
fn test_permission_guard_new() {
let guard = PermissionGuard::new("user/list");
assert_eq!(guard.permission, "user/list");
}
#[test]
fn test_permission_guard_fails_when_not_logged_in() {
let guard = PermissionGuard::new("user/list");
let req = make_request();
let result = guard.check(&req);
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.code, ErrorCode::NotLogin);
}
#[test]
fn test_permission_guard_fails_when_no_user_context() {
let guard = PermissionGuard::new("user/list");
let req = make_request_with_user(1);
let result = guard.check(&req);
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.code, ErrorCode::Forbidden);
}
#[test]
fn test_permission_guard_fails_when_no_permission() {
let guard = PermissionGuard::new("user/delete");
let user_ctx = UserContext::new(1).with_permissions(vec!["user/list".to_string()]);
let req = make_request_with_context(user_ctx);
let result = guard.check(&req);
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.code, ErrorCode::Forbidden);
}
#[test]
fn test_permission_guard_passes_when_has_exact_permission() {
let guard = PermissionGuard::new("user/list");
let user_ctx = UserContext::new(1).with_permissions(vec!["user/list".to_string()]);
let req = make_request_with_context(user_ctx);
assert!(guard.check(&req).is_ok());
}
#[test]
fn test_permission_guard_passes_when_has_wildcard_permission() {
let guard = PermissionGuard::new("user/list");
let user_ctx = UserContext::new(1).with_permissions(vec!["user/*".to_string()]);
let req = make_request_with_context(user_ctx);
assert!(guard.check(&req).is_ok());
}
#[test]
fn test_permission_guard_passes_when_super() {
let guard = PermissionGuard::new("user/delete");
let user_ctx = UserContext::new(1).with_super(true);
let req = make_request_with_context(user_ctx);
assert!(guard.check(&req).is_ok());
}
#[test]
fn test_permission_guard_passes_when_super_without_permissions() {
let guard = PermissionGuard::new("system/config");
let user_ctx = UserContext::new(1).with_super(true);
let req = make_request_with_context(user_ctx);
assert!(guard.check(&req).is_ok());
}
#[test]
fn test_role_guard_new() {
let guard = RoleGuard::new("admin");
assert_eq!(guard.role, "admin");
}
#[test]
fn test_role_guard_fails_when_not_logged_in() {
let guard = RoleGuard::new("admin");
let req = make_request();
let result = guard.check(&req);
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.code, ErrorCode::NotLogin);
}
#[test]
fn test_role_guard_fails_when_no_user_context() {
let guard = RoleGuard::new("admin");
let req = make_request_with_user(1);
let result = guard.check(&req);
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.code, ErrorCode::Forbidden);
}
#[test]
fn test_role_guard_fails_when_no_role() {
let guard = RoleGuard::new("admin");
let user_ctx = UserContext::new(1).with_roles(vec!["editor".to_string()]);
let req = make_request_with_context(user_ctx);
let result = guard.check(&req);
assert!(result.is_err());
}
#[test]
fn test_role_guard_passes_when_has_role() {
let guard = RoleGuard::new("admin");
let user_ctx = UserContext::new(1).with_roles(vec!["admin".to_string()]);
let req = make_request_with_context(user_ctx);
assert!(guard.check(&req).is_ok());
}
#[test]
fn test_role_guard_passes_when_super() {
let guard = RoleGuard::new("admin");
let user_ctx = UserContext::new(1).with_super(true);
let req = make_request_with_context(user_ctx);
assert!(guard.check(&req).is_ok());
}
#[test]
fn test_guard_chain_new() {
let chain = GuardChain::new();
assert!(chain.guards.is_empty());
}
#[test]
fn test_guard_chain_default() {
let chain = GuardChain::default();
assert!(chain.guards.is_empty());
}
#[test]
fn test_guard_chain_with_guard() {
let chain = GuardChain::new()
.with_guard(Arc::new(AuthGuard))
.with_guard(Arc::new(AdminGuard));
assert_eq!(chain.guards.len(), 2);
}
#[test]
fn test_guard_chain_from_guards() {
let guards: Vec<Arc<dyn Guard>> = vec![Arc::new(AuthGuard), Arc::new(AdminGuard)];
let chain = GuardChain::from_guards(guards);
assert_eq!(chain.guards.len(), 2);
}
#[test]
fn test_guard_chain_empty_passes() {
let chain = GuardChain::new();
let req = make_request();
assert!(chain.check(&req).is_ok());
}
#[test]
fn test_guard_chain_single_guard_passes() {
let chain = GuardChain::new().with_guard(Arc::new(AuthGuard));
let req = make_request_with_user(1);
assert!(chain.check(&req).is_ok());
}
#[test]
fn test_guard_chain_single_guard_fails() {
let chain = GuardChain::new().with_guard(Arc::new(AuthGuard));
let req = make_request();
assert!(chain.check(&req).is_err());
}
#[test]
fn test_guard_chain_and_semantics_all_pass() {
let chain = GuardChain::new()
.with_guard(Arc::new(AuthGuard))
.with_guard(Arc::new(AdminGuard));
let user_ctx = UserContext::new(1).with_super(true);
let req = make_request_with_context(user_ctx);
assert!(chain.check(&req).is_ok());
}
#[test]
fn test_guard_chain_and_semantics_first_fails() {
let chain = GuardChain::new()
.with_guard(Arc::new(AuthGuard))
.with_guard(Arc::new(AdminGuard));
let req = make_request();
let result = chain.check(&req);
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.code, ErrorCode::NotLogin);
}
#[test]
fn test_guard_chain_and_semantics_second_fails() {
let chain = GuardChain::new()
.with_guard(Arc::new(AuthGuard))
.with_guard(Arc::new(AdminGuard));
let user_ctx = UserContext::new(1).with_super(false);
let req = make_request_with_context(user_ctx);
let result = chain.check(&req);
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.code, ErrorCode::Forbidden);
}
#[test]
fn test_guard_chain_and_semantics_short_circuit() {
struct FailGuard;
impl Guard for FailGuard {
fn check(&self, _req: &Request) -> Result<(), GuardError> {
Err(GuardError::forbidden("fail_guard_called"))
}
}
struct PanicGuard;
impl Guard for PanicGuard {
fn check(&self, _req: &Request) -> Result<(), GuardError> {
panic!("PanicGuard should not be called due to short-circuit");
}
}
let chain = GuardChain::new()
.with_guard(Arc::new(FailGuard))
.with_guard(Arc::new(PanicGuard));
let req = make_request();
let result = chain.check(&req);
assert!(result.is_err());
assert_eq!(result.unwrap_err().msg, "fail_guard_called");
}
#[test]
fn test_guard_chain_order_matters() {
let chain = GuardChain::new()
.with_guard(Arc::new(AuthGuard))
.with_guard(Arc::new(PermissionGuard::new("user/list")));
let req = make_request();
let result = chain.check(&req);
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.code, ErrorCode::NotLogin);
}
#[test]
fn test_guard_chain_multiple_permissions() {
let chain = GuardChain::new()
.with_guard(Arc::new(AuthGuard))
.with_guard(Arc::new(PermissionGuard::new("user/list")))
.with_guard(Arc::new(PermissionGuard::new("user/save")));
let user_ctx = UserContext::new(1)
.with_permissions(vec!["user/list".to_string(), "user/save".to_string()]);
let req = make_request_with_context(user_ctx);
assert!(chain.check(&req).is_ok());
}
#[test]
fn test_guard_chain_mixed_guard_types() {
let chain = GuardChain::new()
.with_guard(Arc::new(AuthGuard))
.with_guard(Arc::new(RoleGuard::new("editor")))
.with_guard(Arc::new(PermissionGuard::new("post/list")));
let user_ctx = UserContext::new(1)
.with_roles(vec!["editor".to_string()])
.with_permissions(vec!["post/list".to_string()]);
let req = make_request_with_context(user_ctx);
assert!(chain.check(&req).is_ok());
}
#[test]
fn test_check_guards_empty() {
let req = make_request();
assert!(check_guards(&req, &[]).is_ok());
}
#[test]
fn test_check_guards_all_pass() {
let guards: Vec<Arc<dyn Guard>> = vec![Arc::new(AuthGuard)];
let req = make_request_with_user(1);
assert!(check_guards(&req, &guards).is_ok());
}
#[test]
fn test_check_guards_fails() {
let guards: Vec<Arc<dyn Guard>> = vec![Arc::new(AuthGuard)];
let req = make_request();
assert!(check_guards(&req, &guards).is_err());
}
#[tokio::test]
async fn test_guard_middleware_passes() {
let app = build_app(Arc::new(AuthGuard));
let req = Request::builder()
.method("GET")
.uri("/protected")
.extension(AuthenticatedUser { user_id: 1 })
.body(Body::empty())
.unwrap();
let resp = app.oneshot(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
#[tokio::test]
async fn test_guard_middleware_fails_not_login() {
let app = build_app(Arc::new(AuthGuard));
let req = Request::builder()
.method("GET")
.uri("/protected")
.body(Body::empty())
.unwrap();
let resp = app.oneshot(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
let body = read_body(resp).await;
let json: serde_json::Value = serde_json::from_str(&body).unwrap();
assert_eq!(json["code"], -1);
assert_eq!(json["msg"], "not_login");
}
#[tokio::test]
async fn test_guard_middleware_fails_forbidden() {
let app = build_app(Arc::new(AdminGuard));
let req = Request::builder()
.method("GET")
.uri("/protected")
.extension(AuthenticatedUser { user_id: 1 })
.extension(UserContext::new(1).with_super(false))
.body(Body::empty())
.unwrap();
let resp = app.oneshot(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
let body = read_body(resp).await;
let json: serde_json::Value = serde_json::from_str(&body).unwrap();
assert_eq!(json["code"], 403);
}
#[tokio::test]
async fn test_guard_middleware_with_chain() {
let chain = GuardChain::new()
.with_guard(Arc::new(AuthGuard))
.with_guard(Arc::new(AdminGuard));
let app = build_app(Arc::new(chain));
let req = Request::builder()
.method("GET")
.uri("/protected")
.extension(AuthenticatedUser { user_id: 1 })
.extension(UserContext::new(1).with_super(true))
.body(Body::empty())
.unwrap();
let resp = app.clone().oneshot(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let req = Request::builder()
.method("GET")
.uri("/protected")
.extension(AuthenticatedUser { user_id: 2 })
.extension(UserContext::new(2).with_super(false))
.body(Body::empty())
.unwrap();
let resp = app.oneshot(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
}
#[tokio::test]
async fn test_guard_middleware_permission_guard() {
let chain = GuardChain::new()
.with_guard(Arc::new(AuthGuard))
.with_guard(Arc::new(PermissionGuard::new("user/list")));
let app = build_app(Arc::new(chain));
let req = Request::builder()
.method("GET")
.uri("/protected")
.extension(AuthenticatedUser { user_id: 1 })
.extension(UserContext::new(1).with_permissions(vec!["user/list".to_string()]))
.body(Body::empty())
.unwrap();
let resp = app.clone().oneshot(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let req = Request::builder()
.method("GET")
.uri("/protected")
.extension(AuthenticatedUser { user_id: 2 })
.extension(UserContext::new(2).with_permissions(vec!["order/list".to_string()]))
.body(Body::empty())
.unwrap();
let resp = app.oneshot(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
}
#[test]
fn test_php_alignment_is_super_bypass() {
let permission_guard = PermissionGuard::new("system/config");
let role_guard = RoleGuard::new("admin");
let admin_guard = AdminGuard::new();
let user_ctx = UserContext::new(1).with_super(true);
let req = make_request_with_context(user_ctx);
assert!(permission_guard.check(&req).is_ok());
assert!(role_guard.check(&req).is_ok());
assert!(admin_guard.check(&req).is_ok());
}
#[test]
fn test_php_alignment_error_codes() {
let err = GuardError::not_login("not_login");
assert_eq!(err.code.as_i32(), -1);
let err = GuardError::user_disabled("您已离职");
assert_eq!(err.code.as_i32(), -3);
let err = GuardError::forbidden("无权限访问");
assert_eq!(err.code.as_i32(), 403);
}
#[test]
fn test_php_alignment_check_login() {
let guard = AuthGuard::new();
let req = make_request();
let result = guard.check(&req);
assert!(matches!(
result,
Err(GuardError {
code: ErrorCode::NotLogin,
..
})
));
let req = make_request_with_user(1);
assert!(guard.check(&req).is_ok());
}
#[test]
fn test_php_alignment_wildcard_permission() {
let ctx = UserContext::new(1).with_permissions(vec!["user/*".to_string()]);
assert!(ctx.has_permission("user/list"));
assert!(ctx.has_permission("user/save"));
assert!(ctx.has_permission("user/delete"));
assert!(!ctx.has_permission("order/list"));
}
#[test]
fn test_php_alignment_multiple_roles() {
let ctx = UserContext::new(1).with_roles(vec!["editor".to_string(), "viewer".to_string()]);
assert!(ctx.has_role("editor"));
assert!(ctx.has_role("viewer"));
assert!(!ctx.has_role("admin"));
}
#[tokio::test]
async fn test_php_alignment_response_format() {
let err = GuardError::user_disabled("您已离职,无权使用本系统!");
let resp = err.into_response();
let body = read_body(resp).await;
let json: serde_json::Value = serde_json::from_str(&body).unwrap();
assert_eq!(json["code"], -3);
assert_eq!(json["msg"], "您已离职,无权使用本系统!");
assert_eq!(json["data"], serde_json::json!({}));
}
}