1use axum::body::Body;
32use axum::http::Request;
33use axum::response::{IntoResponse, Response};
34use indexmap::IndexMap;
35use once_cell::sync::Lazy;
36use serde_json::{Map, Value};
37use std::future::Future;
38
39use sz_rust_http_facade::request::{
40 fetch_post_data, fetch_post_data_by_key, fetch_query_data, fetch_query_data_by_key,
41};
42use sz_rust_http_facade::response::ApiResponse;
43use sz_rust_infra_facade::validate::Validate;
44
45#[derive(Clone, Default)]
68struct JwtConfig {
69 secret: String,
71 issuer: String,
73 audience: String,
77}
78
79impl std::fmt::Debug for JwtConfig {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 f.debug_struct("JwtConfig")
82 .field("secret", &"[REDACTED]")
83 .field("issuer", &self.issuer)
84 .field("audience", &self.audience)
85 .finish()
86 }
87}
88
89static JWT_CONFIG: Lazy<Option<JwtConfig>> = Lazy::new(|| {
98 let secret = std::env::var("SZ_JWT_SECRET").ok()?;
99 if secret.is_empty() {
101 return None;
102 }
103 Some(JwtConfig {
104 secret,
105 issuer: std::env::var("SZ_JWT_ISSUER").unwrap_or_default(),
106 audience: std::env::var("SZ_JWT_AUDIENCE").unwrap_or_default(),
108 })
109});
110
111pub fn validate_jwt_config() {
119 let Some(config) = JWT_CONFIG.as_ref() else {
120 panic!("SZ_JWT_SECRET 环境变量未设置 — 生产环境必须通过环境变量提供 JWT 密钥");
121 };
122 if config.secret.len() < 32 {
123 panic!(
124 "SZ_JWT_SECRET 强度不足:当前 {} 字节,HS256 要求 ≥ 32 字节(256 位)。\
125 请使用 `openssl rand -base64 32` 生成强密钥",
126 config.secret.len()
127 );
128 }
129}
130
131fn strip_bearer_prefix(header: &str) -> &str {
140 let trimmed = header.trim();
141 if trimmed.len() >= 6 {
143 let prefix = &trimmed[..6];
144 if prefix.eq_ignore_ascii_case("bearer") {
145 return trimmed[6..].trim_start();
147 }
148 }
149 trimmed
150}
151
152fn verify_token_with_config(
167 authorization: Option<&str>,
168 config: &JwtConfig,
169) -> Result<Option<UserInfo>, String> {
170 let header_value = match authorization {
172 Some(v) if !v.is_empty() => v,
173 _ => return Ok(None),
174 };
175
176 let token = strip_bearer_prefix(header_value).trim();
178 if token.is_empty() {
179 return Ok(None);
180 }
181
182 if config.secret.is_empty() {
184 return Ok(None);
185 }
186
187 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&config.secret);
189 let claims = match encoder.decode(token) {
190 Ok(c) => c,
191 Err(_) => return Ok(None),
192 };
193
194 if !config.issuer.is_empty() {
196 match &claims.iss {
197 Some(iss) if iss == &config.issuer => { }
198 _ => return Ok(None),
199 }
200 }
201
202 let user_id = match claims.user_id {
204 Some(id) => id,
205 None => return Ok(None),
206 };
207
208 Ok(Some(UserInfo {
209 user_id,
210 is_login: true,
211 }))
212}
213
214pub trait SzController: Send + Sync {
242 fn render_json(&self, code: i32, msg: impl Into<String>, data: Value) -> Value {
255 let mut map = Map::new();
256 map.insert("code".to_string(), Value::Number(code.into()));
257 map.insert("msg".to_string(), Value::String(msg.into()));
258 map.insert("data".to_string(), data);
259 Value::Object(map)
260 }
261
262 fn render_success(&self, msg: impl Into<String>, data: Value) -> Response {
275 ApiResponse::success(data, msg).into_response()
276 }
277
278 fn render_error(&self, msg: impl Into<String>, data: Value, code: i32) -> Response {
291 ApiResponse::error_with_code(code, msg, data).into_response()
292 }
293
294 fn post_data(&self, req: Request<Body>) -> impl Future<Output = Result<Value, String>> + Send {
307 async move { fetch_post_data(req).await }
308 }
309
310 fn post_data_by_key(
318 &self,
319 req: Request<Body>,
320 key: &str,
321 ) -> impl Future<Output = Result<Option<Value>, String>> + Send {
322 async move { fetch_post_data_by_key(req, key).await }
323 }
324
325 fn get_data(&self, req: &Request<Body>) -> Value {
329 fetch_query_data(req)
330 }
331
332 fn get_data_by_key(&self, req: &Request<Body>, key: &str) -> Option<Value> {
339 fetch_query_data_by_key(req, key)
340 }
341}
342
343pub trait BaseController: SzController {
365 fn batch_validate(&self) -> bool {
370 false
371 }
372
373 fn middlewares(&self) -> Vec<String> {
378 Vec::new()
379 }
380
381 fn initialize(&self) {}
394
395 fn validate(
424 &self,
425 data: &Value,
426 rules: &[(&str, &str)],
427 messages: &[(&str, &str)],
428 ) -> Result<(), String> {
429 let mut validator = Validate::new();
431 for (name, rule) in rules {
432 validator = validator.rule(name, rule);
433 }
434
435 let mut msg_map = IndexMap::new();
437 for (key, msg) in messages {
438 msg_map.insert(key.to_string(), msg.to_string());
439 }
440 validator = validator.message(msg_map);
441
442 if self.batch_validate() {
444 validator = validator.batch(true);
445 }
446
447 match validator.check(data) {
449 Ok(()) => Ok(()),
450 Err(err) => Err(err.to_string()),
451 }
452 }
453}
454
455#[derive(Debug, Clone, PartialEq, Eq)]
459pub struct RouteInfo {
460 pub controller: String,
462 pub action: String,
464 pub route_uri: String,
466 pub group: String,
468}
469
470#[derive(Debug, Clone, PartialEq, Eq)]
474pub struct UserInfo {
475 pub user_id: i64,
477 pub is_login: bool,
479}
480
481pub trait AddonsBaseController: BaseController {
507 fn allow_all_action(&self) -> Vec<&'static str> {
512 vec!["/passport/login", "/task/task/userClerk"]
513 }
514
515 fn parse_route_info(&self, uri: &str) -> RouteInfo {
549 let path = uri.split('?').next().unwrap_or("");
550 let path = path.trim_start_matches('/');
551 let segments: Vec<&str> = if path.is_empty() {
552 Vec::new()
553 } else {
554 path.split('/').collect()
555 };
556
557 let (controller, action) = match segments.len() {
558 0 => (String::new(), String::new()),
559 1 => (segments[0].to_string(), String::new()),
560 _ => (
561 segments[..segments.len() - 1].join("/"),
562 segments[segments.len() - 1].to_string(),
563 ),
564 };
565
566 let group = controller.clone();
568
569 let route_uri = if controller.is_empty() && action.is_empty() {
570 "/".to_string()
571 } else {
572 format!("/{controller}/{action}")
573 };
574
575 RouteInfo {
576 controller,
577 action,
578 route_uri,
579 group,
580 }
581 }
582
583 fn check_login(&self, route_uri: &str, user_is_login: bool) -> Result<(), String> {
607 if self.allow_all_action().contains(&route_uri) {
608 return Ok(());
609 }
610 if user_is_login {
611 return Ok(());
612 }
613 Err("not_login".to_string())
614 }
615
616 fn get_token(&self, authorization: Option<&str>) -> Result<Option<UserInfo>, String> {
656 match JWT_CONFIG.as_ref() {
660 Some(config) => verify_token_with_config(authorization, config),
661 None => Ok(None),
662 }
663 }
664}
665
666use parking_lot::RwLock;
671use sha2::{Digest, Sha256};
672use std::time::Duration;
673
674pub struct KeyRotation {
679 current: RwLock<String>,
681 previous: RwLock<Vec<(String, std::time::Instant)>>,
683 rotation_interval: Duration,
685 grace_period: Duration,
687 max_previous: usize,
689}
690
691impl std::fmt::Debug for KeyRotation {
692 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
693 f.debug_struct("KeyRotation")
694 .field("current", &"[REDACTED]")
695 .field("previous", &"[REDACTED]")
696 .field("rotation_interval", &self.rotation_interval)
697 .field("grace_period", &self.grace_period)
698 .field("max_previous", &self.max_previous)
699 .finish()
700 }
701}
702
703#[derive(Debug, thiserror::Error)]
705pub enum KeyRotationError {
706 #[error("SZ300_JWT_SECRET 环境变量未设置")]
708 SecretMissing,
709 #[error("Token 验证失败:所有密钥均无法解码")]
711 InvalidToken,
712 #[error("Token 签发失败:{0}")]
714 SignError(String),
715}
716
717impl KeyRotation {
718 pub fn from_env() -> Result<Self, KeyRotationError> {
724 let current =
725 std::env::var("SZ300_JWT_SECRET").map_err(|_| KeyRotationError::SecretMissing)?;
726 if current.is_empty() {
727 return Err(KeyRotationError::SecretMissing);
728 }
729
730 let rotation_interval = std::env::var("SZ300_JWT_ROTATION_INTERVAL")
731 .ok()
732 .and_then(|s| s.parse().ok())
733 .map(Duration::from_secs)
734 .unwrap_or(Duration::from_secs(86400));
735
736 let grace_period = std::env::var("SZ300_JWT_GRACE_PERIOD")
737 .ok()
738 .and_then(|s| s.parse().ok())
739 .map(Duration::from_secs)
740 .unwrap_or(Duration::from_secs(3600));
741
742 Ok(Self {
743 current: RwLock::new(current),
744 previous: RwLock::new(Vec::new()),
745 rotation_interval,
746 grace_period,
747 max_previous: 3,
748 })
749 }
750
751 pub fn new(
753 current: String,
754 rotation_interval: Duration,
755 grace_period: Duration,
756 max_previous: usize,
757 ) -> Self {
758 Self {
759 current: RwLock::new(current),
760 previous: RwLock::new(Vec::new()),
761 rotation_interval,
762 grace_period,
763 max_previous,
764 }
765 }
766
767 pub fn sign_token(
769 &self,
770 claims: &sz_rust_orm_facade::jwt::JwtClaims,
771 ) -> Result<String, KeyRotationError> {
772 let secret = self.current.read().clone();
773 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&secret);
774 encoder
775 .encode(claims)
776 .map_err(|e| KeyRotationError::SignError(e.to_string()))
777 }
778
779 pub fn verify_token(
781 &self,
782 token: &str,
783 ) -> Result<sz_rust_orm_facade::jwt::JwtClaims, KeyRotationError> {
784 let current_secret = self.current.read().clone();
786 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(¤t_secret);
787 if let Ok(claims) = encoder.decode(token) {
788 return Ok(claims);
789 }
790
791 let now = std::time::Instant::now();
793 let previous = self.previous.read();
794 for (key, expires_at) in previous.iter() {
795 if now < *expires_at {
796 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(key);
797 if let Ok(claims) = encoder.decode(token) {
798 return Ok(claims);
799 }
800 }
801 }
802
803 Err(KeyRotationError::InvalidToken)
804 }
805
806 pub fn spawn_rotation_task(self: std::sync::Arc<Self>) -> tokio::task::JoinHandle<()> {
808 let interval = self.rotation_interval;
809 let grace_period = self.grace_period;
810 let max_previous = self.max_previous;
811
812 tokio::spawn(async move {
813 let mut ticker = tokio::time::interval(interval);
814 ticker.tick().await; loop {
817 ticker.tick().await;
818 if let Err(e) = Self::do_rotation(&self, grace_period, max_previous).await {
819 tracing::error!("JWT_KEY_ROTATION_FAILED: {e}");
820 }
821 }
822 })
823 }
824
825 pub async fn do_rotation(
827 &self,
828 grace_period: Duration,
829 max_previous: usize,
830 ) -> Result<(), String> {
831 let new_key = {
833 use rand::Rng;
834 let mut rng = rand::thread_rng();
835 let bytes: [u8; 32] = rng.gen();
836 hex::encode(bytes)
837 };
838
839 let now = std::time::Instant::now();
840 let expires_at = now + grace_period;
841
842 let old_current = {
844 let mut current = self.current.write();
845 let old = current.clone();
846 *current = new_key.clone();
847 old
848 };
849
850 {
851 let mut previous = self.previous.write();
852 previous.push((old_current.clone(), expires_at));
853 while previous.len() > max_previous {
855 previous.remove(0);
856 }
857 previous.retain(|(_, exp)| now < *exp);
859 }
860
861 let old_fp = Self::fingerprint(&old_current);
862 let new_fp = Self::fingerprint(&new_key);
863 tracing::info!("JWT_KEY_ROTATED: old_fingerprint={old_fp}, new_fingerprint={new_fp}");
864
865 Ok(())
866 }
867
868 pub fn fingerprint(key: &str) -> String {
870 let mut hasher = Sha256::new();
871 hasher.update(key.as_bytes());
872 let result = hasher.finalize();
873 hex::encode(&result[..4])
874 }
875
876 pub fn current_fingerprint(&self) -> String {
878 let current = self.current.read();
879 Self::fingerprint(¤t)
880 }
881}
882
883#[cfg(test)]
884mod tests {
885 use super::*;
886 use axum::body::Body;
887 use axum::http::{Method, Request, StatusCode};
888 use http_body_util::BodyExt;
889 use serde_json::json;
890 use tower::ServiceExt;
891
892 struct MockController;
894
895 impl SzController for MockController {}
896
897 fn make_json_request(body: &str, query: Option<&str>) -> Request<Body> {
898 let uri = match query {
899 Some(q) => format!("/?{q}"),
900 None => "/".to_string(),
901 };
902 Request::builder()
903 .method(Method::POST)
904 .uri(&uri)
905 .header("content-type", "application/json")
906 .body(Body::from(body.to_string()))
907 .unwrap()
908 }
909
910 fn make_get_request(query: &str) -> Request<Body> {
911 Request::builder()
912 .method(Method::GET)
913 .uri(format!("/?{query}"))
914 .body(Body::empty())
915 .unwrap()
916 }
917
918 async fn collect_body(resp: Response) -> String {
919 let bytes = resp.into_body().collect().await.unwrap().to_bytes();
920 String::from_utf8(bytes.to_vec()).unwrap()
921 }
922
923 #[test]
928 fn test_render_json_field_order() {
929 let ctrl = MockController;
931 let value = ctrl.render_json(1, "ok", json!({"id": 1}));
932 let obj = value.as_object().unwrap();
933 let keys: Vec<&String> = obj.keys().collect();
934 assert_eq!(keys, vec!["code", "msg", "data"]);
935 }
936
937 #[test]
938 fn test_render_json_default_values() {
939 let ctrl = MockController;
941 let value = ctrl.render_json(1, "", Value::Object(Map::new()));
942 assert_eq!(value["code"], 1);
943 assert_eq!(value["msg"], "");
944 assert!(value["data"].is_object());
945 assert!(value["data"].as_object().unwrap().is_empty());
946 }
947
948 #[test]
949 fn test_render_json_custom_code() {
950 let ctrl = MockController;
951 let value = ctrl.render_json(-1, "未登录", json!({}));
952 assert_eq!(value["code"], -1);
953 assert_eq!(value["msg"], "未登录");
954 }
955
956 #[test]
957 fn test_render_json_compact_semantics() {
958 let ctrl = MockController;
961 let value = ctrl.render_json(0, "失败", json!({"field": "name"}));
962 let json_str = value.to_string();
963 assert_eq!(
965 json_str,
966 r#"{"code":0,"msg":"失败","data":{"field":"name"}}"#
967 );
968 }
969
970 #[test]
971 fn test_render_json_returns_value_not_response() {
972 let ctrl = MockController;
974 let value = ctrl.render_json(1, "ok", json!({}));
975 assert!(value.is_object());
977 }
978
979 #[test]
984 fn test_render_success_returns_response() {
985 let ctrl = MockController;
986 let resp = ctrl.render_success("success", json!({"id": 1}));
987 assert_eq!(resp.status(), StatusCode::OK);
988 assert_eq!(
989 resp.headers().get("content-type").unwrap(),
990 "application/json; charset=utf-8"
991 );
992 }
993
994 #[tokio::test]
995 async fn test_render_success_body_format() {
996 let ctrl = MockController;
998 let resp = ctrl.render_success("success", json!({"id": 1, "name": "alice"}));
999 let body = collect_body(resp).await;
1000 assert_eq!(
1001 body,
1002 r#"{"code":1,"msg":"success","data":{"id":1,"name":"alice"}}"#
1003 );
1004 }
1005
1006 #[tokio::test]
1007 async fn test_render_success_default_msg() {
1008 let ctrl = MockController;
1010 let resp = ctrl.render_success("success", json!({}));
1011 let body = collect_body(resp).await;
1012 assert_eq!(body, r#"{"code":1,"msg":"success","data":{}}"#);
1013 }
1014
1015 #[tokio::test]
1016 async fn test_render_success_via_axum_router() {
1017 struct UserController;
1019 impl SzController for UserController {}
1020
1021 async fn handler() -> Response {
1022 let ctrl = UserController;
1023 ctrl.render_success("ok", json!({"id": 1}))
1024 }
1025
1026 let router = axum::Router::new().route("/", axum::routing::get(handler));
1027 let req = Request::builder()
1028 .method(Method::GET)
1029 .uri("/")
1030 .body(Body::empty())
1031 .unwrap();
1032 let resp = router.oneshot(req).await.unwrap();
1033
1034 assert_eq!(resp.status(), StatusCode::OK);
1035 let body = collect_body(resp).await;
1036 assert_eq!(body, r#"{"code":1,"msg":"ok","data":{"id":1}}"#);
1037 }
1038
1039 #[test]
1044 fn test_render_error_returns_response() {
1045 let ctrl = MockController;
1046 let resp = ctrl.render_error("error", json!({}), 0);
1047 assert_eq!(resp.status(), StatusCode::OK); }
1049
1050 #[tokio::test]
1051 async fn test_render_error_default_code() {
1052 let ctrl = MockController;
1054 let resp = ctrl.render_error("参数错误", json!({}), 0);
1055 let body = collect_body(resp).await;
1056 assert_eq!(body, r#"{"code":0,"msg":"参数错误","data":{}}"#);
1057 }
1058
1059 #[tokio::test]
1060 async fn test_render_error_custom_code() {
1061 let ctrl = MockController;
1063 let resp = ctrl.render_error("not_login", json!({}), -1);
1064 let body = collect_body(resp).await;
1065 assert_eq!(body, r#"{"code":-1,"msg":"not_login","data":{}}"#);
1066 }
1067
1068 #[tokio::test]
1069 async fn test_render_error_with_data() {
1070 let ctrl = MockController;
1071 let resp = ctrl.render_error("失败", json!({"field": "name"}), 0);
1072 let body = collect_body(resp).await;
1073 assert_eq!(body, r#"{"code":0,"msg":"失败","data":{"field":"name"}}"#);
1074 }
1075
1076 #[tokio::test]
1077 async fn test_render_error_via_axum_router() {
1078 struct UserController;
1079 impl SzController for UserController {}
1080
1081 async fn handler() -> Response {
1082 let ctrl = UserController;
1083 ctrl.render_error("参数错误", json!({}), 0)
1084 }
1085
1086 let router = axum::Router::new().route("/", axum::routing::post(handler));
1087 let req = Request::builder()
1088 .method(Method::POST)
1089 .uri("/")
1090 .body(Body::empty())
1091 .unwrap();
1092 let resp = router.oneshot(req).await.unwrap();
1093
1094 assert_eq!(resp.status(), StatusCode::OK);
1095 let body = collect_body(resp).await;
1096 assert_eq!(body, r#"{"code":0,"msg":"参数错误","data":{}}"#);
1097 }
1098
1099 #[tokio::test]
1104 async fn test_post_data_json_body() {
1105 let ctrl = MockController;
1106 let req = make_json_request(r#"{"name":"alice","age":30}"#, None);
1107 let data = ctrl.post_data(req).await.unwrap();
1108 assert_eq!(data["name"], "alice");
1109 assert_eq!(data["age"], 30);
1110 }
1111
1112 #[tokio::test]
1113 async fn test_post_data_query_only() {
1114 let ctrl = MockController;
1115 let req = make_json_request("", Some("page=1&size=10"));
1116 let data = ctrl.post_data(req).await.unwrap();
1117 assert_eq!(data["page"], "1");
1118 assert_eq!(data["size"], "10");
1119 }
1120
1121 #[tokio::test]
1122 async fn test_post_data_body_overrides_query() {
1123 let ctrl = MockController;
1125 let req = make_json_request(r#"{"page":99}"#, Some("page=1&size=10"));
1126 let data = ctrl.post_data(req).await.unwrap();
1127 assert_eq!(data["page"], 99);
1128 assert_eq!(data["size"], "10");
1129 }
1130
1131 #[tokio::test]
1132 async fn test_post_data_by_key_exists() {
1133 let ctrl = MockController;
1134 let req = make_json_request(r#"{"name":"alice","age":30}"#, None);
1135 let name = ctrl.post_data_by_key(req, "name").await.unwrap();
1136 assert_eq!(name, Some(json!("alice")));
1137 }
1138
1139 #[tokio::test]
1140 async fn test_post_data_by_key_missing() {
1141 let ctrl = MockController;
1142 let req = make_json_request(r#"{"name":"alice"}"#, None);
1143 let age = ctrl.post_data_by_key(req, "age").await.unwrap();
1144 assert_eq!(age, None);
1145 }
1146
1147 #[test]
1152 fn test_get_data_query() {
1153 let ctrl = MockController;
1154 let req = make_get_request("page=1&size=10");
1155 let data = ctrl.get_data(&req);
1156 assert_eq!(data["page"], "1");
1157 assert_eq!(data["size"], "10");
1158 }
1159
1160 #[test]
1161 fn test_get_data_empty_query() {
1162 let ctrl = MockController;
1163 let req = make_get_request("");
1164 let data = ctrl.get_data(&req);
1165 assert!(data.as_object().unwrap().is_empty());
1166 }
1167
1168 #[test]
1169 fn test_get_data_by_key_exists() {
1170 let ctrl = MockController;
1171 let req = make_get_request("page=1&size=10");
1172 let page = ctrl.get_data_by_key(&req, "page");
1173 assert_eq!(page, Some(json!("1")));
1174 }
1175
1176 #[test]
1177 fn test_get_data_by_key_missing() {
1178 let ctrl = MockController;
1179 let req = make_get_request("page=1");
1180 let size = ctrl.get_data_by_key(&req, "size");
1181 assert_eq!(size, None);
1182 }
1183
1184 #[tokio::test]
1189 async fn test_multiple_controllers_independent() {
1190 struct CtrlA;
1192 struct CtrlB;
1193 impl SzController for CtrlA {}
1194 impl SzController for CtrlB {}
1195
1196 let a = CtrlA;
1197 let b = CtrlB;
1198
1199 let req_a = make_json_request(r#"{"k":"a"}"#, None);
1200 let req_b = make_json_request(r#"{"k":"b"}"#, None);
1201
1202 let data_a = a.post_data(req_a).await.unwrap();
1203 let data_b = b.post_data(req_b).await.unwrap();
1204
1205 assert_eq!(data_a["k"], "a");
1206 assert_eq!(data_b["k"], "b");
1207
1208 let va = a.render_json(1, "a", json!({}));
1210 let vb = b.render_json(0, "b", json!({}));
1211 assert_eq!(va["code"], 1);
1212 assert_eq!(va["msg"], "a");
1213 assert_eq!(vb["code"], 0);
1214 assert_eq!(vb["msg"], "b");
1215 }
1216
1217 #[tokio::test]
1222 async fn test_php_consistency_full_flow() {
1223 struct OrderController;
1225 impl SzController for OrderController {}
1226
1227 let ctrl = OrderController;
1228
1229 let req = make_json_request(r#"{"order_id":12345,"amount":99.5}"#, None);
1231 let data = ctrl.post_data(req).await.unwrap();
1232 let order_id = data["order_id"].as_i64().unwrap();
1233 let amount = data["amount"].as_f64().unwrap();
1234
1235 let result = json!({
1237 "order_id": order_id,
1238 "amount": amount,
1239 "status": "paid"
1240 });
1241
1242 let resp = ctrl.render_success("支付成功", result);
1244 let body = collect_body(resp).await;
1245
1246 assert_eq!(
1248 body,
1249 r#"{"code":1,"msg":"支付成功","data":{"order_id":12345,"amount":99.5,"status":"paid"}}"#
1250 );
1251 }
1252
1253 #[tokio::test]
1254 async fn test_php_consistency_error_flow() {
1255 struct UserController;
1257 impl SzController for UserController {}
1258
1259 let ctrl = UserController;
1260
1261 let req = make_json_request(r#"{"name":""}"#, None);
1262 let data = ctrl.post_data(req).await.unwrap();
1263 let name = data["name"].as_str().unwrap();
1264
1265 if name.is_empty() {
1266 let resp = ctrl.render_error("用户名不能为空", json!({"field": "name"}), 0);
1267 let body = collect_body(resp).await;
1268 assert_eq!(
1269 body,
1270 r#"{"code":0,"msg":"用户名不能为空","data":{"field":"name"}}"#
1271 );
1272 } else {
1273 panic!("should be empty");
1274 }
1275 }
1276
1277 #[tokio::test]
1278 async fn test_php_consistency_not_login_flow() {
1279 struct PassportController;
1281 impl SzController for PassportController {}
1282
1283 let ctrl = PassportController;
1284 let resp = ctrl.render_error("not_login", json!({}), -1);
1285 let body = collect_body(resp).await;
1286 assert_eq!(body, r#"{"code":-1,"msg":"not_login","data":{}}"#);
1287 }
1288
1289 struct MockBaseController;
1295
1296 impl SzController for MockBaseController {}
1297 impl BaseController for MockBaseController {}
1298
1299 #[test]
1300 fn test_base_controller_default_batch_validate() {
1301 let ctrl = MockBaseController;
1303 assert!(!ctrl.batch_validate());
1304 }
1305
1306 #[test]
1307 fn test_base_controller_default_middlewares_empty() {
1308 let ctrl = MockBaseController;
1310 assert!(ctrl.middlewares().is_empty());
1311 }
1312
1313 #[test]
1314 fn test_base_controller_default_initialize_no_panic() {
1315 let ctrl = MockBaseController;
1317 ctrl.initialize(); }
1319
1320 #[test]
1321 fn test_base_controller_default_validate_returns_ok() {
1322 let ctrl = MockBaseController;
1324 let data = json!({"name": "alice"});
1325 let rules = [("name", "require")];
1326 let messages: [(&str, &str); 0] = [];
1327 let result = ctrl.validate(&data, &rules, &messages);
1328 assert!(result.is_ok());
1329 }
1330
1331 #[test]
1332 fn test_validate_require_pass_with_value() {
1333 let ctrl = MockBaseController;
1335 let data = json!({"name": "alice", "age": 30});
1336 let rules = [("name", "require"), ("age", "require|integer")];
1337 let messages: [(&str, &str); 0] = [];
1338 assert!(ctrl.validate(&data, &rules, &messages).is_ok());
1339 }
1340
1341 #[test]
1342 fn test_validate_require_fail_when_missing() {
1343 let ctrl = MockBaseController;
1345 let data = json!({"name": "alice"});
1346 let rules = [("name", "require"), ("age", "require|integer")];
1347 let messages: [(&str, &str); 0] = [];
1348 let result = ctrl.validate(&data, &rules, &messages);
1349 assert!(result.is_err());
1350 let err = result.unwrap_err();
1351 assert!(err.contains("age"), "error: {err}");
1353 }
1354
1355 #[test]
1356 fn test_validate_integer_fail_on_string() {
1357 let ctrl = MockBaseController;
1359 let data = json!({"age": "not-a-number"});
1360 let rules = [("age", "require|integer")];
1361 let messages: [(&str, &str); 0] = [];
1362 let result = ctrl.validate(&data, &rules, &messages);
1363 assert!(result.is_err());
1364 }
1365
1366 #[test]
1367 fn test_validate_custom_message_applied() {
1368 let ctrl = MockBaseController;
1370 let data = json!({}); let rules = [("name", "require")];
1372 let messages = [("name.require", "名称必填")];
1373 let result = ctrl.validate(&data, &rules, &messages);
1374 assert!(result.is_err());
1375 assert_eq!(result.unwrap_err(), "名称必填");
1376 }
1377
1378 #[test]
1379 fn test_validate_batch_mode_returns_multiple_errors() {
1380 struct BatchController;
1382 impl SzController for BatchController {}
1383 impl BaseController for BatchController {
1384 fn batch_validate(&self) -> bool {
1385 true
1386 }
1387 }
1388
1389 let ctrl = BatchController;
1390 let data = json!({}); let rules = [("name", "require"), ("age", "require")];
1392 let messages = [("name.require", "名称必填"), ("age.require", "年龄必填")];
1393 let result = ctrl.validate(&data, &rules, &messages);
1394 assert!(result.is_err());
1395 let err = result.unwrap_err();
1396 assert!(err.contains("名称必填"), "err: {err}");
1398 assert!(err.contains("年龄必填"), "err: {err}");
1399 assert!(err.contains("; "), "err: {err}");
1401 }
1402
1403 #[test]
1404 fn test_validate_single_mode_returns_first_error_only() {
1405 let ctrl = MockBaseController;
1407 let data = json!({}); let rules = [("name", "require"), ("age", "require")];
1409 let messages = [("name.require", "名称必填"), ("age.require", "年龄必填")];
1410 let result = ctrl.validate(&data, &rules, &messages);
1411 assert!(result.is_err());
1412 let err = result.unwrap_err();
1413 assert!(err.contains("名称必填"), "err: {err}");
1415 assert!(!err.contains("年龄必填"), "err: {err}");
1416 }
1417
1418 #[test]
1419 fn test_validate_in_rule_pass() {
1420 let ctrl = MockBaseController;
1422 let data = json!({"status": "active"});
1423 let rules = [("status", "require|in:active,inactive")];
1424 let messages: [(&str, &str); 0] = [];
1425 assert!(ctrl.validate(&data, &rules, &messages).is_ok());
1426 }
1427
1428 #[test]
1429 fn test_validate_in_rule_fail() {
1430 let ctrl = MockBaseController;
1432 let data = json!({"status": "deleted"});
1433 let rules = [("status", "require|in:active,inactive")];
1434 let messages: [(&str, &str); 0] = [];
1435 assert!(ctrl.validate(&data, &rules, &messages).is_err());
1436 }
1437
1438 #[test]
1439 fn test_validate_empty_rules_always_pass() {
1440 let ctrl = MockBaseController;
1442 let data = json!({"anything": "value"});
1443 let rules: [(&str, &str); 0] = [];
1444 let messages: [(&str, &str); 0] = [];
1445 assert!(ctrl.validate(&data, &rules, &messages).is_ok());
1446 }
1447
1448 #[test]
1449 fn test_base_controller_inherits_sz_controller_methods() {
1450 let ctrl = MockBaseController;
1452 let value = ctrl.render_json(1, "ok", json!({}));
1453 assert_eq!(value["code"], 1);
1454 assert_eq!(value["msg"], "ok");
1455
1456 let resp = ctrl.render_success("ok", json!({"id": 1}));
1457 assert_eq!(resp.status(), StatusCode::OK);
1458 }
1459
1460 struct CustomController;
1462
1463 impl SzController for CustomController {}
1464 impl BaseController for CustomController {
1465 fn batch_validate(&self) -> bool {
1466 true
1467 }
1468
1469 fn middlewares(&self) -> Vec<String> {
1470 vec!["auth".to_string(), "cors".to_string()]
1471 }
1472
1473 fn initialize(&self) {
1474 }
1476 }
1477
1478 #[test]
1479 fn test_base_controller_override_batch_validate() {
1480 let ctrl = CustomController;
1481 assert!(ctrl.batch_validate());
1482 }
1483
1484 #[test]
1485 fn test_base_controller_override_middlewares() {
1486 let ctrl = CustomController;
1487 let mws = ctrl.middlewares();
1488 assert_eq!(mws, vec!["auth", "cors"]);
1489 }
1490
1491 #[test]
1492 fn test_base_controller_override_initialize() {
1493 let ctrl = CustomController;
1494 ctrl.initialize(); assert!(
1497 ctrl.batch_validate(),
1498 "覆盖 initialize 后默认校验开关应保持"
1499 );
1500 }
1501
1502 struct StatefulController {
1505 initialized: parking_lot::Mutex<bool>,
1506 custom_batch: bool,
1507 }
1508
1509 impl StatefulController {
1510 fn new() -> Self {
1511 Self {
1512 initialized: parking_lot::Mutex::new(false),
1513 custom_batch: false,
1514 }
1515 }
1516 }
1517
1518 impl SzController for StatefulController {}
1519 impl BaseController for StatefulController {
1520 fn batch_validate(&self) -> bool {
1521 self.custom_batch
1522 }
1523
1524 fn initialize(&self) {
1525 *self.initialized.lock() = true;
1526 }
1529 }
1530
1531 #[test]
1532 fn test_base_controller_stateful_initialize() {
1533 let ctrl = StatefulController::new();
1534 assert!(!*ctrl.initialized.lock()); ctrl.initialize(); assert!(*ctrl.initialized.lock()); }
1538
1539 #[tokio::test]
1541 async fn test_base_controller_php_full_flow() {
1542 struct UserController;
1543 impl SzController for UserController {}
1544 impl BaseController for UserController {}
1545
1546 let ctrl = UserController;
1547
1548 ctrl.initialize();
1550
1551 let req = make_json_request(r#"{"name":"alice","age":30}"#, None);
1553 let data = ctrl.post_data(req).await.unwrap();
1554
1555 let rules = [("name", "require"), ("age", "require|integer|gt:0")];
1557 let messages: [(&str, &str); 0] = [];
1558 ctrl.validate(&data, &rules, &messages).unwrap();
1559
1560 let resp =
1562 ctrl.render_success("success", json!({"name": data["name"], "age": data["age"]}));
1563 let body = collect_body(resp).await;
1564 assert_eq!(
1565 body,
1566 r#"{"code":1,"msg":"success","data":{"name":"alice","age":30}}"#
1567 );
1568 }
1569
1570 #[test]
1572 fn test_base_controller_multiple_instances_independent() {
1573 struct ControllerA;
1574 struct ControllerB;
1575
1576 impl SzController for ControllerA {}
1577 impl BaseController for ControllerA {
1578 fn middlewares(&self) -> Vec<String> {
1579 vec!["auth".to_string()]
1580 }
1581 }
1582
1583 impl SzController for ControllerB {}
1584 impl BaseController for ControllerB {
1585 fn middlewares(&self) -> Vec<String> {
1586 vec!["cors".to_string(), "log".to_string()]
1587 }
1588 }
1589
1590 let a = ControllerA;
1591 let b = ControllerB;
1592
1593 assert_eq!(a.middlewares(), vec!["auth"]);
1594 assert_eq!(b.middlewares(), vec!["cors", "log"]);
1595
1596 assert!(!a.batch_validate());
1598 assert!(!b.batch_validate());
1599 }
1600
1601 #[test]
1603 fn test_base_controller_inheritance_chain() {
1604 struct BusinessController;
1606 impl SzController for BusinessController {}
1607 impl BaseController for BusinessController {}
1608
1609 let ctrl = BusinessController;
1610
1611 let value = ctrl.render_json(0, "error", json!({}));
1613 assert!(value.is_object());
1614
1615 assert!(!ctrl.batch_validate());
1617 assert!(ctrl.middlewares().is_empty());
1618 ctrl.initialize();
1619 }
1620
1621 struct MockAddonsController;
1627
1628 impl SzController for MockAddonsController {}
1629 impl BaseController for MockAddonsController {}
1630 impl AddonsBaseController for MockAddonsController {}
1631
1632 #[test]
1633 fn test_addons_default_allow_all_action() {
1634 let ctrl = MockAddonsController;
1636 let allow = ctrl.allow_all_action();
1637 assert!(allow.contains(&"/passport/login"));
1638 assert!(allow.contains(&"/task/task/userClerk"));
1639 assert_eq!(allow.len(), 2);
1640 }
1641
1642 #[test]
1643 fn test_addons_parse_route_info_two_segments() {
1644 let ctrl = MockAddonsController;
1646 let info = ctrl.parse_route_info("/passport/login");
1647 assert_eq!(info.controller, "passport");
1648 assert_eq!(info.action, "login");
1649 assert_eq!(info.group, "passport");
1650 assert_eq!(info.route_uri, "/passport/login");
1651 }
1652
1653 #[test]
1654 fn test_addons_parse_route_info_three_segments() {
1655 let ctrl = MockAddonsController;
1658 let info = ctrl.parse_route_info("/task/task/userClerk");
1659 assert_eq!(info.controller, "task/task");
1660 assert_eq!(info.action, "userClerk");
1661 assert_eq!(info.group, "task/task"); assert_eq!(info.route_uri, "/task/task/userClerk");
1663 }
1664
1665 #[test]
1666 fn test_addons_parse_route_info_single_segment() {
1667 let ctrl = MockAddonsController;
1669 let info = ctrl.parse_route_info("/passport");
1670 assert_eq!(info.controller, "passport");
1671 assert_eq!(info.action, "");
1672 assert_eq!(info.group, "passport");
1673 assert_eq!(info.route_uri, "/passport/");
1674 }
1675
1676 #[test]
1677 fn test_addons_parse_route_info_root() {
1678 let ctrl = MockAddonsController;
1680 let info = ctrl.parse_route_info("/");
1681 assert_eq!(info.controller, "");
1682 assert_eq!(info.action, "");
1683 assert_eq!(info.group, "");
1684 assert_eq!(info.route_uri, "/");
1685 }
1686
1687 #[test]
1688 fn test_addons_parse_route_info_empty_uri() {
1689 let ctrl = MockAddonsController;
1690 let info = ctrl.parse_route_info("");
1691 assert_eq!(info.controller, "");
1692 assert_eq!(info.route_uri, "/");
1693 }
1694
1695 #[test]
1696 fn test_addons_parse_route_info_with_query_string() {
1697 let ctrl = MockAddonsController;
1699 let info = ctrl.parse_route_info("/user/info?foo=bar&baz=1");
1700 assert_eq!(info.controller, "user");
1701 assert_eq!(info.action, "info");
1702 assert_eq!(info.route_uri, "/user/info");
1703 }
1704
1705 #[test]
1706 fn test_addons_parse_route_info_trailing_slash() {
1707 let ctrl = MockAddonsController;
1709 let info = ctrl.parse_route_info("/user/info/");
1710 assert_eq!(info.controller, "user/info");
1712 assert_eq!(info.action, "");
1713 assert_eq!(info.route_uri, "/user/info/");
1714 }
1715
1716 #[test]
1717 fn test_addons_check_login_whitelist_pass() {
1718 let ctrl = MockAddonsController;
1720 let result = ctrl.check_login("/passport/login", false);
1721 assert!(result.is_ok());
1722 }
1723
1724 #[test]
1725 fn test_addons_check_login_whitelist_pass_2() {
1726 let ctrl = MockAddonsController;
1728 let result = ctrl.check_login("/task/task/userClerk", false);
1729 assert!(result.is_ok());
1730 }
1731
1732 #[test]
1733 fn test_addons_check_login_logged_in_pass() {
1734 let ctrl = MockAddonsController;
1736 let result = ctrl.check_login("/user/info", true);
1737 assert!(result.is_ok());
1738 }
1739
1740 #[test]
1741 fn test_addons_check_login_not_logged_in_fail() {
1742 let ctrl = MockAddonsController;
1744 let result = ctrl.check_login("/user/info", false);
1745 assert!(result.is_err());
1746 assert_eq!(result.unwrap_err(), "not_login");
1747 }
1748
1749 #[test]
1750 fn test_addons_get_token_default_returns_none() {
1751 let ctrl = MockAddonsController;
1754 let result = ctrl.get_token(Some("Bearer xxx.yyy.zzz"));
1755 assert!(result.is_ok());
1756 assert_eq!(result.unwrap(), None);
1757 }
1758
1759 #[test]
1760 fn test_addons_get_token_no_authorization() {
1761 let ctrl = MockAddonsController;
1762 let result = ctrl.get_token(None);
1763 assert!(result.is_ok());
1764 assert_eq!(result.unwrap(), None);
1765 }
1766
1767 #[test]
1768 fn test_addons_get_token_empty_authorization() {
1769 let ctrl = MockAddonsController;
1770 let result = ctrl.get_token(Some(""));
1771 assert!(result.is_ok());
1772 assert_eq!(result.unwrap(), None);
1773 }
1774
1775 #[test]
1776 fn test_addons_get_token_invalid_format_returns_none() {
1777 let ctrl = MockAddonsController;
1780 let result = ctrl.get_token(Some("Bearer not.a.valid.jwt.token"));
1781 assert!(result.is_ok());
1782 assert_eq!(result.unwrap(), None);
1784 }
1785
1786 #[test]
1787 fn test_strip_bearer_prefix_uppercase() {
1788 assert_eq!(strip_bearer_prefix("Bearer abc.def.ghi"), "abc.def.ghi");
1789 }
1790
1791 #[test]
1792 fn test_strip_bearer_prefix_lowercase() {
1793 assert_eq!(strip_bearer_prefix("bearer abc.def.ghi"), "abc.def.ghi");
1794 }
1795
1796 #[test]
1797 fn test_strip_bearer_prefix_mixed_case() {
1798 assert_eq!(strip_bearer_prefix("BEARER abc.def.ghi"), "abc.def.ghi");
1799 }
1800
1801 #[test]
1802 fn test_strip_bearer_prefix_no_prefix() {
1803 assert_eq!(strip_bearer_prefix("abc.def.ghi"), "abc.def.ghi");
1805 }
1806
1807 #[test]
1808 fn test_strip_bearer_prefix_empty() {
1809 assert_eq!(strip_bearer_prefix(""), "");
1810 }
1811
1812 #[test]
1813 fn test_strip_bearer_prefix_with_extra_spaces() {
1814 assert_eq!(
1815 strip_bearer_prefix(" Bearer abc.def.ghi "),
1816 "abc.def.ghi"
1817 );
1818 }
1819
1820 #[test]
1825 fn test_get_token_valid_jwt_returns_user_info() {
1826 let config = JwtConfig {
1827 secret: "test-secret".to_string(),
1828 issuer: String::new(), audience: String::new(),
1830 };
1831
1832 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&config.secret);
1834 let exp = std::time::SystemTime::now()
1835 .duration_since(std::time::UNIX_EPOCH)
1836 .unwrap()
1837 .as_secs() as i64
1838 + 3600; let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp).with_user_id(12345);
1840 let token = encoder.encode(&claims).unwrap();
1841
1842 let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1843 assert!(result.is_ok());
1844 let user = result.unwrap();
1845 assert!(user.is_some());
1846 let user = user.unwrap();
1847 assert_eq!(user.user_id, 12345);
1848 assert!(user.is_login);
1849 }
1850
1851 #[test]
1853 fn test_get_token_wrong_secret_returns_none() {
1854 let config = JwtConfig {
1855 secret: "correct-secret".to_string(),
1856 issuer: String::new(),
1857 audience: String::new(),
1858 };
1859
1860 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new("wrong-secret");
1862 let exp = std::time::SystemTime::now()
1863 .duration_since(std::time::UNIX_EPOCH)
1864 .unwrap()
1865 .as_secs() as i64
1866 + 3600;
1867 let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp).with_user_id(12345);
1868 let token = encoder.encode(&claims).unwrap();
1869
1870 let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1871 assert!(result.is_ok());
1872 assert_eq!(result.unwrap(), None); }
1874
1875 #[test]
1877 fn test_get_token_expired_returns_none() {
1878 let config = JwtConfig {
1879 secret: "test-secret".to_string(),
1880 issuer: String::new(),
1881 audience: String::new(),
1882 };
1883
1884 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&config.secret);
1885 let exp = std::time::SystemTime::now()
1887 .duration_since(std::time::UNIX_EPOCH)
1888 .unwrap()
1889 .as_secs() as i64
1890 - 3600;
1891 let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp).with_user_id(12345);
1892 let token = encoder.encode(&claims).unwrap();
1893
1894 let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1895 assert!(result.is_ok());
1896 assert_eq!(result.unwrap(), None); }
1898
1899 #[test]
1901 fn test_get_token_no_user_id_claim_returns_none() {
1902 let config = JwtConfig {
1903 secret: "test-secret".to_string(),
1904 issuer: String::new(),
1905 audience: String::new(),
1906 };
1907
1908 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&config.secret);
1909 let exp = std::time::SystemTime::now()
1910 .duration_since(std::time::UNIX_EPOCH)
1911 .unwrap()
1912 .as_secs() as i64
1913 + 3600;
1914 let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp);
1916 let token = encoder.encode(&claims).unwrap();
1917
1918 let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1919 assert!(result.is_ok());
1920 assert_eq!(result.unwrap(), None); }
1922
1923 #[test]
1925 fn test_get_token_iss_mismatch_returns_none() {
1926 let config = JwtConfig {
1927 secret: "test-secret".to_string(),
1928 issuer: "https://expected-issuer.com".to_string(),
1929 audience: String::new(),
1930 };
1931
1932 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&config.secret);
1933 let exp = std::time::SystemTime::now()
1934 .duration_since(std::time::UNIX_EPOCH)
1935 .unwrap()
1936 .as_secs() as i64
1937 + 3600;
1938 let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp)
1940 .with_issuer("https://wrong-issuer.com")
1941 .with_user_id(12345);
1942 let token = encoder.encode(&claims).unwrap();
1943
1944 let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1945 assert!(result.is_ok());
1946 assert_eq!(result.unwrap(), None); }
1948
1949 #[test]
1951 fn test_get_token_iss_match_returns_user_info() {
1952 let config = JwtConfig {
1953 secret: "test-secret".to_string(),
1954 issuer: "https://mall.ljclz.shop".to_string(),
1955 audience: String::new(),
1956 };
1957
1958 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&config.secret);
1959 let exp = std::time::SystemTime::now()
1960 .duration_since(std::time::UNIX_EPOCH)
1961 .unwrap()
1962 .as_secs() as i64
1963 + 3600;
1964 let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp)
1965 .with_issuer(&config.issuer)
1966 .with_user_id(67890);
1967 let token = encoder.encode(&claims).unwrap();
1968
1969 let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1970 assert!(result.is_ok());
1971 let user = result.unwrap().unwrap();
1972 assert_eq!(user.user_id, 67890);
1973 assert!(user.is_login);
1974 }
1975
1976 #[test]
1978 fn test_get_token_empty_secret_returns_none() {
1979 let config = JwtConfig::default(); let result = verify_token_with_config(Some("Bearer any.token.here"), &config);
1982 assert!(result.is_ok());
1983 assert_eq!(result.unwrap(), None); }
1985
1986 #[test]
1988 fn test_get_token_without_bearer_prefix() {
1989 let config = JwtConfig {
1990 secret: "test-secret".to_string(),
1991 issuer: String::new(),
1992 audience: String::new(),
1993 };
1994
1995 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&config.secret);
1996 let exp = std::time::SystemTime::now()
1997 .duration_since(std::time::UNIX_EPOCH)
1998 .unwrap()
1999 .as_secs() as i64
2000 + 3600;
2001 let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp).with_user_id(99999);
2002 let token = encoder.encode(&claims).unwrap();
2003
2004 let result = verify_token_with_config(Some(&token), &config);
2006 assert!(result.is_ok());
2007 let user = result.unwrap().unwrap();
2008 assert_eq!(user.user_id, 99999);
2009 }
2010
2011 struct CustomAddonsController;
2013
2014 impl SzController for CustomAddonsController {}
2015 impl BaseController for CustomAddonsController {}
2016 impl AddonsBaseController for CustomAddonsController {
2017 fn allow_all_action(&self) -> Vec<&'static str> {
2018 vec!["/custom/public", "/custom/health"]
2019 }
2020 }
2021
2022 #[test]
2023 fn test_addons_override_allow_all_action() {
2024 let ctrl = CustomAddonsController;
2025 let allow = ctrl.allow_all_action();
2026 assert_eq!(allow, vec!["/custom/public", "/custom/health"]);
2027 assert!(!allow.contains(&"/passport/login"));
2029 }
2030
2031 #[test]
2032 fn test_addons_override_check_login_uses_new_whitelist() {
2033 let ctrl = CustomAddonsController;
2034 assert!(ctrl.check_login("/custom/public", false).is_ok());
2036 assert!(ctrl.check_login("/passport/login", false).is_err());
2038 }
2039
2040 #[tokio::test]
2042 async fn test_addons_php_full_flow_whitelist() {
2043 struct PassportController;
2044 impl SzController for PassportController {}
2045 impl BaseController for PassportController {}
2046 impl AddonsBaseController for PassportController {}
2047
2048 let ctrl = PassportController;
2049
2050 let info = ctrl.parse_route_info("/passport/login");
2052 assert_eq!(info.route_uri, "/passport/login");
2053
2054 let user = ctrl.get_token(None).unwrap();
2056
2057 let is_login = user.as_ref().is_some_and(|u| u.is_login);
2059 ctrl.check_login(&info.route_uri, is_login).unwrap();
2060
2061 let resp = ctrl.render_success("登录成功", json!({"token": "fake.jwt.token"}));
2063 let body = collect_body(resp).await;
2064 assert_eq!(
2065 body,
2066 r#"{"code":1,"msg":"登录成功","data":{"token":"fake.jwt.token"}}"#
2067 );
2068 }
2069
2070 #[tokio::test]
2071 async fn test_addons_php_full_flow_not_login() {
2072 struct UserController;
2073 impl SzController for UserController {}
2074 impl BaseController for UserController {}
2075 impl AddonsBaseController for UserController {}
2076
2077 let ctrl = UserController;
2078
2079 let info = ctrl.parse_route_info("/user/info");
2081
2082 let user = ctrl.get_token(None).unwrap();
2084 let is_login = user.as_ref().is_some_and(|u| u.is_login);
2085
2086 let result = ctrl.check_login(&info.route_uri, is_login);
2088 assert!(result.is_err());
2089 assert_eq!(result.unwrap_err(), "not_login");
2090
2091 let resp = ctrl.render_error("not_login", json!({}), -1);
2093 let body = collect_body(resp).await;
2094 assert_eq!(body, r#"{"code":-1,"msg":"not_login","data":{}}"#);
2095 }
2096
2097 #[tokio::test]
2098 async fn test_addons_php_full_flow_logged_in() {
2099 struct OrderController;
2100 impl SzController for OrderController {}
2101 impl BaseController for OrderController {}
2102 impl AddonsBaseController for OrderController {
2103 fn get_token(&self, _authorization: Option<&str>) -> Result<Option<UserInfo>, String> {
2105 Ok(Some(UserInfo {
2106 user_id: 12345,
2107 is_login: true,
2108 }))
2109 }
2110 }
2111
2112 let ctrl = OrderController;
2113
2114 let info = ctrl.parse_route_info("/order/list");
2116
2117 let user = ctrl.get_token(None).unwrap();
2119 let is_login = user.as_ref().is_some_and(|u| u.is_login);
2120
2121 ctrl.check_login(&info.route_uri, is_login).unwrap();
2123
2124 let resp = ctrl.render_success(
2126 "success",
2127 json!({"list": [{"id": 1}, {"id": 2}], "total": 2}),
2128 );
2129 let body = collect_body(resp).await;
2130 assert_eq!(
2131 body,
2132 r#"{"code":1,"msg":"success","data":{"list":[{"id":1},{"id":2}],"total":2}}"#
2133 );
2134 }
2135
2136 #[test]
2138 fn test_route_info_debug_clone_eq() {
2139 let info1 = RouteInfo {
2140 controller: "user".to_string(),
2141 action: "info".to_string(),
2142 route_uri: "/user/info".to_string(),
2143 group: "user".to_string(),
2144 };
2145 let info2 = info1.clone();
2146 assert_eq!(info1, info2);
2147 let debug_str = format!("{info1:?}");
2148 assert!(debug_str.contains("RouteInfo"));
2149 assert!(debug_str.contains("user"));
2150 }
2151
2152 #[test]
2153 fn test_user_info_debug_clone_eq() {
2154 let user1 = UserInfo {
2155 user_id: 12345,
2156 is_login: true,
2157 };
2158 let user2 = user1.clone();
2159 assert_eq!(user1, user2);
2160 let debug_str = format!("{user1:?}");
2161 assert!(debug_str.contains("UserInfo"));
2162 assert!(debug_str.contains("12345"));
2163 }
2164
2165 #[test]
2167 fn test_addons_inheritance_chain_all_methods() {
2168 struct BusinessController;
2169 impl SzController for BusinessController {}
2170 impl BaseController for BusinessController {}
2171 impl AddonsBaseController for BusinessController {}
2172
2173 let ctrl = BusinessController;
2174
2175 let value = ctrl.render_json(1, "ok", json!({}));
2177 assert_eq!(value["code"], 1);
2178
2179 assert!(!ctrl.batch_validate());
2181 assert!(ctrl.middlewares().is_empty());
2182 ctrl.initialize();
2183
2184 let info = ctrl.parse_route_info("/test/action");
2186 assert_eq!(info.route_uri, "/test/action");
2187 assert!(ctrl.check_login("/passport/login", false).is_ok());
2188 assert!(ctrl.get_token(None).unwrap().is_none());
2189 }
2190
2191 #[test]
2200 fn test_p0_sec_02_empty_secret_returns_none_not_accept_token() {
2201 let config = JwtConfig {
2202 secret: String::new(), issuer: String::new(),
2204 audience: String::new(),
2205 };
2206
2207 let result = verify_token_with_config(Some("any.token.here"), &config);
2209 assert!(
2210 result.is_ok(),
2211 "空密钥不应导致 panic 或 Err(应优雅降级为 None)"
2212 );
2213 assert_eq!(
2214 result.unwrap(),
2215 None,
2216 "P0-SEC-02: 空密钥时必须返回 None,绝不能接受任何 token"
2217 );
2218 }
2219
2220 #[test]
2225 fn test_p0_sec_02_rejects_alg_none_token_with_empty_secret() {
2226 let config = JwtConfig::default(); let alg_none_token = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.\
2233 eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImhhY2tlciIsImlhdCI6MCwiZXhwIjo5OTk5OTk5OTk5fQ.";
2234
2235 let result = verify_token_with_config(Some(alg_none_token), &config);
2236 assert!(result.is_ok(), "空密钥下不应 panic");
2237 assert_eq!(
2238 result.unwrap(),
2239 None,
2240 "P0-SEC-02: 空密钥下 alg=none 伪造 token 必须被拒绝(返回 None)"
2241 );
2242 }
2243
2244 #[test]
2252 fn test_p1_sec_10_skips_audience_check_when_not_configured() {
2253 let config = JwtConfig {
2254 secret: "test-secret".to_string(),
2255 issuer: String::new(),
2256 audience: String::new(),
2257 };
2258
2259 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&config.secret);
2260 let exp = std::time::SystemTime::now()
2261 .duration_since(std::time::UNIX_EPOCH)
2262 .unwrap()
2263 .as_secs() as i64
2264 + 3600;
2265 let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp).with_user_id(42);
2266 let token = encoder.encode(&claims).unwrap();
2267
2268 let result = verify_token_with_config(Some(&token), &config);
2269 assert!(result.is_ok());
2270 assert!(
2271 result.unwrap().is_some(),
2272 "未配置 audience 时应跳过 aud 验证"
2273 );
2274 }
2275
2276 #[test]
2285 fn test_p1_sec_12_jwt_config_debug_redacts_secret() {
2286 let config = JwtConfig {
2287 secret: "super-secret-key-12345".to_string(),
2288 issuer: "https://example.com".to_string(),
2289 audience: String::new(),
2290 };
2291
2292 let debug_output = format!("{:?}", config);
2293
2294 assert!(
2296 !debug_output.contains("super-secret-key-12345"),
2297 "P1-SEC-12: JwtConfig::Debug 泄漏了 secret 字段: {debug_output}"
2298 );
2299 assert!(
2301 debug_output.contains("[REDACTED]"),
2302 "P1-SEC-12: JwtConfig::Debug 应包含 [REDACTED] 标记: {debug_output}"
2303 );
2304 assert!(
2306 debug_output.contains("https://example.com"),
2307 "issuer 字段应正常显示: {debug_output}"
2308 );
2309 }
2310}