Skip to main content

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