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() {
116 if JWT_CONFIG.is_none() {
117 panic!("SZ_JWT_SECRET 环境变量未设置 — 生产环境必须通过环境变量提供 JWT 密钥");
118 }
119}
120
121fn strip_bearer_prefix(header: &str) -> &str {
130 let trimmed = header.trim();
131 if trimmed.len() >= 6 {
133 let prefix = &trimmed[..6];
134 if prefix.eq_ignore_ascii_case("bearer") {
135 return trimmed[6..].trim_start();
137 }
138 }
139 trimmed
140}
141
142fn verify_token_with_config(
157 authorization: Option<&str>,
158 config: &JwtConfig,
159) -> Result<Option<UserInfo>, String> {
160 let header_value = match authorization {
162 Some(v) if !v.is_empty() => v,
163 _ => return Ok(None),
164 };
165
166 let token = strip_bearer_prefix(header_value).trim();
168 if token.is_empty() {
169 return Ok(None);
170 }
171
172 if config.secret.is_empty() {
174 return Ok(None);
175 }
176
177 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&config.secret);
179 let claims = match encoder.decode(token) {
180 Ok(c) => c,
181 Err(_) => return Ok(None),
182 };
183
184 if !config.issuer.is_empty() {
186 match &claims.iss {
187 Some(iss) if iss == &config.issuer => { }
188 _ => return Ok(None),
189 }
190 }
191
192 let user_id = match claims.user_id {
194 Some(id) => id,
195 None => return Ok(None),
196 };
197
198 Ok(Some(UserInfo {
199 user_id,
200 is_login: true,
201 }))
202}
203
204pub trait SzController: Send + Sync {
232 fn render_json(&self, code: i32, msg: impl Into<String>, data: Value) -> Value {
245 let mut map = Map::new();
246 map.insert("code".to_string(), Value::Number(code.into()));
247 map.insert("msg".to_string(), Value::String(msg.into()));
248 map.insert("data".to_string(), data);
249 Value::Object(map)
250 }
251
252 fn render_success(&self, msg: impl Into<String>, data: Value) -> Response {
265 ApiResponse::success(data, msg).into_response()
266 }
267
268 fn render_error(&self, msg: impl Into<String>, data: Value, code: i32) -> Response {
281 ApiResponse::error_with_code(code, msg, data).into_response()
282 }
283
284 fn post_data(&self, req: Request<Body>) -> impl Future<Output = Result<Value, String>> + Send {
297 async move { fetch_post_data(req).await }
298 }
299
300 fn post_data_by_key(
308 &self,
309 req: Request<Body>,
310 key: &str,
311 ) -> impl Future<Output = Result<Option<Value>, String>> + Send {
312 async move { fetch_post_data_by_key(req, key).await }
313 }
314
315 fn get_data(&self, req: &Request<Body>) -> Value {
319 fetch_query_data(req)
320 }
321
322 fn get_data_by_key(&self, req: &Request<Body>, key: &str) -> Option<Value> {
329 fetch_query_data_by_key(req, key)
330 }
331}
332
333pub trait BaseController: SzController {
355 fn batch_validate(&self) -> bool {
360 false
361 }
362
363 fn middlewares(&self) -> Vec<String> {
368 Vec::new()
369 }
370
371 fn initialize(&self) {}
384
385 fn validate(
414 &self,
415 data: &Value,
416 rules: &[(&str, &str)],
417 messages: &[(&str, &str)],
418 ) -> Result<(), String> {
419 let mut validator = Validate::new();
421 for (name, rule) in rules {
422 validator = validator.rule(name, rule);
423 }
424
425 let mut msg_map = IndexMap::new();
427 for (key, msg) in messages {
428 msg_map.insert(key.to_string(), msg.to_string());
429 }
430 validator = validator.message(msg_map);
431
432 if self.batch_validate() {
434 validator = validator.batch(true);
435 }
436
437 match validator.check(data) {
439 Ok(()) => Ok(()),
440 Err(err) => Err(err.to_string()),
441 }
442 }
443}
444
445#[derive(Debug, Clone, PartialEq, Eq)]
449pub struct RouteInfo {
450 pub controller: String,
452 pub action: String,
454 pub route_uri: String,
456 pub group: String,
458}
459
460#[derive(Debug, Clone, PartialEq, Eq)]
464pub struct UserInfo {
465 pub user_id: i64,
467 pub is_login: bool,
469}
470
471pub trait AddonsBaseController: BaseController {
497 fn allow_all_action(&self) -> Vec<&'static str> {
502 vec!["/passport/login", "/task/task/userClerk"]
503 }
504
505 fn parse_route_info(&self, uri: &str) -> RouteInfo {
539 let path = uri.split('?').next().unwrap_or("");
540 let path = path.trim_start_matches('/');
541 let segments: Vec<&str> = if path.is_empty() {
542 Vec::new()
543 } else {
544 path.split('/').collect()
545 };
546
547 let (controller, action) = match segments.len() {
548 0 => (String::new(), String::new()),
549 1 => (segments[0].to_string(), String::new()),
550 _ => (
551 segments[..segments.len() - 1].join("/"),
552 segments[segments.len() - 1].to_string(),
553 ),
554 };
555
556 let group = controller.clone();
558
559 let route_uri = if controller.is_empty() && action.is_empty() {
560 "/".to_string()
561 } else {
562 format!("/{controller}/{action}")
563 };
564
565 RouteInfo {
566 controller,
567 action,
568 route_uri,
569 group,
570 }
571 }
572
573 fn check_login(&self, route_uri: &str, user_is_login: bool) -> Result<(), String> {
597 if self.allow_all_action().contains(&route_uri) {
598 return Ok(());
599 }
600 if user_is_login {
601 return Ok(());
602 }
603 Err("not_login".to_string())
604 }
605
606 fn get_token(&self, authorization: Option<&str>) -> Result<Option<UserInfo>, String> {
646 match JWT_CONFIG.as_ref() {
650 Some(config) => verify_token_with_config(authorization, config),
651 None => Ok(None),
652 }
653 }
654}
655
656#[cfg(test)]
657mod tests {
658 use super::*;
659 use axum::body::Body;
660 use axum::http::{Method, Request, StatusCode};
661 use http_body_util::BodyExt;
662 use serde_json::json;
663 use tower::ServiceExt;
664
665 struct MockController;
667
668 impl SzController for MockController {}
669
670 fn make_json_request(body: &str, query: Option<&str>) -> Request<Body> {
671 let uri = match query {
672 Some(q) => format!("/?{q}"),
673 None => "/".to_string(),
674 };
675 Request::builder()
676 .method(Method::POST)
677 .uri(&uri)
678 .header("content-type", "application/json")
679 .body(Body::from(body.to_string()))
680 .unwrap()
681 }
682
683 fn make_get_request(query: &str) -> Request<Body> {
684 Request::builder()
685 .method(Method::GET)
686 .uri(format!("/?{query}"))
687 .body(Body::empty())
688 .unwrap()
689 }
690
691 async fn collect_body(resp: Response) -> String {
692 let bytes = resp.into_body().collect().await.unwrap().to_bytes();
693 String::from_utf8(bytes.to_vec()).unwrap()
694 }
695
696 #[test]
701 fn test_render_json_field_order() {
702 let ctrl = MockController;
704 let value = ctrl.render_json(1, "ok", json!({"id": 1}));
705 let obj = value.as_object().unwrap();
706 let keys: Vec<&String> = obj.keys().collect();
707 assert_eq!(keys, vec!["code", "msg", "data"]);
708 }
709
710 #[test]
711 fn test_render_json_default_values() {
712 let ctrl = MockController;
714 let value = ctrl.render_json(1, "", Value::Object(Map::new()));
715 assert_eq!(value["code"], 1);
716 assert_eq!(value["msg"], "");
717 assert!(value["data"].is_object());
718 assert!(value["data"].as_object().unwrap().is_empty());
719 }
720
721 #[test]
722 fn test_render_json_custom_code() {
723 let ctrl = MockController;
724 let value = ctrl.render_json(-1, "未登录", json!({}));
725 assert_eq!(value["code"], -1);
726 assert_eq!(value["msg"], "未登录");
727 }
728
729 #[test]
730 fn test_render_json_compact_semantics() {
731 let ctrl = MockController;
734 let value = ctrl.render_json(0, "失败", json!({"field": "name"}));
735 let json_str = value.to_string();
736 assert_eq!(
738 json_str,
739 r#"{"code":0,"msg":"失败","data":{"field":"name"}}"#
740 );
741 }
742
743 #[test]
744 fn test_render_json_returns_value_not_response() {
745 let ctrl = MockController;
747 let value = ctrl.render_json(1, "ok", json!({}));
748 assert!(value.is_object());
750 }
751
752 #[test]
757 fn test_render_success_returns_response() {
758 let ctrl = MockController;
759 let resp = ctrl.render_success("success", json!({"id": 1}));
760 assert_eq!(resp.status(), StatusCode::OK);
761 assert_eq!(
762 resp.headers().get("content-type").unwrap(),
763 "application/json; charset=utf-8"
764 );
765 }
766
767 #[tokio::test]
768 async fn test_render_success_body_format() {
769 let ctrl = MockController;
771 let resp = ctrl.render_success("success", json!({"id": 1, "name": "alice"}));
772 let body = collect_body(resp).await;
773 assert_eq!(
774 body,
775 r#"{"code":1,"msg":"success","data":{"id":1,"name":"alice"}}"#
776 );
777 }
778
779 #[tokio::test]
780 async fn test_render_success_default_msg() {
781 let ctrl = MockController;
783 let resp = ctrl.render_success("success", json!({}));
784 let body = collect_body(resp).await;
785 assert_eq!(body, r#"{"code":1,"msg":"success","data":{}}"#);
786 }
787
788 #[tokio::test]
789 async fn test_render_success_via_axum_router() {
790 struct UserController;
792 impl SzController for UserController {}
793
794 async fn handler() -> Response {
795 let ctrl = UserController;
796 ctrl.render_success("ok", json!({"id": 1}))
797 }
798
799 let router = axum::Router::new().route("/", axum::routing::get(handler));
800 let req = Request::builder()
801 .method(Method::GET)
802 .uri("/")
803 .body(Body::empty())
804 .unwrap();
805 let resp = router.oneshot(req).await.unwrap();
806
807 assert_eq!(resp.status(), StatusCode::OK);
808 let body = collect_body(resp).await;
809 assert_eq!(body, r#"{"code":1,"msg":"ok","data":{"id":1}}"#);
810 }
811
812 #[test]
817 fn test_render_error_returns_response() {
818 let ctrl = MockController;
819 let resp = ctrl.render_error("error", json!({}), 0);
820 assert_eq!(resp.status(), StatusCode::OK); }
822
823 #[tokio::test]
824 async fn test_render_error_default_code() {
825 let ctrl = MockController;
827 let resp = ctrl.render_error("参数错误", json!({}), 0);
828 let body = collect_body(resp).await;
829 assert_eq!(body, r#"{"code":0,"msg":"参数错误","data":{}}"#);
830 }
831
832 #[tokio::test]
833 async fn test_render_error_custom_code() {
834 let ctrl = MockController;
836 let resp = ctrl.render_error("not_login", json!({}), -1);
837 let body = collect_body(resp).await;
838 assert_eq!(body, r#"{"code":-1,"msg":"not_login","data":{}}"#);
839 }
840
841 #[tokio::test]
842 async fn test_render_error_with_data() {
843 let ctrl = MockController;
844 let resp = ctrl.render_error("失败", json!({"field": "name"}), 0);
845 let body = collect_body(resp).await;
846 assert_eq!(body, r#"{"code":0,"msg":"失败","data":{"field":"name"}}"#);
847 }
848
849 #[tokio::test]
850 async fn test_render_error_via_axum_router() {
851 struct UserController;
852 impl SzController for UserController {}
853
854 async fn handler() -> Response {
855 let ctrl = UserController;
856 ctrl.render_error("参数错误", json!({}), 0)
857 }
858
859 let router = axum::Router::new().route("/", axum::routing::post(handler));
860 let req = Request::builder()
861 .method(Method::POST)
862 .uri("/")
863 .body(Body::empty())
864 .unwrap();
865 let resp = router.oneshot(req).await.unwrap();
866
867 assert_eq!(resp.status(), StatusCode::OK);
868 let body = collect_body(resp).await;
869 assert_eq!(body, r#"{"code":0,"msg":"参数错误","data":{}}"#);
870 }
871
872 #[tokio::test]
877 async fn test_post_data_json_body() {
878 let ctrl = MockController;
879 let req = make_json_request(r#"{"name":"alice","age":30}"#, None);
880 let data = ctrl.post_data(req).await.unwrap();
881 assert_eq!(data["name"], "alice");
882 assert_eq!(data["age"], 30);
883 }
884
885 #[tokio::test]
886 async fn test_post_data_query_only() {
887 let ctrl = MockController;
888 let req = make_json_request("", Some("page=1&size=10"));
889 let data = ctrl.post_data(req).await.unwrap();
890 assert_eq!(data["page"], "1");
891 assert_eq!(data["size"], "10");
892 }
893
894 #[tokio::test]
895 async fn test_post_data_body_overrides_query() {
896 let ctrl = MockController;
898 let req = make_json_request(r#"{"page":99}"#, Some("page=1&size=10"));
899 let data = ctrl.post_data(req).await.unwrap();
900 assert_eq!(data["page"], 99);
901 assert_eq!(data["size"], "10");
902 }
903
904 #[tokio::test]
905 async fn test_post_data_by_key_exists() {
906 let ctrl = MockController;
907 let req = make_json_request(r#"{"name":"alice","age":30}"#, None);
908 let name = ctrl.post_data_by_key(req, "name").await.unwrap();
909 assert_eq!(name, Some(json!("alice")));
910 }
911
912 #[tokio::test]
913 async fn test_post_data_by_key_missing() {
914 let ctrl = MockController;
915 let req = make_json_request(r#"{"name":"alice"}"#, None);
916 let age = ctrl.post_data_by_key(req, "age").await.unwrap();
917 assert_eq!(age, None);
918 }
919
920 #[test]
925 fn test_get_data_query() {
926 let ctrl = MockController;
927 let req = make_get_request("page=1&size=10");
928 let data = ctrl.get_data(&req);
929 assert_eq!(data["page"], "1");
930 assert_eq!(data["size"], "10");
931 }
932
933 #[test]
934 fn test_get_data_empty_query() {
935 let ctrl = MockController;
936 let req = make_get_request("");
937 let data = ctrl.get_data(&req);
938 assert!(data.as_object().unwrap().is_empty());
939 }
940
941 #[test]
942 fn test_get_data_by_key_exists() {
943 let ctrl = MockController;
944 let req = make_get_request("page=1&size=10");
945 let page = ctrl.get_data_by_key(&req, "page");
946 assert_eq!(page, Some(json!("1")));
947 }
948
949 #[test]
950 fn test_get_data_by_key_missing() {
951 let ctrl = MockController;
952 let req = make_get_request("page=1");
953 let size = ctrl.get_data_by_key(&req, "size");
954 assert_eq!(size, None);
955 }
956
957 #[tokio::test]
962 async fn test_multiple_controllers_independent() {
963 struct CtrlA;
965 struct CtrlB;
966 impl SzController for CtrlA {}
967 impl SzController for CtrlB {}
968
969 let a = CtrlA;
970 let b = CtrlB;
971
972 let req_a = make_json_request(r#"{"k":"a"}"#, None);
973 let req_b = make_json_request(r#"{"k":"b"}"#, None);
974
975 let data_a = a.post_data(req_a).await.unwrap();
976 let data_b = b.post_data(req_b).await.unwrap();
977
978 assert_eq!(data_a["k"], "a");
979 assert_eq!(data_b["k"], "b");
980
981 let va = a.render_json(1, "a", json!({}));
983 let vb = b.render_json(0, "b", json!({}));
984 assert_eq!(va["code"], 1);
985 assert_eq!(va["msg"], "a");
986 assert_eq!(vb["code"], 0);
987 assert_eq!(vb["msg"], "b");
988 }
989
990 #[tokio::test]
995 async fn test_php_consistency_full_flow() {
996 struct OrderController;
998 impl SzController for OrderController {}
999
1000 let ctrl = OrderController;
1001
1002 let req = make_json_request(r#"{"order_id":12345,"amount":99.5}"#, None);
1004 let data = ctrl.post_data(req).await.unwrap();
1005 let order_id = data["order_id"].as_i64().unwrap();
1006 let amount = data["amount"].as_f64().unwrap();
1007
1008 let result = json!({
1010 "order_id": order_id,
1011 "amount": amount,
1012 "status": "paid"
1013 });
1014
1015 let resp = ctrl.render_success("支付成功", result);
1017 let body = collect_body(resp).await;
1018
1019 assert_eq!(
1021 body,
1022 r#"{"code":1,"msg":"支付成功","data":{"order_id":12345,"amount":99.5,"status":"paid"}}"#
1023 );
1024 }
1025
1026 #[tokio::test]
1027 async fn test_php_consistency_error_flow() {
1028 struct UserController;
1030 impl SzController for UserController {}
1031
1032 let ctrl = UserController;
1033
1034 let req = make_json_request(r#"{"name":""}"#, None);
1035 let data = ctrl.post_data(req).await.unwrap();
1036 let name = data["name"].as_str().unwrap();
1037
1038 if name.is_empty() {
1039 let resp = ctrl.render_error("用户名不能为空", json!({"field": "name"}), 0);
1040 let body = collect_body(resp).await;
1041 assert_eq!(
1042 body,
1043 r#"{"code":0,"msg":"用户名不能为空","data":{"field":"name"}}"#
1044 );
1045 } else {
1046 panic!("should be empty");
1047 }
1048 }
1049
1050 #[tokio::test]
1051 async fn test_php_consistency_not_login_flow() {
1052 struct PassportController;
1054 impl SzController for PassportController {}
1055
1056 let ctrl = PassportController;
1057 let resp = ctrl.render_error("not_login", json!({}), -1);
1058 let body = collect_body(resp).await;
1059 assert_eq!(body, r#"{"code":-1,"msg":"not_login","data":{}}"#);
1060 }
1061
1062 struct MockBaseController;
1068
1069 impl SzController for MockBaseController {}
1070 impl BaseController for MockBaseController {}
1071
1072 #[test]
1073 fn test_base_controller_default_batch_validate() {
1074 let ctrl = MockBaseController;
1076 assert!(!ctrl.batch_validate());
1077 }
1078
1079 #[test]
1080 fn test_base_controller_default_middlewares_empty() {
1081 let ctrl = MockBaseController;
1083 assert!(ctrl.middlewares().is_empty());
1084 }
1085
1086 #[test]
1087 fn test_base_controller_default_initialize_no_panic() {
1088 let ctrl = MockBaseController;
1090 ctrl.initialize(); }
1092
1093 #[test]
1094 fn test_base_controller_default_validate_returns_ok() {
1095 let ctrl = MockBaseController;
1097 let data = json!({"name": "alice"});
1098 let rules = [("name", "require")];
1099 let messages: [(&str, &str); 0] = [];
1100 let result = ctrl.validate(&data, &rules, &messages);
1101 assert!(result.is_ok());
1102 }
1103
1104 #[test]
1105 fn test_validate_require_pass_with_value() {
1106 let ctrl = MockBaseController;
1108 let data = json!({"name": "alice", "age": 30});
1109 let rules = [("name", "require"), ("age", "require|integer")];
1110 let messages: [(&str, &str); 0] = [];
1111 assert!(ctrl.validate(&data, &rules, &messages).is_ok());
1112 }
1113
1114 #[test]
1115 fn test_validate_require_fail_when_missing() {
1116 let ctrl = MockBaseController;
1118 let data = json!({"name": "alice"});
1119 let rules = [("name", "require"), ("age", "require|integer")];
1120 let messages: [(&str, &str); 0] = [];
1121 let result = ctrl.validate(&data, &rules, &messages);
1122 assert!(result.is_err());
1123 let err = result.unwrap_err();
1124 assert!(err.contains("age"), "error: {err}");
1126 }
1127
1128 #[test]
1129 fn test_validate_integer_fail_on_string() {
1130 let ctrl = MockBaseController;
1132 let data = json!({"age": "not-a-number"});
1133 let rules = [("age", "require|integer")];
1134 let messages: [(&str, &str); 0] = [];
1135 let result = ctrl.validate(&data, &rules, &messages);
1136 assert!(result.is_err());
1137 }
1138
1139 #[test]
1140 fn test_validate_custom_message_applied() {
1141 let ctrl = MockBaseController;
1143 let data = json!({}); let rules = [("name", "require")];
1145 let messages = [("name.require", "名称必填")];
1146 let result = ctrl.validate(&data, &rules, &messages);
1147 assert!(result.is_err());
1148 assert_eq!(result.unwrap_err(), "名称必填");
1149 }
1150
1151 #[test]
1152 fn test_validate_batch_mode_returns_multiple_errors() {
1153 struct BatchController;
1155 impl SzController for BatchController {}
1156 impl BaseController for BatchController {
1157 fn batch_validate(&self) -> bool {
1158 true
1159 }
1160 }
1161
1162 let ctrl = BatchController;
1163 let data = json!({}); let rules = [("name", "require"), ("age", "require")];
1165 let messages = [("name.require", "名称必填"), ("age.require", "年龄必填")];
1166 let result = ctrl.validate(&data, &rules, &messages);
1167 assert!(result.is_err());
1168 let err = result.unwrap_err();
1169 assert!(err.contains("名称必填"), "err: {err}");
1171 assert!(err.contains("年龄必填"), "err: {err}");
1172 assert!(err.contains("; "), "err: {err}");
1174 }
1175
1176 #[test]
1177 fn test_validate_single_mode_returns_first_error_only() {
1178 let ctrl = MockBaseController;
1180 let data = json!({}); let rules = [("name", "require"), ("age", "require")];
1182 let messages = [("name.require", "名称必填"), ("age.require", "年龄必填")];
1183 let result = ctrl.validate(&data, &rules, &messages);
1184 assert!(result.is_err());
1185 let err = result.unwrap_err();
1186 assert!(err.contains("名称必填"), "err: {err}");
1188 assert!(!err.contains("年龄必填"), "err: {err}");
1189 }
1190
1191 #[test]
1192 fn test_validate_in_rule_pass() {
1193 let ctrl = MockBaseController;
1195 let data = json!({"status": "active"});
1196 let rules = [("status", "require|in:active,inactive")];
1197 let messages: [(&str, &str); 0] = [];
1198 assert!(ctrl.validate(&data, &rules, &messages).is_ok());
1199 }
1200
1201 #[test]
1202 fn test_validate_in_rule_fail() {
1203 let ctrl = MockBaseController;
1205 let data = json!({"status": "deleted"});
1206 let rules = [("status", "require|in:active,inactive")];
1207 let messages: [(&str, &str); 0] = [];
1208 assert!(ctrl.validate(&data, &rules, &messages).is_err());
1209 }
1210
1211 #[test]
1212 fn test_validate_empty_rules_always_pass() {
1213 let ctrl = MockBaseController;
1215 let data = json!({"anything": "value"});
1216 let rules: [(&str, &str); 0] = [];
1217 let messages: [(&str, &str); 0] = [];
1218 assert!(ctrl.validate(&data, &rules, &messages).is_ok());
1219 }
1220
1221 #[test]
1222 fn test_base_controller_inherits_sz_controller_methods() {
1223 let ctrl = MockBaseController;
1225 let value = ctrl.render_json(1, "ok", json!({}));
1226 assert_eq!(value["code"], 1);
1227 assert_eq!(value["msg"], "ok");
1228
1229 let resp = ctrl.render_success("ok", json!({"id": 1}));
1230 assert_eq!(resp.status(), StatusCode::OK);
1231 }
1232
1233 struct CustomController;
1235
1236 impl SzController for CustomController {}
1237 impl BaseController for CustomController {
1238 fn batch_validate(&self) -> bool {
1239 true
1240 }
1241
1242 fn middlewares(&self) -> Vec<String> {
1243 vec!["auth".to_string(), "cors".to_string()]
1244 }
1245
1246 fn initialize(&self) {
1247 }
1249 }
1250
1251 #[test]
1252 fn test_base_controller_override_batch_validate() {
1253 let ctrl = CustomController;
1254 assert!(ctrl.batch_validate());
1255 }
1256
1257 #[test]
1258 fn test_base_controller_override_middlewares() {
1259 let ctrl = CustomController;
1260 let mws = ctrl.middlewares();
1261 assert_eq!(mws, vec!["auth", "cors"]);
1262 }
1263
1264 #[test]
1265 fn test_base_controller_override_initialize() {
1266 let ctrl = CustomController;
1267 ctrl.initialize(); }
1269
1270 struct StatefulController {
1273 initialized: parking_lot::Mutex<bool>,
1274 custom_batch: bool,
1275 }
1276
1277 impl StatefulController {
1278 fn new() -> Self {
1279 Self {
1280 initialized: parking_lot::Mutex::new(false),
1281 custom_batch: false,
1282 }
1283 }
1284 }
1285
1286 impl SzController for StatefulController {}
1287 impl BaseController for StatefulController {
1288 fn batch_validate(&self) -> bool {
1289 self.custom_batch
1290 }
1291
1292 fn initialize(&self) {
1293 *self.initialized.lock() = true;
1294 }
1297 }
1298
1299 #[test]
1300 fn test_base_controller_stateful_initialize() {
1301 let ctrl = StatefulController::new();
1302 assert!(!*ctrl.initialized.lock()); ctrl.initialize(); assert!(*ctrl.initialized.lock()); }
1306
1307 #[tokio::test]
1309 async fn test_base_controller_php_full_flow() {
1310 struct UserController;
1311 impl SzController for UserController {}
1312 impl BaseController for UserController {}
1313
1314 let ctrl = UserController;
1315
1316 ctrl.initialize();
1318
1319 let req = make_json_request(r#"{"name":"alice","age":30}"#, None);
1321 let data = ctrl.post_data(req).await.unwrap();
1322
1323 let rules = [("name", "require"), ("age", "require|integer|gt:0")];
1325 let messages: [(&str, &str); 0] = [];
1326 ctrl.validate(&data, &rules, &messages).unwrap();
1327
1328 let resp =
1330 ctrl.render_success("success", json!({"name": data["name"], "age": data["age"]}));
1331 let body = collect_body(resp).await;
1332 assert_eq!(
1333 body,
1334 r#"{"code":1,"msg":"success","data":{"name":"alice","age":30}}"#
1335 );
1336 }
1337
1338 #[test]
1340 fn test_base_controller_multiple_instances_independent() {
1341 struct ControllerA;
1342 struct ControllerB;
1343
1344 impl SzController for ControllerA {}
1345 impl BaseController for ControllerA {
1346 fn middlewares(&self) -> Vec<String> {
1347 vec!["auth".to_string()]
1348 }
1349 }
1350
1351 impl SzController for ControllerB {}
1352 impl BaseController for ControllerB {
1353 fn middlewares(&self) -> Vec<String> {
1354 vec!["cors".to_string(), "log".to_string()]
1355 }
1356 }
1357
1358 let a = ControllerA;
1359 let b = ControllerB;
1360
1361 assert_eq!(a.middlewares(), vec!["auth"]);
1362 assert_eq!(b.middlewares(), vec!["cors", "log"]);
1363
1364 assert!(!a.batch_validate());
1366 assert!(!b.batch_validate());
1367 }
1368
1369 #[test]
1371 fn test_base_controller_inheritance_chain() {
1372 struct BusinessController;
1374 impl SzController for BusinessController {}
1375 impl BaseController for BusinessController {}
1376
1377 let ctrl = BusinessController;
1378
1379 let value = ctrl.render_json(0, "error", json!({}));
1381 assert!(value.is_object());
1382
1383 assert!(!ctrl.batch_validate());
1385 assert!(ctrl.middlewares().is_empty());
1386 ctrl.initialize();
1387 }
1388
1389 struct MockAddonsController;
1395
1396 impl SzController for MockAddonsController {}
1397 impl BaseController for MockAddonsController {}
1398 impl AddonsBaseController for MockAddonsController {}
1399
1400 #[test]
1401 fn test_addons_default_allow_all_action() {
1402 let ctrl = MockAddonsController;
1404 let allow = ctrl.allow_all_action();
1405 assert!(allow.contains(&"/passport/login"));
1406 assert!(allow.contains(&"/task/task/userClerk"));
1407 assert_eq!(allow.len(), 2);
1408 }
1409
1410 #[test]
1411 fn test_addons_parse_route_info_two_segments() {
1412 let ctrl = MockAddonsController;
1414 let info = ctrl.parse_route_info("/passport/login");
1415 assert_eq!(info.controller, "passport");
1416 assert_eq!(info.action, "login");
1417 assert_eq!(info.group, "passport");
1418 assert_eq!(info.route_uri, "/passport/login");
1419 }
1420
1421 #[test]
1422 fn test_addons_parse_route_info_three_segments() {
1423 let ctrl = MockAddonsController;
1426 let info = ctrl.parse_route_info("/task/task/userClerk");
1427 assert_eq!(info.controller, "task/task");
1428 assert_eq!(info.action, "userClerk");
1429 assert_eq!(info.group, "task/task"); assert_eq!(info.route_uri, "/task/task/userClerk");
1431 }
1432
1433 #[test]
1434 fn test_addons_parse_route_info_single_segment() {
1435 let ctrl = MockAddonsController;
1437 let info = ctrl.parse_route_info("/passport");
1438 assert_eq!(info.controller, "passport");
1439 assert_eq!(info.action, "");
1440 assert_eq!(info.group, "passport");
1441 assert_eq!(info.route_uri, "/passport/");
1442 }
1443
1444 #[test]
1445 fn test_addons_parse_route_info_root() {
1446 let ctrl = MockAddonsController;
1448 let info = ctrl.parse_route_info("/");
1449 assert_eq!(info.controller, "");
1450 assert_eq!(info.action, "");
1451 assert_eq!(info.group, "");
1452 assert_eq!(info.route_uri, "/");
1453 }
1454
1455 #[test]
1456 fn test_addons_parse_route_info_empty_uri() {
1457 let ctrl = MockAddonsController;
1458 let info = ctrl.parse_route_info("");
1459 assert_eq!(info.controller, "");
1460 assert_eq!(info.route_uri, "/");
1461 }
1462
1463 #[test]
1464 fn test_addons_parse_route_info_with_query_string() {
1465 let ctrl = MockAddonsController;
1467 let info = ctrl.parse_route_info("/user/info?foo=bar&baz=1");
1468 assert_eq!(info.controller, "user");
1469 assert_eq!(info.action, "info");
1470 assert_eq!(info.route_uri, "/user/info");
1471 }
1472
1473 #[test]
1474 fn test_addons_parse_route_info_trailing_slash() {
1475 let ctrl = MockAddonsController;
1477 let info = ctrl.parse_route_info("/user/info/");
1478 assert_eq!(info.controller, "user/info");
1480 assert_eq!(info.action, "");
1481 assert_eq!(info.route_uri, "/user/info/");
1482 }
1483
1484 #[test]
1485 fn test_addons_check_login_whitelist_pass() {
1486 let ctrl = MockAddonsController;
1488 let result = ctrl.check_login("/passport/login", false);
1489 assert!(result.is_ok());
1490 }
1491
1492 #[test]
1493 fn test_addons_check_login_whitelist_pass_2() {
1494 let ctrl = MockAddonsController;
1496 let result = ctrl.check_login("/task/task/userClerk", false);
1497 assert!(result.is_ok());
1498 }
1499
1500 #[test]
1501 fn test_addons_check_login_logged_in_pass() {
1502 let ctrl = MockAddonsController;
1504 let result = ctrl.check_login("/user/info", true);
1505 assert!(result.is_ok());
1506 }
1507
1508 #[test]
1509 fn test_addons_check_login_not_logged_in_fail() {
1510 let ctrl = MockAddonsController;
1512 let result = ctrl.check_login("/user/info", false);
1513 assert!(result.is_err());
1514 assert_eq!(result.unwrap_err(), "not_login");
1515 }
1516
1517 #[test]
1518 fn test_addons_get_token_default_returns_none() {
1519 let ctrl = MockAddonsController;
1522 let result = ctrl.get_token(Some("Bearer xxx.yyy.zzz"));
1523 assert!(result.is_ok());
1524 assert_eq!(result.unwrap(), None);
1525 }
1526
1527 #[test]
1528 fn test_addons_get_token_no_authorization() {
1529 let ctrl = MockAddonsController;
1530 let result = ctrl.get_token(None);
1531 assert!(result.is_ok());
1532 assert_eq!(result.unwrap(), None);
1533 }
1534
1535 #[test]
1536 fn test_addons_get_token_empty_authorization() {
1537 let ctrl = MockAddonsController;
1538 let result = ctrl.get_token(Some(""));
1539 assert!(result.is_ok());
1540 assert_eq!(result.unwrap(), None);
1541 }
1542
1543 #[test]
1544 fn test_addons_get_token_invalid_format_returns_none() {
1545 let ctrl = MockAddonsController;
1548 let result = ctrl.get_token(Some("Bearer not.a.valid.jwt.token"));
1549 assert!(result.is_ok());
1550 assert_eq!(result.unwrap(), None);
1552 }
1553
1554 #[test]
1555 fn test_strip_bearer_prefix_uppercase() {
1556 assert_eq!(strip_bearer_prefix("Bearer abc.def.ghi"), "abc.def.ghi");
1557 }
1558
1559 #[test]
1560 fn test_strip_bearer_prefix_lowercase() {
1561 assert_eq!(strip_bearer_prefix("bearer abc.def.ghi"), "abc.def.ghi");
1562 }
1563
1564 #[test]
1565 fn test_strip_bearer_prefix_mixed_case() {
1566 assert_eq!(strip_bearer_prefix("BEARER abc.def.ghi"), "abc.def.ghi");
1567 }
1568
1569 #[test]
1570 fn test_strip_bearer_prefix_no_prefix() {
1571 assert_eq!(strip_bearer_prefix("abc.def.ghi"), "abc.def.ghi");
1573 }
1574
1575 #[test]
1576 fn test_strip_bearer_prefix_empty() {
1577 assert_eq!(strip_bearer_prefix(""), "");
1578 }
1579
1580 #[test]
1581 fn test_strip_bearer_prefix_with_extra_spaces() {
1582 assert_eq!(
1583 strip_bearer_prefix(" Bearer abc.def.ghi "),
1584 "abc.def.ghi"
1585 );
1586 }
1587
1588 #[test]
1593 fn test_get_token_valid_jwt_returns_user_info() {
1594 let config = JwtConfig {
1595 secret: "test-secret".to_string(),
1596 issuer: String::new(), audience: String::new(),
1598 };
1599
1600 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&config.secret);
1602 let exp = std::time::SystemTime::now()
1603 .duration_since(std::time::UNIX_EPOCH)
1604 .unwrap()
1605 .as_secs() as i64
1606 + 3600; let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp).with_user_id(12345);
1608 let token = encoder.encode(&claims).unwrap();
1609
1610 let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1611 assert!(result.is_ok());
1612 let user = result.unwrap();
1613 assert!(user.is_some());
1614 let user = user.unwrap();
1615 assert_eq!(user.user_id, 12345);
1616 assert!(user.is_login);
1617 }
1618
1619 #[test]
1621 fn test_get_token_wrong_secret_returns_none() {
1622 let config = JwtConfig {
1623 secret: "correct-secret".to_string(),
1624 issuer: String::new(),
1625 audience: String::new(),
1626 };
1627
1628 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new("wrong-secret");
1630 let exp = std::time::SystemTime::now()
1631 .duration_since(std::time::UNIX_EPOCH)
1632 .unwrap()
1633 .as_secs() as i64
1634 + 3600;
1635 let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp).with_user_id(12345);
1636 let token = encoder.encode(&claims).unwrap();
1637
1638 let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1639 assert!(result.is_ok());
1640 assert_eq!(result.unwrap(), None); }
1642
1643 #[test]
1645 fn test_get_token_expired_returns_none() {
1646 let config = JwtConfig {
1647 secret: "test-secret".to_string(),
1648 issuer: String::new(),
1649 audience: String::new(),
1650 };
1651
1652 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&config.secret);
1653 let exp = std::time::SystemTime::now()
1655 .duration_since(std::time::UNIX_EPOCH)
1656 .unwrap()
1657 .as_secs() as i64
1658 - 3600;
1659 let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp).with_user_id(12345);
1660 let token = encoder.encode(&claims).unwrap();
1661
1662 let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1663 assert!(result.is_ok());
1664 assert_eq!(result.unwrap(), None); }
1666
1667 #[test]
1669 fn test_get_token_no_user_id_claim_returns_none() {
1670 let config = JwtConfig {
1671 secret: "test-secret".to_string(),
1672 issuer: String::new(),
1673 audience: String::new(),
1674 };
1675
1676 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&config.secret);
1677 let exp = std::time::SystemTime::now()
1678 .duration_since(std::time::UNIX_EPOCH)
1679 .unwrap()
1680 .as_secs() as i64
1681 + 3600;
1682 let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp);
1684 let token = encoder.encode(&claims).unwrap();
1685
1686 let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1687 assert!(result.is_ok());
1688 assert_eq!(result.unwrap(), None); }
1690
1691 #[test]
1693 fn test_get_token_iss_mismatch_returns_none() {
1694 let config = JwtConfig {
1695 secret: "test-secret".to_string(),
1696 issuer: "https://expected-issuer.com".to_string(),
1697 audience: String::new(),
1698 };
1699
1700 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&config.secret);
1701 let exp = std::time::SystemTime::now()
1702 .duration_since(std::time::UNIX_EPOCH)
1703 .unwrap()
1704 .as_secs() as i64
1705 + 3600;
1706 let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp)
1708 .with_issuer("https://wrong-issuer.com")
1709 .with_user_id(12345);
1710 let token = encoder.encode(&claims).unwrap();
1711
1712 let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1713 assert!(result.is_ok());
1714 assert_eq!(result.unwrap(), None); }
1716
1717 #[test]
1719 fn test_get_token_iss_match_returns_user_info() {
1720 let config = JwtConfig {
1721 secret: "test-secret".to_string(),
1722 issuer: "https://mall.ljclz.shop".to_string(),
1723 audience: String::new(),
1724 };
1725
1726 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&config.secret);
1727 let exp = std::time::SystemTime::now()
1728 .duration_since(std::time::UNIX_EPOCH)
1729 .unwrap()
1730 .as_secs() as i64
1731 + 3600;
1732 let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp)
1733 .with_issuer(&config.issuer)
1734 .with_user_id(67890);
1735 let token = encoder.encode(&claims).unwrap();
1736
1737 let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1738 assert!(result.is_ok());
1739 let user = result.unwrap().unwrap();
1740 assert_eq!(user.user_id, 67890);
1741 assert!(user.is_login);
1742 }
1743
1744 #[test]
1746 fn test_get_token_empty_secret_returns_none() {
1747 let config = JwtConfig::default(); let result = verify_token_with_config(Some("Bearer any.token.here"), &config);
1750 assert!(result.is_ok());
1751 assert_eq!(result.unwrap(), None); }
1753
1754 #[test]
1756 fn test_get_token_without_bearer_prefix() {
1757 let config = JwtConfig {
1758 secret: "test-secret".to_string(),
1759 issuer: String::new(),
1760 audience: String::new(),
1761 };
1762
1763 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&config.secret);
1764 let exp = std::time::SystemTime::now()
1765 .duration_since(std::time::UNIX_EPOCH)
1766 .unwrap()
1767 .as_secs() as i64
1768 + 3600;
1769 let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp).with_user_id(99999);
1770 let token = encoder.encode(&claims).unwrap();
1771
1772 let result = verify_token_with_config(Some(&token), &config);
1774 assert!(result.is_ok());
1775 let user = result.unwrap().unwrap();
1776 assert_eq!(user.user_id, 99999);
1777 }
1778
1779 struct CustomAddonsController;
1781
1782 impl SzController for CustomAddonsController {}
1783 impl BaseController for CustomAddonsController {}
1784 impl AddonsBaseController for CustomAddonsController {
1785 fn allow_all_action(&self) -> Vec<&'static str> {
1786 vec!["/custom/public", "/custom/health"]
1787 }
1788 }
1789
1790 #[test]
1791 fn test_addons_override_allow_all_action() {
1792 let ctrl = CustomAddonsController;
1793 let allow = ctrl.allow_all_action();
1794 assert_eq!(allow, vec!["/custom/public", "/custom/health"]);
1795 assert!(!allow.contains(&"/passport/login"));
1797 }
1798
1799 #[test]
1800 fn test_addons_override_check_login_uses_new_whitelist() {
1801 let ctrl = CustomAddonsController;
1802 assert!(ctrl.check_login("/custom/public", false).is_ok());
1804 assert!(ctrl.check_login("/passport/login", false).is_err());
1806 }
1807
1808 #[tokio::test]
1810 async fn test_addons_php_full_flow_whitelist() {
1811 struct PassportController;
1812 impl SzController for PassportController {}
1813 impl BaseController for PassportController {}
1814 impl AddonsBaseController for PassportController {}
1815
1816 let ctrl = PassportController;
1817
1818 let info = ctrl.parse_route_info("/passport/login");
1820 assert_eq!(info.route_uri, "/passport/login");
1821
1822 let user = ctrl.get_token(None).unwrap();
1824
1825 let is_login = user.as_ref().is_some_and(|u| u.is_login);
1827 ctrl.check_login(&info.route_uri, is_login).unwrap();
1828
1829 let resp = ctrl.render_success("登录成功", json!({"token": "fake.jwt.token"}));
1831 let body = collect_body(resp).await;
1832 assert_eq!(
1833 body,
1834 r#"{"code":1,"msg":"登录成功","data":{"token":"fake.jwt.token"}}"#
1835 );
1836 }
1837
1838 #[tokio::test]
1839 async fn test_addons_php_full_flow_not_login() {
1840 struct UserController;
1841 impl SzController for UserController {}
1842 impl BaseController for UserController {}
1843 impl AddonsBaseController for UserController {}
1844
1845 let ctrl = UserController;
1846
1847 let info = ctrl.parse_route_info("/user/info");
1849
1850 let user = ctrl.get_token(None).unwrap();
1852 let is_login = user.as_ref().is_some_and(|u| u.is_login);
1853
1854 let result = ctrl.check_login(&info.route_uri, is_login);
1856 assert!(result.is_err());
1857 assert_eq!(result.unwrap_err(), "not_login");
1858
1859 let resp = ctrl.render_error("not_login", json!({}), -1);
1861 let body = collect_body(resp).await;
1862 assert_eq!(body, r#"{"code":-1,"msg":"not_login","data":{}}"#);
1863 }
1864
1865 #[tokio::test]
1866 async fn test_addons_php_full_flow_logged_in() {
1867 struct OrderController;
1868 impl SzController for OrderController {}
1869 impl BaseController for OrderController {}
1870 impl AddonsBaseController for OrderController {
1871 fn get_token(&self, _authorization: Option<&str>) -> Result<Option<UserInfo>, String> {
1873 Ok(Some(UserInfo {
1874 user_id: 12345,
1875 is_login: true,
1876 }))
1877 }
1878 }
1879
1880 let ctrl = OrderController;
1881
1882 let info = ctrl.parse_route_info("/order/list");
1884
1885 let user = ctrl.get_token(None).unwrap();
1887 let is_login = user.as_ref().is_some_and(|u| u.is_login);
1888
1889 ctrl.check_login(&info.route_uri, is_login).unwrap();
1891
1892 let resp = ctrl.render_success(
1894 "success",
1895 json!({"list": [{"id": 1}, {"id": 2}], "total": 2}),
1896 );
1897 let body = collect_body(resp).await;
1898 assert_eq!(
1899 body,
1900 r#"{"code":1,"msg":"success","data":{"list":[{"id":1},{"id":2}],"total":2}}"#
1901 );
1902 }
1903
1904 #[test]
1906 fn test_route_info_debug_clone_eq() {
1907 let info1 = RouteInfo {
1908 controller: "user".to_string(),
1909 action: "info".to_string(),
1910 route_uri: "/user/info".to_string(),
1911 group: "user".to_string(),
1912 };
1913 let info2 = info1.clone();
1914 assert_eq!(info1, info2);
1915 let debug_str = format!("{info1:?}");
1916 assert!(debug_str.contains("RouteInfo"));
1917 assert!(debug_str.contains("user"));
1918 }
1919
1920 #[test]
1921 fn test_user_info_debug_clone_eq() {
1922 let user1 = UserInfo {
1923 user_id: 12345,
1924 is_login: true,
1925 };
1926 let user2 = user1.clone();
1927 assert_eq!(user1, user2);
1928 let debug_str = format!("{user1:?}");
1929 assert!(debug_str.contains("UserInfo"));
1930 assert!(debug_str.contains("12345"));
1931 }
1932
1933 #[test]
1935 fn test_addons_inheritance_chain_all_methods() {
1936 struct BusinessController;
1937 impl SzController for BusinessController {}
1938 impl BaseController for BusinessController {}
1939 impl AddonsBaseController for BusinessController {}
1940
1941 let ctrl = BusinessController;
1942
1943 let value = ctrl.render_json(1, "ok", json!({}));
1945 assert_eq!(value["code"], 1);
1946
1947 assert!(!ctrl.batch_validate());
1949 assert!(ctrl.middlewares().is_empty());
1950 ctrl.initialize();
1951
1952 let info = ctrl.parse_route_info("/test/action");
1954 assert_eq!(info.route_uri, "/test/action");
1955 assert!(ctrl.check_login("/passport/login", false).is_ok());
1956 assert!(ctrl.get_token(None).unwrap().is_none());
1957 }
1958
1959 #[test]
1968 fn test_p0_sec_02_empty_secret_returns_none_not_accept_token() {
1969 let config = JwtConfig {
1970 secret: String::new(), issuer: String::new(),
1972 audience: String::new(),
1973 };
1974
1975 let result = verify_token_with_config(Some("any.token.here"), &config);
1977 assert!(
1978 result.is_ok(),
1979 "空密钥不应导致 panic 或 Err(应优雅降级为 None)"
1980 );
1981 assert_eq!(
1982 result.unwrap(),
1983 None,
1984 "P0-SEC-02: 空密钥时必须返回 None,绝不能接受任何 token"
1985 );
1986 }
1987
1988 #[test]
1993 fn test_p0_sec_02_rejects_alg_none_token_with_empty_secret() {
1994 let config = JwtConfig::default(); let alg_none_token = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.\
2001 eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImhhY2tlciIsImlhdCI6MCwiZXhwIjo5OTk5OTk5OTk5fQ.";
2002
2003 let result = verify_token_with_config(Some(alg_none_token), &config);
2004 assert!(result.is_ok(), "空密钥下不应 panic");
2005 assert_eq!(
2006 result.unwrap(),
2007 None,
2008 "P0-SEC-02: 空密钥下 alg=none 伪造 token 必须被拒绝(返回 None)"
2009 );
2010 }
2011
2012 #[test]
2020 fn test_p1_sec_10_skips_audience_check_when_not_configured() {
2021 let config = JwtConfig {
2022 secret: "test-secret".to_string(),
2023 issuer: String::new(),
2024 audience: String::new(),
2025 };
2026
2027 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&config.secret);
2028 let exp = std::time::SystemTime::now()
2029 .duration_since(std::time::UNIX_EPOCH)
2030 .unwrap()
2031 .as_secs() as i64
2032 + 3600;
2033 let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp).with_user_id(42);
2034 let token = encoder.encode(&claims).unwrap();
2035
2036 let result = verify_token_with_config(Some(&token), &config);
2037 assert!(result.is_ok());
2038 assert!(
2039 result.unwrap().is_some(),
2040 "未配置 audience 时应跳过 aud 验证"
2041 );
2042 }
2043
2044 #[test]
2053 fn test_p1_sec_12_jwt_config_debug_redacts_secret() {
2054 let config = JwtConfig {
2055 secret: "super-secret-key-12345".to_string(),
2056 issuer: "https://example.com".to_string(),
2057 audience: String::new(),
2058 };
2059
2060 let debug_output = format!("{:?}", config);
2061
2062 assert!(
2064 !debug_output.contains("super-secret-key-12345"),
2065 "P1-SEC-12: JwtConfig::Debug 泄漏了 secret 字段: {debug_output}"
2066 );
2067 assert!(
2069 debug_output.contains("[REDACTED]"),
2070 "P1-SEC-12: JwtConfig::Debug 应包含 [REDACTED] 标记: {debug_output}"
2071 );
2072 assert!(
2074 debug_output.contains("https://example.com"),
2075 "issuer 字段应正常显示: {debug_output}"
2076 );
2077 }
2078}