Skip to main content

sz_rust_core/
guard.rs

1//! Guard 守卫模块 — 鉴权决策(借鉴 NestJS Guard + Spring Security)
2//!
3//! sz-rust 自研模块,PHP 端无直接对应物。PHP 端鉴权分散在各应用的 Controller 基类
4//! (`addons\BaseController::checkLogin` / `app\<app>\controller\Base::checkAuth`),
5//! sz-rust 将鉴权决策抽象为独立的 Guard 层,与 Middleware 分离。
6//!
7//! ## Guard vs Middleware
8//!
9//! | 维度 | Middleware | Guard |
10//! |------|-----------|-------|
11//! | 关注点 | 横切关注点(日志/CORS/追踪/限流) | 鉴权决策(allow/deny) |
12//! | 返回值 | Response(可修改请求/响应) | `Result<(), GuardError>`(二元决策) |
13//! | 执行时机 | 请求全生命周期 | Auth 中间件之后、handler 之前 |
14//! | 组合语义 | 链式(顺序执行) | AND 语义(全部通过) |
15//!
16//! ## PHP 端鉴权调研
17//!
18//! PHP 端 9 个应用各自独立鉴权:
19//! - `szoa` / `szoapc` / `szweb`:JWT + RBAC(`users → user_role → role → role_access → access`)
20//! - `szadmin`:Basic Auth
21//! - 其他应用:Cache token / Session
22//!
23//! PHP szoa RBAC 模型:
24//! - `is_super=1` 绕过所有 RBAC 检查
25//! - 错误码:`-1` not_login / `0` 无权限 / `-3` 用户已禁用
26//! - 权限格式:`controller/action`(如 `user/list`、`user/save`)
27//!
28//! ## 执行顺序
29//!
30//! Guard 在 [`crate::middleware::order::DEFAULT_ORDER`] 的 Auth 中间件之后执行:
31//!
32//! ```text
33//! Trace → Cors → Log → RateLimit → Auth → [Guard] → Handler
34//! ```
35//!
36//! Auth 中间件负责 JWT 校验并注入 [`AuthenticatedUser`] 到 request extensions,
37//! Guard 基于 `AuthenticatedUser` 和 [`UserContext`] 进行鉴权决策。
38//!
39//! ## 用法
40//!
41//! ### 单个 Guard
42//!
43//! ```ignore
44//! use sz_rust_core::guard::{AuthGuard, Guard, guard_middleware};
45//! use std::sync::Arc;
46//! use axum::Router;
47//! use axum::middleware::from_fn_with_state;
48//!
49//! let app: Router = Router::new()
50//!     .route("/profile", axum::routing::get(handler))
51//!     .layer(from_fn_with_state(
52//!         Arc::new(AuthGuard) as Arc<dyn Guard>,
53//!         guard_middleware,
54//!     ));
55//! ```
56//!
57//! ### Guard 链(AND 语义)
58//!
59//! ```ignore
60//! use sz_rust_core::guard::{AuthGuard, AdminGuard, GuardChain, Guard, guard_middleware};
61//! use std::sync::Arc;
62//! use axum::Router;
63//! use axum::middleware::from_fn_with_state;
64//!
65//! let chain = GuardChain::new()
66//!     .with_guard(Arc::new(AuthGuard))
67//!     .with_guard(Arc::new(AdminGuard));
68//!
69//! let app: Router = Router::new()
70//!     .route("/admin", axum::routing::get(handler))
71//!     .layer(from_fn_with_state(
72//!         Arc::new(chain) as Arc<dyn Guard>,
73//!         guard_middleware,
74//!     ));
75//! ```
76//!
77//! ### 权限 Guard
78//!
79//! ```ignore
80//! use sz_rust_core::guard::{AuthGuard, PermissionGuard, GuardChain, Guard, guard_middleware};
81//! use std::sync::Arc;
82//!
83//! let chain = GuardChain::new()
84//!     .with_guard(Arc::new(AuthGuard))
85//!     .with_guard(Arc::new(PermissionGuard::new("user/list")));
86//! ```
87
88use axum::extract::Request;
89use axum::middleware::Next;
90use axum::response::{IntoResponse, Response};
91use std::sync::Arc;
92
93use crate::error::{BaseException, ErrorCode};
94use crate::middleware::auth::{base_exception_to_response, AuthenticatedUser};
95
96// ============================================================================
97// GuardError — Guard 错误类型
98// ============================================================================
99
100/// Guard 错误类型 — 对齐 PHP 错误码
101///
102/// Guard 拒绝请求时返回此错误,由 [`IntoResponse`] 实现转换为 HTTP 响应。
103///
104/// ## 错误码对齐
105///
106/// | GuardError | ErrorCode | PHP 对应 | HTTP 状态码 |
107/// |-----------|-----------|---------|------------|
108/// | `not_login` | `NotLogin(-1)` | `not_login` | 401 |
109/// | `forbidden` | `Forbidden(403)` | 无权限 | 403 |
110/// | `user_disabled` | `UserDisabled(-3)` | `您已离职` | 403 |
111///
112/// ## 响应格式
113///
114/// 对齐 PHP `renderJson`:
115/// ```json
116/// { "code": <code>, "msg": "<msg>", "data": {} }
117/// ```
118#[derive(Debug, Clone)]
119pub struct GuardError {
120    /// 错误码(对齐 PHP BaseException 的 code 字段)
121    pub code: ErrorCode,
122    /// 错误消息(对齐 PHP BaseException 的 msg 字段)
123    pub msg: String,
124}
125
126impl GuardError {
127    /// 创建 GuardError
128    pub fn new(code: ErrorCode, msg: impl Into<String>) -> Self {
129        Self {
130            code,
131            msg: msg.into(),
132        }
133    }
134
135    /// 未登录快捷构造(对齐 PHP `code=-1, msg='not_login'`)
136    pub fn not_login(msg: impl Into<String>) -> Self {
137        Self::new(ErrorCode::NotLogin, msg)
138    }
139
140    /// 无权限快捷构造(HTTP 403)
141    pub fn forbidden(msg: impl Into<String>) -> Self {
142        Self::new(ErrorCode::Forbidden, msg)
143    }
144
145    /// 用户已禁用快捷构造(对齐 PHP `code=-3, msg='您已离职'`)
146    pub fn user_disabled(msg: impl Into<String>) -> Self {
147        Self::new(ErrorCode::UserDisabled, msg)
148    }
149}
150
151impl IntoResponse for GuardError {
152    /// 转换为 HTTP 响应
153    ///
154    /// 复用 [`base_exception_to_response`],确保响应格式与 PHP `renderJson` 对齐:
155    /// - HTTP 状态码由 [`ErrorCode::http_status`] 决定
156    /// - 响应体:`{"code": <code>, "msg": "<msg>", "data": {}}`
157    fn into_response(self) -> Response {
158        let exc = BaseException::new(self.code, self.msg);
159        base_exception_to_response(exc)
160    }
161}
162
163impl From<GuardError> for BaseException {
164    fn from(err: GuardError) -> Self {
165        BaseException::new(err.code, err.msg)
166    }
167}
168
169impl std::fmt::Display for GuardError {
170    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
171        write!(f, "[{}] {}", self.code.as_i32(), self.msg)
172    }
173}
174
175impl std::error::Error for GuardError {}
176
177// ============================================================================
178// UserContext — 用户上下文(扩展 AuthenticatedUser,提供权限信息)
179// ============================================================================
180
181/// 用户上下文 — 扩展 [`AuthenticatedUser`],提供权限信息
182///
183/// 由业务层在登录时构建并注入到 request extensions(在 Auth 中间件之后、Guard 之前)。
184/// Guard 通过 `UserContext` 进行权限判断。
185///
186/// ## PHP 对齐
187///
188/// 对齐 PHP szoa RBAC 模型:
189/// - `is_super=true` 绕过所有 RBAC(对齐 PHP `is_super=1`)
190/// - `roles` 对齐 PHP `role` 表(通过 `user_role` 关联)
191/// - `permissions` 对齐 PHP `access` 表(格式:`controller/action`,如 `user/list`)
192///
193/// ## 注入时机
194///
195/// ```text
196/// Auth 中间件(注入 AuthenticatedUser)
197///     ↓
198/// 业务中间件(查 DB 获取 roles/permissions,注入 UserContext)
199///     ↓
200/// Guard(基于 UserContext 进行鉴权决策)
201///     ↓
202/// Handler
203/// ```
204///
205/// 如果业务代码未注入 `UserContext`,[`AdminGuard`] / [`PermissionGuard`] / [`RoleGuard`]
206/// 将返回 `Forbidden`(无权限访问)。
207pub struct UserContext {
208    /// 用户 ID(对齐 `AuthenticatedUser::user_id`)
209    pub user_id: i64,
210    /// 是否超级管理员(对齐 PHP `is_super=1`,绕过所有 RBAC)
211    pub is_super: bool,
212    /// 角色列表(对齐 PHP `role` 表)
213    pub roles: Vec<String>,
214    /// 权限列表(对齐 PHP `access` 表,格式:`controller/action`)
215    pub permissions: Vec<String>,
216}
217
218impl UserContext {
219    /// 创建 UserContext(默认非超级管理员,无角色,无权限)
220    pub fn new(user_id: i64) -> Self {
221        Self {
222            user_id,
223            is_super: false,
224            roles: Vec::new(),
225            permissions: Vec::new(),
226        }
227    }
228
229    /// 设置是否超级管理员
230    pub fn with_super(mut self, is_super: bool) -> Self {
231        self.is_super = is_super;
232        self
233    }
234
235    /// 设置角色列表
236    pub fn with_roles(mut self, roles: Vec<String>) -> Self {
237        self.roles = roles;
238        self
239    }
240
241    /// 设置权限列表
242    pub fn with_permissions(mut self, permissions: Vec<String>) -> Self {
243        self.permissions = permissions;
244        self
245    }
246
247    /// 检查是否具有指定角色
248    ///
249    /// 对齐 PHP `User::hasRole()` 角色 检查
250    pub fn has_role(&self, role: &str) -> bool {
251        self.roles.iter().any(|r| r == role)
252    }
253
254    /// 检查是否具有指定权限
255    ///
256    /// 对齐 PHP `AuthService::check()` 权限检查:
257    /// - 精确匹配:`user/list` == `user/list` → true
258    /// - 通配符匹配:`user/*` 匹配 `user/list` → true(对齐 PHP 通配符权限)
259    ///
260    /// 注意:`is_super=true` 的绕过逻辑由 Guard 负责,本方法仅检查权限列表。
261    pub fn has_permission(&self, permission: &str) -> bool {
262        // 1. 精确匹配
263        if self.permissions.iter().any(|p| p == permission) {
264            return true;
265        }
266        // 2. 通配符匹配(对齐 PHP `user/*` 匹配 `user/list`)
267        for perm in &self.permissions {
268            if perm.ends_with("/*") {
269                let prefix = &perm[..perm.len() - 1]; // 去掉 `*`,保留 `user/`
270                if permission.starts_with(prefix) {
271                    return true;
272                }
273            }
274        }
275        false
276    }
277}
278
279impl std::fmt::Debug for UserContext {
280    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
281        f.debug_struct("UserContext")
282            .field("user_id", &self.user_id)
283            .field("is_super", &self.is_super)
284            .field("roles", &self.roles)
285            .field("permissions", &self.permissions)
286            .finish()
287    }
288}
289
290impl Clone for UserContext {
291    fn clone(&self) -> Self {
292        Self {
293            user_id: self.user_id,
294            is_super: self.is_super,
295            roles: self.roles.clone(),
296            permissions: self.permissions.clone(),
297        }
298    }
299}
300
301impl Default for UserContext {
302    fn default() -> Self {
303        Self::new(0)
304    }
305}
306
307impl From<AuthenticatedUser> for UserContext {
308    /// 从 [`AuthenticatedUser`] 创建 [`UserContext`]
309    ///
310    /// 默认非超级管理员,无角色,无权限(业务层需后续调用 `with_super` / `with_roles`
311    /// / `with_permissions` 补充权限信息)。
312    fn from(user: AuthenticatedUser) -> Self {
313        Self::new(user.user_id)
314    }
315}
316
317// ============================================================================
318// Guard trait — NestJS 风格的守卫接口
319// ============================================================================
320
321/// Guard trait — NestJS 风格的守卫接口
322///
323/// 守卫在 Auth 中间件之后执行,用于鉴权决策(allow/deny)。
324///
325/// ## 同步 trait 设计
326///
327/// `check` 为同步方法,因为:
328/// - 决策基于 request extensions(已在 Auth 中间件中预加载)
329/// - 不需要 I/O(DB 查询由业务层中间件完成,结果存入 [`UserContext`])
330/// - 对齐 `sz-orm-auth` 的 `Authorizer` trait 设计
331///
332/// 如需异步 DB 查询,应在 Guard 之前的中间件中预加载权限信息到 `UserContext`。
333///
334/// ## PHP 对齐
335///
336/// PHP 端无直接对应物。PHP 鉴权分散在各应用的 Controller 基类:
337/// - `addons\BaseController::checkLogin`:登录校验(对齐 [`AuthGuard`])
338/// - `app\szoa\controller\Base::checkAuth`:权限校验(对齐 [`PermissionGuard`])
339///
340/// sz-rust 将鉴权抽象为独立 Guard 层,便于复用和组合。
341pub trait Guard: Send + Sync {
342    /// 检查请求是否通过守卫
343    ///
344    /// 返回 `Ok(())` 表示通过,`Err(GuardError)` 表示拒绝。
345    ///
346    /// ## 实现约定
347    ///
348    /// - 应先检查 [`AuthenticatedUser`] 是否存在(登录校验)
349    /// - 再检查 [`UserContext`] 中的权限信息
350    /// - `is_super=true` 应绕过所有权限检查(对齐 PHP `is_super=1`)
351    fn check(&self, req: &Request) -> Result<(), GuardError>;
352}
353
354// ============================================================================
355// AuthGuard — 登录校验
356// ============================================================================
357
358/// AuthGuard — 检查用户已登录([`AuthenticatedUser`] 存在)
359///
360/// 对齐 PHP `addons\BaseController::checkLogin`:
361/// ```php
362/// private function checkLogin(): void {
363///     if (!empty($this->user) && $this->user['is_login'] == 1) {
364///         return;
365///     }
366///     throw new BaseException(['code' => -1, 'msg' => 'not_login']);
367/// }
368/// ```
369///
370/// Rust 端 `AuthenticatedUser` 存在于 extensions 即表示已登录(`is_login=1`)。
371#[derive(Debug, Default)]
372pub struct AuthGuard;
373
374impl AuthGuard {
375    /// 创建 AuthGuard
376    pub fn new() -> Self {
377        Self
378    }
379}
380
381impl Guard for AuthGuard {
382    fn check(&self, req: &Request) -> Result<(), GuardError> {
383        if req.extensions().get::<AuthenticatedUser>().is_some() {
384            Ok(())
385        } else {
386            Err(GuardError::not_login("not_login"))
387        }
388    }
389}
390
391// ============================================================================
392// AdminGuard — 超级管理员校验
393// ============================================================================
394
395/// AdminGuard — 检查用户是超级管理员(`is_super=true`)
396///
397/// 对齐 PHP szoa `is_super=1` 绕过所有 RBAC:
398/// ```php
399/// if ($user['is_super'] == 1) {
400///     return true; // 绕过所有权限检查
401/// }
402/// ```
403///
404/// 需要 [`UserContext`] extension(由业务层注入)。
405/// 如果 `UserContext` 不存在,返回 `Forbidden`(无权限访问)。
406#[derive(Debug, Default)]
407pub struct AdminGuard;
408
409impl AdminGuard {
410    /// 创建 AdminGuard
411    pub fn new() -> Self {
412        Self
413    }
414}
415
416impl Guard for AdminGuard {
417    fn check(&self, req: &Request) -> Result<(), GuardError> {
418        // 1. 先检查已登录(对齐 PHP checkLogin)
419        let _user = req
420            .extensions()
421            .get::<AuthenticatedUser>()
422            .ok_or_else(|| GuardError::not_login("not_login"))?;
423
424        // 2. 检查 UserContext 中的 is_super
425        let user_ctx = req
426            .extensions()
427            .get::<UserContext>()
428            .ok_or_else(|| GuardError::forbidden("无权限访问"))?;
429
430        if user_ctx.is_super {
431            Ok(())
432        } else {
433            Err(GuardError::forbidden("无权限访问"))
434        }
435    }
436}
437
438// ============================================================================
439// PermissionGuard — 权限校验
440// ============================================================================
441
442/// PermissionGuard — 检查用户具有特定权限
443///
444/// 对齐 PHP szoa RBAC `AuthService::check()`:
445/// ```php
446/// public function check($action): bool {
447///     if ($user['is_super'] == 1) {
448///         return true; // 超级管理员绕过
449///     }
450///     return in_array($action, $user['permissions']);
451/// }
452/// ```
453///
454/// ## 权限格式
455///
456/// 对齐 PHP `access` 表,格式:`controller/action`(如 `user/list`、`user/save`)。
457///
458/// 支持通配符:`user/*` 匹配 `user/list`、`user/save` 等。
459///
460/// ## is_super 绕过
461///
462/// `is_super=true` 绕过所有权限检查(对齐 PHP `is_super=1`)。
463#[derive(Debug)]
464pub struct PermissionGuard {
465    /// 所需权限(格式:`controller/action`)
466    pub permission: String,
467}
468
469impl PermissionGuard {
470    /// 创建 PermissionGuard
471    pub fn new(permission: impl Into<String>) -> Self {
472        Self {
473            permission: permission.into(),
474        }
475    }
476}
477
478impl Guard for PermissionGuard {
479    fn check(&self, req: &Request) -> Result<(), GuardError> {
480        // 1. 先检查已登录
481        let _user = req
482            .extensions()
483            .get::<AuthenticatedUser>()
484            .ok_or_else(|| GuardError::not_login("not_login"))?;
485
486        // 2. 检查 UserContext
487        let user_ctx = req
488            .extensions()
489            .get::<UserContext>()
490            .ok_or_else(|| GuardError::forbidden("无权限访问"))?;
491
492        // 3. is_super 绕过所有 RBAC(对齐 PHP is_super=1)
493        if user_ctx.is_super {
494            return Ok(());
495        }
496
497        // 4. 检查权限
498        if user_ctx.has_permission(&self.permission) {
499            Ok(())
500        } else {
501            Err(GuardError::forbidden("无权限访问"))
502        }
503    }
504}
505
506// ============================================================================
507// RoleGuard — 角色校验
508// ============================================================================
509
510/// RoleGuard — 检查用户具有指定角色
511///
512/// 对齐 PHP szoa `user_role` 关联表的角色检查。
513///
514/// ## is_super 绕过
515///
516/// `is_super=true` 绕过所有角色检查(对齐 PHP `is_super=1`)。
517#[derive(Debug)]
518pub struct RoleGuard {
519    /// 所需角色名
520    pub role: String,
521}
522
523impl RoleGuard {
524    /// 创建 RoleGuard
525    pub fn new(role: impl Into<String>) -> Self {
526        Self { role: role.into() }
527    }
528}
529
530impl Guard for RoleGuard {
531    fn check(&self, req: &Request) -> Result<(), GuardError> {
532        // 1. 先检查已登录
533        let _user = req
534            .extensions()
535            .get::<AuthenticatedUser>()
536            .ok_or_else(|| GuardError::not_login("not_login"))?;
537
538        // 2. 检查 UserContext
539        let user_ctx = req
540            .extensions()
541            .get::<UserContext>()
542            .ok_or_else(|| GuardError::forbidden("无权限访问"))?;
543
544        // 3. is_super 绕过(对齐 PHP is_super=1)
545        if user_ctx.is_super {
546            return Ok(());
547        }
548
549        // 4. 检查角色
550        if user_ctx.has_role(&self.role) {
551            Ok(())
552        } else {
553            Err(GuardError::forbidden("无权限访问"))
554        }
555    }
556}
557
558// ============================================================================
559// GuardChain — 守卫链(AND 语义组合)
560// ============================================================================
561
562/// GuardChain — 守卫链(AND 语义组合)
563///
564/// 所有 Guard 必须通过,任一失败则整个链失败。
565/// 对齐 NestJS `UseGuards(...)` 多个 Guard 的 AND 语义。
566///
567/// ## 执行顺序
568///
569/// 按添加顺序执行(先添加先执行)。建议顺序:
570/// 1. [`AuthGuard`](先检查登录)
571/// 2. [`AdminGuard`] / [`PermissionGuard`] / [`RoleGuard`](再检查权限)
572///
573/// ## 用法
574///
575/// ```ignore
576/// use sz_rust_core::guard::{AuthGuard, AdminGuard, GuardChain};
577/// use std::sync::Arc;
578///
579/// let chain = GuardChain::new()
580///     .with_guard(Arc::new(AuthGuard))
581///     .with_guard(Arc::new(AdminGuard));
582/// ```
583pub struct GuardChain {
584    /// 守卫列表(按添加顺序执行)
585    pub guards: Vec<Arc<dyn Guard>>,
586}
587
588impl GuardChain {
589    /// 创建空的 GuardChain
590    pub fn new() -> Self {
591        Self { guards: Vec::new() }
592    }
593
594    /// 添加 Guard(builder 风格)
595    pub fn with_guard(mut self, guard: Arc<dyn Guard>) -> Self {
596        self.guards.push(guard);
597        self
598    }
599
600    /// 从 Guard 列表创建 GuardChain
601    pub fn from_guards(guards: Vec<Arc<dyn Guard>>) -> Self {
602        Self { guards }
603    }
604}
605
606impl Default for GuardChain {
607    fn default() -> Self {
608        Self::new()
609    }
610}
611
612impl Guard for GuardChain {
613    fn check(&self, req: &Request) -> Result<(), GuardError> {
614        // AND 语义:所有 guard 必须通过
615        // 顺序:按添加顺序执行(先 AuthGuard,再权限 Guard)
616        for guard in &self.guards {
617            guard.check(req)?;
618        }
619        Ok(())
620    }
621}
622
623// ============================================================================
624// guard_middleware — axum 中间件集成
625// ============================================================================
626
627/// Guard 中间件 — 将 Guard 集成到 axum 中间件链
628///
629/// 在 Auth 中间件之后执行,检查 Guard。Guard 通过则调用下游 handler,
630/// Guard 拒绝则返回错误响应(对齐 PHP `renderJson` 格式)。
631///
632/// ## 用法
633///
634/// ```ignore
635/// use sz_rust_core::guard::{AuthGuard, Guard, guard_middleware};
636/// use std::sync::Arc;
637/// use axum::Router;
638/// use axum::middleware::from_fn_with_state;
639///
640/// let app: Router = Router::new()
641///     .route("/profile", axum::routing::get(handler))
642///     .layer(from_fn_with_state(
643///         Arc::new(AuthGuard) as Arc<dyn Guard>,
644///         guard_middleware,
645///     ));
646/// ```
647pub async fn guard_middleware(
648    axum::extract::State(guard): axum::extract::State<Arc<dyn Guard>>,
649    req: Request,
650    next: Next,
651) -> Response {
652    match guard.check(&req) {
653        Ok(()) => next.run(req).await,
654        Err(err) => err.into_response(),
655    }
656}
657
658/// 执行守卫检查(无中间件,纯函数)
659///
660/// 按 `guards` 顺序执行 AND 语义检查,任一失败则返回错误。
661///
662/// ## 用法
663///
664/// ```ignore
665/// use sz_rust_core::guard::{AuthGuard, Guard, check_guards};
666/// use std::sync::Arc;
667///
668/// let guards: Vec<Arc<dyn Guard>> = vec![Arc::new(AuthGuard)];
669/// let result = check_guards(&req, &guards);
670/// ```
671pub fn check_guards(req: &Request, guards: &[Arc<dyn Guard>]) -> Result<(), GuardError> {
672    for guard in guards {
673        guard.check(req)?;
674    }
675    Ok(())
676}
677
678// ============================================================================
679// 单元测试
680// ============================================================================
681
682#[cfg(test)]
683mod tests {
684    use super::*;
685    use axum::body::Body;
686    use axum::http::StatusCode;
687    use axum::Router;
688    use http_body_util::BodyExt;
689    use tower::ServiceExt;
690
691    // ====================================================================
692    // 辅助函数
693    // ====================================================================
694
695    /// 构建无 extensions 的 Request
696    fn make_request() -> Request {
697        Request::builder()
698            .method("GET")
699            .uri("/test")
700            .body(Body::empty())
701            .unwrap()
702    }
703
704    /// 构建带 AuthenticatedUser 的 Request
705    fn make_request_with_user(user_id: i64) -> Request {
706        let mut req = make_request();
707        req.extensions_mut().insert(AuthenticatedUser { user_id });
708        req
709    }
710
711    /// 构建带 AuthenticatedUser + UserContext 的 Request
712    fn make_request_with_context(user_ctx: UserContext) -> Request {
713        let mut req = make_request_with_user(user_ctx.user_id);
714        req.extensions_mut().insert(user_ctx);
715        req
716    }
717
718    /// 读取响应体为字符串
719    async fn read_body(resp: Response) -> String {
720        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
721        String::from_utf8(bytes.to_vec()).unwrap()
722    }
723
724    /// 构建测试 Router(带 Guard)
725    fn build_app(guard: Arc<dyn Guard>) -> Router {
726        Router::new()
727            .route(
728                "/protected",
729                axum::routing::get(|| async { axum::http::StatusCode::OK }),
730            )
731            .layer(axum::middleware::from_fn_with_state(
732                guard,
733                guard_middleware,
734            ))
735    }
736
737    // ====================================================================
738    // GuardError 单元测试
739    // ====================================================================
740
741    #[test]
742    fn test_guard_error_new() {
743        let err = GuardError::new(ErrorCode::Forbidden, "无权限");
744        assert_eq!(err.code, ErrorCode::Forbidden);
745        assert_eq!(err.msg, "无权限");
746    }
747
748    #[test]
749    fn test_guard_error_not_login() {
750        let err = GuardError::not_login("not_login");
751        assert_eq!(err.code, ErrorCode::NotLogin);
752        assert_eq!(err.msg, "not_login");
753        // 对齐 PHP code=-1
754        assert_eq!(err.code.as_i32(), -1);
755    }
756
757    #[test]
758    fn test_guard_error_forbidden() {
759        let err = GuardError::forbidden("无权限访问");
760        assert_eq!(err.code, ErrorCode::Forbidden);
761        assert_eq!(err.msg, "无权限访问");
762        assert_eq!(err.code.as_i32(), 403);
763    }
764
765    #[test]
766    fn test_guard_error_user_disabled() {
767        let err = GuardError::user_disabled("您已离职");
768        assert_eq!(err.code, ErrorCode::UserDisabled);
769        assert_eq!(err.msg, "您已离职");
770        // 对齐 PHP code=-3
771        assert_eq!(err.code.as_i32(), -3);
772    }
773
774    #[test]
775    fn test_guard_error_display() {
776        let err = GuardError::not_login("not_login");
777        assert_eq!(format!("{}", err), "[-1] not_login");
778    }
779
780    #[test]
781    fn test_guard_error_clone() {
782        let err = GuardError::forbidden("无权限");
783        let cloned = err.clone();
784        assert_eq!(err.code, cloned.code);
785        assert_eq!(err.msg, cloned.msg);
786    }
787
788    #[test]
789    fn test_guard_error_into_response_not_login() {
790        let err = GuardError::not_login("not_login");
791        let resp = err.into_response();
792        // NotLogin → HTTP 401
793        assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
794    }
795
796    #[test]
797    fn test_guard_error_into_response_forbidden() {
798        let err = GuardError::forbidden("无权限访问");
799        let resp = err.into_response();
800        // Forbidden → HTTP 403
801        assert_eq!(resp.status(), StatusCode::FORBIDDEN);
802    }
803
804    #[test]
805    fn test_guard_error_into_response_user_disabled() {
806        let err = GuardError::user_disabled("您已离职");
807        let resp = err.into_response();
808        // UserDisabled → HTTP 403
809        assert_eq!(resp.status(), StatusCode::FORBIDDEN);
810    }
811
812    #[test]
813    fn test_guard_error_into_base_exception() {
814        let err = GuardError::not_login("not_login");
815        let exc: BaseException = err.into();
816        assert_eq!(exc.code, -1);
817        assert_eq!(exc.msg, "not_login");
818    }
819
820    #[tokio::test]
821    async fn test_guard_error_response_body_format() {
822        // 对齐 PHP renderJson: {"code": <code>, "msg": "<msg>", "data": {}}
823        let err = GuardError::not_login("not_login");
824        let resp = err.into_response();
825        let body = read_body(resp).await;
826        let json: serde_json::Value = serde_json::from_str(&body).unwrap();
827        assert_eq!(json["code"], -1);
828        assert_eq!(json["msg"], "not_login");
829        assert_eq!(json["data"], serde_json::json!({}));
830    }
831
832    // ====================================================================
833    // UserContext 单元测试
834    // ====================================================================
835
836    #[test]
837    fn test_user_context_new() {
838        let ctx = UserContext::new(100);
839        assert_eq!(ctx.user_id, 100);
840        assert!(!ctx.is_super);
841        assert!(ctx.roles.is_empty());
842        assert!(ctx.permissions.is_empty());
843    }
844
845    #[test]
846    fn test_user_context_with_super() {
847        let ctx = UserContext::new(1).with_super(true);
848        assert!(ctx.is_super);
849    }
850
851    #[test]
852    fn test_user_context_with_roles() {
853        let ctx = UserContext::new(1).with_roles(vec!["admin".to_string(), "editor".to_string()]);
854        assert_eq!(ctx.roles, vec!["admin", "editor"]);
855    }
856
857    #[test]
858    fn test_user_context_with_permissions() {
859        let ctx = UserContext::new(1)
860            .with_permissions(vec!["user/list".to_string(), "user/save".to_string()]);
861        assert_eq!(ctx.permissions, vec!["user/list", "user/save"]);
862    }
863
864    #[test]
865    fn test_user_context_has_role() {
866        let ctx = UserContext::new(1).with_roles(vec!["admin".to_string(), "editor".to_string()]);
867        assert!(ctx.has_role("admin"));
868        assert!(ctx.has_role("editor"));
869        assert!(!ctx.has_role("guest"));
870    }
871
872    #[test]
873    fn test_user_context_has_role_empty() {
874        let ctx = UserContext::new(1);
875        assert!(!ctx.has_role("admin"));
876    }
877
878    #[test]
879    fn test_user_context_has_permission_exact() {
880        let ctx = UserContext::new(1).with_permissions(vec!["user/list".to_string()]);
881        assert!(ctx.has_permission("user/list"));
882        assert!(!ctx.has_permission("user/save"));
883    }
884
885    #[test]
886    fn test_user_context_has_permission_wildcard() {
887        // 对齐 PHP 通配符权限:user/* 匹配 user/list, user/save 等
888        let ctx = UserContext::new(1).with_permissions(vec!["user/*".to_string()]);
889        assert!(ctx.has_permission("user/list"));
890        assert!(ctx.has_permission("user/save"));
891        assert!(ctx.has_permission("user/delete"));
892        // 不匹配其他 controller
893        assert!(!ctx.has_permission("order/list"));
894    }
895
896    #[test]
897    fn test_user_context_has_permission_empty() {
898        let ctx = UserContext::new(1);
899        assert!(!ctx.has_permission("user/list"));
900    }
901
902    #[test]
903    fn test_user_context_has_permission_multiple() {
904        let ctx = UserContext::new(1).with_permissions(vec![
905            "user/list".to_string(),
906            "order/*".to_string(),
907            "system/config".to_string(),
908        ]);
909        // 精确匹配
910        assert!(ctx.has_permission("user/list"));
911        assert!(ctx.has_permission("system/config"));
912        // 通配符匹配
913        assert!(ctx.has_permission("order/list"));
914        assert!(ctx.has_permission("order/save"));
915        // 不匹配
916        assert!(!ctx.has_permission("user/save"));
917        assert!(!ctx.has_permission("product/list"));
918    }
919
920    #[test]
921    fn test_user_context_from_authenticated_user() {
922        let user = AuthenticatedUser { user_id: 42 };
923        let ctx = UserContext::from(user);
924        assert_eq!(ctx.user_id, 42);
925        assert!(!ctx.is_super);
926        assert!(ctx.roles.is_empty());
927        assert!(ctx.permissions.is_empty());
928    }
929
930    #[test]
931    fn test_user_context_default() {
932        let ctx = UserContext::default();
933        assert_eq!(ctx.user_id, 0);
934        assert!(!ctx.is_super);
935    }
936
937    #[test]
938    fn test_user_context_clone() {
939        let ctx = UserContext::new(1)
940            .with_super(true)
941            .with_roles(vec!["admin".to_string()])
942            .with_permissions(vec!["user/list".to_string()]);
943        let cloned = ctx.clone();
944        assert_eq!(ctx.user_id, cloned.user_id);
945        assert_eq!(ctx.is_super, cloned.is_super);
946        assert_eq!(ctx.roles, cloned.roles);
947        assert_eq!(ctx.permissions, cloned.permissions);
948    }
949
950    #[test]
951    fn test_user_context_debug() {
952        let ctx = UserContext::new(1).with_super(true);
953        let debug_str = format!("{:?}", ctx);
954        assert!(debug_str.contains("UserContext"));
955        assert!(debug_str.contains("user_id"));
956        assert!(debug_str.contains("is_super"));
957    }
958
959    #[test]
960    fn test_user_context_builder_chain() {
961        let ctx = UserContext::new(1)
962            .with_super(false)
963            .with_roles(vec!["editor".to_string()])
964            .with_permissions(vec!["post/list".to_string(), "post/save".to_string()]);
965        assert_eq!(ctx.user_id, 1);
966        assert!(!ctx.is_super);
967        assert_eq!(ctx.roles, vec!["editor"]);
968        assert_eq!(ctx.permissions.len(), 2);
969        assert!(ctx.has_role("editor"));
970        assert!(ctx.has_permission("post/list"));
971    }
972
973    // ====================================================================
974    // AuthGuard 单元测试
975    // ====================================================================
976
977    #[test]
978    fn test_auth_guard_new() {
979        let guard = AuthGuard::new();
980        // 确保 new() 可调用
981        let _ = format!("{:?}", guard);
982    }
983
984    #[test]
985    fn test_auth_guard_passes_when_authenticated() {
986        let guard = AuthGuard::new();
987        let req = make_request_with_user(1);
988        assert!(guard.check(&req).is_ok());
989    }
990
991    #[test]
992    fn test_auth_guard_fails_when_not_authenticated() {
993        let guard = AuthGuard::new();
994        let req = make_request();
995        let result = guard.check(&req);
996        assert!(result.is_err());
997        let err = result.unwrap_err();
998        assert_eq!(err.code, ErrorCode::NotLogin);
999        assert_eq!(err.msg, "not_login");
1000    }
1001
1002    // ====================================================================
1003    // AdminGuard 单元测试
1004    // ====================================================================
1005
1006    #[test]
1007    fn test_admin_guard_new() {
1008        let _guard = AdminGuard::new();
1009    }
1010
1011    #[test]
1012    fn test_admin_guard_fails_when_not_logged_in() {
1013        let guard = AdminGuard::new();
1014        let req = make_request();
1015        let result = guard.check(&req);
1016        assert!(result.is_err());
1017        let err = result.unwrap_err();
1018        // 未登录应返回 NotLogin(对齐 PHP checkLogin)
1019        assert_eq!(err.code, ErrorCode::NotLogin);
1020    }
1021
1022    #[test]
1023    fn test_admin_guard_fails_when_logged_in_but_no_user_context() {
1024        let guard = AdminGuard::new();
1025        let req = make_request_with_user(1);
1026        let result = guard.check(&req);
1027        assert!(result.is_err());
1028        let err = result.unwrap_err();
1029        // 已登录但无 UserContext → Forbidden
1030        assert_eq!(err.code, ErrorCode::Forbidden);
1031    }
1032
1033    #[test]
1034    fn test_admin_guard_fails_when_not_super() {
1035        let guard = AdminGuard::new();
1036        let user_ctx = UserContext::new(1).with_super(false);
1037        let req = make_request_with_context(user_ctx);
1038        let result = guard.check(&req);
1039        assert!(result.is_err());
1040        let err = result.unwrap_err();
1041        assert_eq!(err.code, ErrorCode::Forbidden);
1042    }
1043
1044    #[test]
1045    fn test_admin_guard_passes_when_super() {
1046        let guard = AdminGuard::new();
1047        let user_ctx = UserContext::new(1).with_super(true);
1048        let req = make_request_with_context(user_ctx);
1049        assert!(guard.check(&req).is_ok());
1050    }
1051
1052    // ====================================================================
1053    // PermissionGuard 单元测试
1054    // ====================================================================
1055
1056    #[test]
1057    fn test_permission_guard_new() {
1058        let guard = PermissionGuard::new("user/list");
1059        assert_eq!(guard.permission, "user/list");
1060    }
1061
1062    #[test]
1063    fn test_permission_guard_fails_when_not_logged_in() {
1064        let guard = PermissionGuard::new("user/list");
1065        let req = make_request();
1066        let result = guard.check(&req);
1067        assert!(result.is_err());
1068        let err = result.unwrap_err();
1069        assert_eq!(err.code, ErrorCode::NotLogin);
1070    }
1071
1072    #[test]
1073    fn test_permission_guard_fails_when_no_user_context() {
1074        let guard = PermissionGuard::new("user/list");
1075        let req = make_request_with_user(1);
1076        let result = guard.check(&req);
1077        assert!(result.is_err());
1078        let err = result.unwrap_err();
1079        assert_eq!(err.code, ErrorCode::Forbidden);
1080    }
1081
1082    #[test]
1083    fn test_permission_guard_fails_when_no_permission() {
1084        let guard = PermissionGuard::new("user/delete");
1085        let user_ctx = UserContext::new(1).with_permissions(vec!["user/list".to_string()]);
1086        let req = make_request_with_context(user_ctx);
1087        let result = guard.check(&req);
1088        assert!(result.is_err());
1089        let err = result.unwrap_err();
1090        assert_eq!(err.code, ErrorCode::Forbidden);
1091    }
1092
1093    #[test]
1094    fn test_permission_guard_passes_when_has_exact_permission() {
1095        let guard = PermissionGuard::new("user/list");
1096        let user_ctx = UserContext::new(1).with_permissions(vec!["user/list".to_string()]);
1097        let req = make_request_with_context(user_ctx);
1098        assert!(guard.check(&req).is_ok());
1099    }
1100
1101    #[test]
1102    fn test_permission_guard_passes_when_has_wildcard_permission() {
1103        let guard = PermissionGuard::new("user/list");
1104        let user_ctx = UserContext::new(1).with_permissions(vec!["user/*".to_string()]);
1105        let req = make_request_with_context(user_ctx);
1106        assert!(guard.check(&req).is_ok());
1107    }
1108
1109    #[test]
1110    fn test_permission_guard_passes_when_super() {
1111        // is_super=true 绕过所有 RBAC(对齐 PHP is_super=1)
1112        let guard = PermissionGuard::new("user/delete");
1113        let user_ctx = UserContext::new(1).with_super(true);
1114        let req = make_request_with_context(user_ctx);
1115        assert!(guard.check(&req).is_ok());
1116    }
1117
1118    #[test]
1119    fn test_permission_guard_passes_when_super_without_permissions() {
1120        // is_super=true 即使 permissions 为空也通过
1121        let guard = PermissionGuard::new("system/config");
1122        let user_ctx = UserContext::new(1).with_super(true);
1123        let req = make_request_with_context(user_ctx);
1124        assert!(guard.check(&req).is_ok());
1125    }
1126
1127    // ====================================================================
1128    // RoleGuard 单元测试
1129    // ====================================================================
1130
1131    #[test]
1132    fn test_role_guard_new() {
1133        let guard = RoleGuard::new("admin");
1134        assert_eq!(guard.role, "admin");
1135    }
1136
1137    #[test]
1138    fn test_role_guard_fails_when_not_logged_in() {
1139        let guard = RoleGuard::new("admin");
1140        let req = make_request();
1141        let result = guard.check(&req);
1142        assert!(result.is_err());
1143        let err = result.unwrap_err();
1144        assert_eq!(err.code, ErrorCode::NotLogin);
1145    }
1146
1147    #[test]
1148    fn test_role_guard_fails_when_no_user_context() {
1149        let guard = RoleGuard::new("admin");
1150        let req = make_request_with_user(1);
1151        let result = guard.check(&req);
1152        assert!(result.is_err());
1153        let err = result.unwrap_err();
1154        assert_eq!(err.code, ErrorCode::Forbidden);
1155    }
1156
1157    #[test]
1158    fn test_role_guard_fails_when_no_role() {
1159        let guard = RoleGuard::new("admin");
1160        let user_ctx = UserContext::new(1).with_roles(vec!["editor".to_string()]);
1161        let req = make_request_with_context(user_ctx);
1162        let result = guard.check(&req);
1163        assert!(result.is_err());
1164    }
1165
1166    #[test]
1167    fn test_role_guard_passes_when_has_role() {
1168        let guard = RoleGuard::new("admin");
1169        let user_ctx = UserContext::new(1).with_roles(vec!["admin".to_string()]);
1170        let req = make_request_with_context(user_ctx);
1171        assert!(guard.check(&req).is_ok());
1172    }
1173
1174    #[test]
1175    fn test_role_guard_passes_when_super() {
1176        // is_super 绕过角色检查
1177        let guard = RoleGuard::new("admin");
1178        let user_ctx = UserContext::new(1).with_super(true);
1179        let req = make_request_with_context(user_ctx);
1180        assert!(guard.check(&req).is_ok());
1181    }
1182
1183    // ====================================================================
1184    // GuardChain 单元测试
1185    // ====================================================================
1186
1187    #[test]
1188    fn test_guard_chain_new() {
1189        let chain = GuardChain::new();
1190        assert!(chain.guards.is_empty());
1191    }
1192
1193    #[test]
1194    fn test_guard_chain_default() {
1195        let chain = GuardChain::default();
1196        assert!(chain.guards.is_empty());
1197    }
1198
1199    #[test]
1200    fn test_guard_chain_with_guard() {
1201        let chain = GuardChain::new()
1202            .with_guard(Arc::new(AuthGuard))
1203            .with_guard(Arc::new(AdminGuard));
1204        assert_eq!(chain.guards.len(), 2);
1205    }
1206
1207    #[test]
1208    fn test_guard_chain_from_guards() {
1209        let guards: Vec<Arc<dyn Guard>> = vec![Arc::new(AuthGuard), Arc::new(AdminGuard)];
1210        let chain = GuardChain::from_guards(guards);
1211        assert_eq!(chain.guards.len(), 2);
1212    }
1213
1214    #[test]
1215    fn test_guard_chain_empty_passes() {
1216        // 空链应通过(无 Guard 需要检查)
1217        let chain = GuardChain::new();
1218        let req = make_request();
1219        assert!(chain.check(&req).is_ok());
1220    }
1221
1222    #[test]
1223    fn test_guard_chain_single_guard_passes() {
1224        let chain = GuardChain::new().with_guard(Arc::new(AuthGuard));
1225        let req = make_request_with_user(1);
1226        assert!(chain.check(&req).is_ok());
1227    }
1228
1229    #[test]
1230    fn test_guard_chain_single_guard_fails() {
1231        let chain = GuardChain::new().with_guard(Arc::new(AuthGuard));
1232        let req = make_request();
1233        assert!(chain.check(&req).is_err());
1234    }
1235
1236    #[test]
1237    fn test_guard_chain_and_semantics_all_pass() {
1238        let chain = GuardChain::new()
1239            .with_guard(Arc::new(AuthGuard))
1240            .with_guard(Arc::new(AdminGuard));
1241        let user_ctx = UserContext::new(1).with_super(true);
1242        let req = make_request_with_context(user_ctx);
1243        assert!(chain.check(&req).is_ok());
1244    }
1245
1246    #[test]
1247    fn test_guard_chain_and_semantics_first_fails() {
1248        // AuthGuard 失败 → 整个链失败
1249        let chain = GuardChain::new()
1250            .with_guard(Arc::new(AuthGuard))
1251            .with_guard(Arc::new(AdminGuard));
1252        let req = make_request();
1253        let result = chain.check(&req);
1254        assert!(result.is_err());
1255        let err = result.unwrap_err();
1256        // 第一个 Guard 失败,返回 NotLogin
1257        assert_eq!(err.code, ErrorCode::NotLogin);
1258    }
1259
1260    #[test]
1261    fn test_guard_chain_and_semantics_second_fails() {
1262        // AuthGuard 通过,AdminGuard 失败
1263        let chain = GuardChain::new()
1264            .with_guard(Arc::new(AuthGuard))
1265            .with_guard(Arc::new(AdminGuard));
1266        // 已登录但非超级管理员
1267        let user_ctx = UserContext::new(1).with_super(false);
1268        let req = make_request_with_context(user_ctx);
1269        let result = chain.check(&req);
1270        assert!(result.is_err());
1271        let err = result.unwrap_err();
1272        // 第二个 Guard 失败,返回 Forbidden
1273        assert_eq!(err.code, ErrorCode::Forbidden);
1274    }
1275
1276    #[test]
1277    fn test_guard_chain_and_semantics_short_circuit() {
1278        // 短路:第一个失败后不执行后续 Guard
1279        struct FailGuard;
1280        impl Guard for FailGuard {
1281            fn check(&self, _req: &Request) -> Result<(), GuardError> {
1282                Err(GuardError::forbidden("fail_guard_called"))
1283            }
1284        }
1285        struct PanicGuard;
1286        impl Guard for PanicGuard {
1287            fn check(&self, _req: &Request) -> Result<(), GuardError> {
1288                panic!("PanicGuard should not be called due to short-circuit");
1289            }
1290        }
1291        let chain = GuardChain::new()
1292            .with_guard(Arc::new(FailGuard))
1293            .with_guard(Arc::new(PanicGuard));
1294        let req = make_request();
1295        let result = chain.check(&req);
1296        assert!(result.is_err());
1297        assert_eq!(result.unwrap_err().msg, "fail_guard_called");
1298    }
1299
1300    #[test]
1301    fn test_guard_chain_order_matters() {
1302        // 顺序:先 AuthGuard,再 PermissionGuard
1303        let chain = GuardChain::new()
1304            .with_guard(Arc::new(AuthGuard))
1305            .with_guard(Arc::new(PermissionGuard::new("user/list")));
1306        // 未登录 → 应返回 NotLogin(而非 Forbidden)
1307        let req = make_request();
1308        let result = chain.check(&req);
1309        assert!(result.is_err());
1310        let err = result.unwrap_err();
1311        assert_eq!(err.code, ErrorCode::NotLogin);
1312    }
1313
1314    #[test]
1315    fn test_guard_chain_multiple_permissions() {
1316        let chain = GuardChain::new()
1317            .with_guard(Arc::new(AuthGuard))
1318            .with_guard(Arc::new(PermissionGuard::new("user/list")))
1319            .with_guard(Arc::new(PermissionGuard::new("user/save")));
1320        let user_ctx = UserContext::new(1)
1321            .with_permissions(vec!["user/list".to_string(), "user/save".to_string()]);
1322        let req = make_request_with_context(user_ctx);
1323        assert!(chain.check(&req).is_ok());
1324    }
1325
1326    #[test]
1327    fn test_guard_chain_mixed_guard_types() {
1328        let chain = GuardChain::new()
1329            .with_guard(Arc::new(AuthGuard))
1330            .with_guard(Arc::new(RoleGuard::new("editor")))
1331            .with_guard(Arc::new(PermissionGuard::new("post/list")));
1332        let user_ctx = UserContext::new(1)
1333            .with_roles(vec!["editor".to_string()])
1334            .with_permissions(vec!["post/list".to_string()]);
1335        let req = make_request_with_context(user_ctx);
1336        assert!(chain.check(&req).is_ok());
1337    }
1338
1339    // ====================================================================
1340    // check_guards 函数测试
1341    // ====================================================================
1342
1343    #[test]
1344    fn test_check_guards_empty() {
1345        let req = make_request();
1346        assert!(check_guards(&req, &[]).is_ok());
1347    }
1348
1349    #[test]
1350    fn test_check_guards_all_pass() {
1351        let guards: Vec<Arc<dyn Guard>> = vec![Arc::new(AuthGuard)];
1352        let req = make_request_with_user(1);
1353        assert!(check_guards(&req, &guards).is_ok());
1354    }
1355
1356    #[test]
1357    fn test_check_guards_fails() {
1358        let guards: Vec<Arc<dyn Guard>> = vec![Arc::new(AuthGuard)];
1359        let req = make_request();
1360        assert!(check_guards(&req, &guards).is_err());
1361    }
1362
1363    // ====================================================================
1364    // guard_middleware 集成测试
1365    // ====================================================================
1366
1367    #[tokio::test]
1368    async fn test_guard_middleware_passes() {
1369        let app = build_app(Arc::new(AuthGuard));
1370        // 模拟 Auth 中间件注入 AuthenticatedUser
1371        let req = Request::builder()
1372            .method("GET")
1373            .uri("/protected")
1374            .extension(AuthenticatedUser { user_id: 1 })
1375            .body(Body::empty())
1376            .unwrap();
1377        let resp = app.oneshot(req).await.unwrap();
1378        assert_eq!(resp.status(), StatusCode::OK);
1379    }
1380
1381    #[tokio::test]
1382    async fn test_guard_middleware_fails_not_login() {
1383        let app = build_app(Arc::new(AuthGuard));
1384        let req = Request::builder()
1385            .method("GET")
1386            .uri("/protected")
1387            .body(Body::empty())
1388            .unwrap();
1389        let resp = app.oneshot(req).await.unwrap();
1390        // NotLogin → HTTP 401
1391        assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
1392        let body = read_body(resp).await;
1393        let json: serde_json::Value = serde_json::from_str(&body).unwrap();
1394        assert_eq!(json["code"], -1);
1395        assert_eq!(json["msg"], "not_login");
1396    }
1397
1398    #[tokio::test]
1399    async fn test_guard_middleware_fails_forbidden() {
1400        let app = build_app(Arc::new(AdminGuard));
1401        // 已登录但非超级管理员
1402        let req = Request::builder()
1403            .method("GET")
1404            .uri("/protected")
1405            .extension(AuthenticatedUser { user_id: 1 })
1406            .extension(UserContext::new(1).with_super(false))
1407            .body(Body::empty())
1408            .unwrap();
1409        let resp = app.oneshot(req).await.unwrap();
1410        // Forbidden → HTTP 403
1411        assert_eq!(resp.status(), StatusCode::FORBIDDEN);
1412        let body = read_body(resp).await;
1413        let json: serde_json::Value = serde_json::from_str(&body).unwrap();
1414        assert_eq!(json["code"], 403);
1415    }
1416
1417    #[tokio::test]
1418    async fn test_guard_middleware_with_chain() {
1419        let chain = GuardChain::new()
1420            .with_guard(Arc::new(AuthGuard))
1421            .with_guard(Arc::new(AdminGuard));
1422        let app = build_app(Arc::new(chain));
1423
1424        // 已登录 + 超级管理员 → 通过
1425        let req = Request::builder()
1426            .method("GET")
1427            .uri("/protected")
1428            .extension(AuthenticatedUser { user_id: 1 })
1429            .extension(UserContext::new(1).with_super(true))
1430            .body(Body::empty())
1431            .unwrap();
1432        let resp = app.clone().oneshot(req).await.unwrap();
1433        assert_eq!(resp.status(), StatusCode::OK);
1434
1435        // 已登录 + 非超级管理员 → Forbidden
1436        let req = Request::builder()
1437            .method("GET")
1438            .uri("/protected")
1439            .extension(AuthenticatedUser { user_id: 2 })
1440            .extension(UserContext::new(2).with_super(false))
1441            .body(Body::empty())
1442            .unwrap();
1443        let resp = app.oneshot(req).await.unwrap();
1444        assert_eq!(resp.status(), StatusCode::FORBIDDEN);
1445    }
1446
1447    #[tokio::test]
1448    async fn test_guard_middleware_permission_guard() {
1449        let chain = GuardChain::new()
1450            .with_guard(Arc::new(AuthGuard))
1451            .with_guard(Arc::new(PermissionGuard::new("user/list")));
1452        let app = build_app(Arc::new(chain));
1453
1454        // 有权限 → 通过
1455        let req = Request::builder()
1456            .method("GET")
1457            .uri("/protected")
1458            .extension(AuthenticatedUser { user_id: 1 })
1459            .extension(UserContext::new(1).with_permissions(vec!["user/list".to_string()]))
1460            .body(Body::empty())
1461            .unwrap();
1462        let resp = app.clone().oneshot(req).await.unwrap();
1463        assert_eq!(resp.status(), StatusCode::OK);
1464
1465        // 无权限 → Forbidden
1466        let req = Request::builder()
1467            .method("GET")
1468            .uri("/protected")
1469            .extension(AuthenticatedUser { user_id: 2 })
1470            .extension(UserContext::new(2).with_permissions(vec!["order/list".to_string()]))
1471            .body(Body::empty())
1472            .unwrap();
1473        let resp = app.oneshot(req).await.unwrap();
1474        assert_eq!(resp.status(), StatusCode::FORBIDDEN);
1475    }
1476
1477    // ====================================================================
1478    // PHP 行为对齐验证
1479    // ====================================================================
1480
1481    /// PHP is_super=1 绕过所有 RBAC
1482    /// 对齐 PHP szoa `is_super=1` 行为
1483    #[test]
1484    fn test_php_alignment_is_super_bypass() {
1485        let permission_guard = PermissionGuard::new("system/config");
1486        let role_guard = RoleGuard::new("admin");
1487        let admin_guard = AdminGuard::new();
1488
1489        // is_super=true 即使无任何权限/角色也通过
1490        let user_ctx = UserContext::new(1).with_super(true);
1491        let req = make_request_with_context(user_ctx);
1492
1493        assert!(permission_guard.check(&req).is_ok());
1494        assert!(role_guard.check(&req).is_ok());
1495        assert!(admin_guard.check(&req).is_ok());
1496    }
1497
1498    /// PHP 错误码对齐
1499    /// - not_login → code=-1
1500    /// - 无权限 → code=403(Rust 扩展,PHP 端为 0)
1501    /// - 用户已禁用 → code=-3
1502    #[test]
1503    fn test_php_alignment_error_codes() {
1504        // not_login → code=-1(对齐 PHP)
1505        let err = GuardError::not_login("not_login");
1506        assert_eq!(err.code.as_i32(), -1);
1507
1508        // 用户已禁用 → code=-3(对齐 PHP 您已离职)
1509        let err = GuardError::user_disabled("您已离职");
1510        assert_eq!(err.code.as_i32(), -3);
1511
1512        // 无权限 → code=403(Rust 扩展,PHP 端为 0)
1513        // 注:PHP 端无统一无权限码,常用 0 表示失败;Rust 端使用 HTTP 403 更语义化
1514        let err = GuardError::forbidden("无权限访问");
1515        assert_eq!(err.code.as_i32(), 403);
1516    }
1517
1518    /// PHP checkLogin 行为对齐
1519    /// 对齐 PHP `addons\BaseController::checkLogin`:
1520    /// - 未登录 → throw BaseException(['code' => -1, 'msg' => 'not_login'])
1521    /// - 已登录 → return(通过)
1522    #[test]
1523    fn test_php_alignment_check_login() {
1524        let guard = AuthGuard::new();
1525
1526        // 未登录 → NotLogin
1527        let req = make_request();
1528        let result = guard.check(&req);
1529        assert!(matches!(
1530            result,
1531            Err(GuardError {
1532                code: ErrorCode::NotLogin,
1533                ..
1534            })
1535        ));
1536
1537        // 已登录 → 通过
1538        let req = make_request_with_user(1);
1539        assert!(guard.check(&req).is_ok());
1540    }
1541
1542    /// PHP 权限通配符对齐
1543    /// 对齐 PHP szoa `access` 表的通配符权限:`user/*` 匹配 `user/list` 等
1544    #[test]
1545    fn test_php_alignment_wildcard_permission() {
1546        let ctx = UserContext::new(1).with_permissions(vec!["user/*".to_string()]);
1547        assert!(ctx.has_permission("user/list"));
1548        assert!(ctx.has_permission("user/save"));
1549        assert!(ctx.has_permission("user/delete"));
1550        assert!(!ctx.has_permission("order/list"));
1551    }
1552
1553    /// PHP RBAC 多角色组合对齐
1554    /// 对齐 PHP szoa `user_role` 多角色关联:用户可同时拥有多个角色,任一角色满足即通过
1555    #[test]
1556    fn test_php_alignment_multiple_roles() {
1557        let ctx = UserContext::new(1).with_roles(vec!["editor".to_string(), "viewer".to_string()]);
1558        assert!(ctx.has_role("editor"));
1559        assert!(ctx.has_role("viewer"));
1560        assert!(!ctx.has_role("admin"));
1561    }
1562
1563    /// PHP 响应格式对齐
1564    /// 对齐 PHP `renderJson(code, msg, data)`:`{"code": <code>, "msg": "<msg>", "data": {}}`
1565    #[tokio::test]
1566    async fn test_php_alignment_response_format() {
1567        let err = GuardError::user_disabled("您已离职,无权使用本系统!");
1568        let resp = err.into_response();
1569        let body = read_body(resp).await;
1570        let json: serde_json::Value = serde_json::from_str(&body).unwrap();
1571        // 严格对齐 PHP renderJson 格式
1572        assert_eq!(json["code"], -3);
1573        assert_eq!(json["msg"], "您已离职,无权使用本系统!");
1574        assert_eq!(json["data"], serde_json::json!({}));
1575    }
1576}