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 crate::request::{
40 fetch_post_data, fetch_post_data_by_key, fetch_query_data, fetch_query_data_by_key,
41};
42use crate::response::ApiResponse;
43use crate::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_orm_auth::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 if !config.audience.is_empty() {
194 match &claims.aud {
195 Some(aud) if aud == &config.audience => { }
196 _ => return Ok(None),
197 }
198 }
199
200 let user_id = match claims.user_id {
202 Some(id) => id,
203 None => return Ok(None),
204 };
205
206 Ok(Some(UserInfo {
207 user_id,
208 is_login: true,
209 }))
210}
211
212pub trait SzController: Send + Sync {
240 fn render_json(&self, code: i32, msg: impl Into<String>, data: Value) -> Value {
253 let mut map = Map::new();
254 map.insert("code".to_string(), Value::Number(code.into()));
255 map.insert("msg".to_string(), Value::String(msg.into()));
256 map.insert("data".to_string(), data);
257 Value::Object(map)
258 }
259
260 fn render_success(&self, msg: impl Into<String>, data: Value) -> Response {
273 ApiResponse::success(data, msg).into_response()
274 }
275
276 fn render_error(&self, msg: impl Into<String>, data: Value, code: i32) -> Response {
289 ApiResponse::error_with_code(code, msg, data).into_response()
290 }
291
292 fn post_data(&self, req: Request<Body>) -> impl Future<Output = Result<Value, String>> + Send {
305 async move { fetch_post_data(req).await }
306 }
307
308 fn post_data_by_key(
316 &self,
317 req: Request<Body>,
318 key: &str,
319 ) -> impl Future<Output = Result<Option<Value>, String>> + Send {
320 async move { fetch_post_data_by_key(req, key).await }
321 }
322
323 fn get_data(&self, req: &Request<Body>) -> Value {
327 fetch_query_data(req)
328 }
329
330 fn get_data_by_key(&self, req: &Request<Body>, key: &str) -> Option<Value> {
337 fetch_query_data_by_key(req, key)
338 }
339}
340
341pub trait BaseController: SzController {
363 fn batch_validate(&self) -> bool {
368 false
369 }
370
371 fn middlewares(&self) -> Vec<String> {
376 Vec::new()
377 }
378
379 fn initialize(&self) {}
392
393 fn validate(
422 &self,
423 data: &Value,
424 rules: &[(&str, &str)],
425 messages: &[(&str, &str)],
426 ) -> Result<(), String> {
427 let mut validator = Validate::new();
429 for (name, rule) in rules {
430 validator = validator.rule(name, rule);
431 }
432
433 let mut msg_map = IndexMap::new();
435 for (key, msg) in messages {
436 msg_map.insert(key.to_string(), msg.to_string());
437 }
438 validator = validator.message(msg_map);
439
440 if self.batch_validate() {
442 validator = validator.batch(true);
443 }
444
445 match validator.check(data) {
447 Ok(()) => Ok(()),
448 Err(err) => Err(err.to_string()),
449 }
450 }
451}
452
453#[derive(Debug, Clone, PartialEq, Eq)]
457pub struct RouteInfo {
458 pub controller: String,
460 pub action: String,
462 pub route_uri: String,
464 pub group: String,
466}
467
468#[derive(Debug, Clone, PartialEq, Eq)]
472pub struct UserInfo {
473 pub user_id: i64,
475 pub is_login: bool,
477}
478
479pub trait AddonsBaseController: BaseController {
505 fn allow_all_action(&self) -> Vec<&'static str> {
510 vec!["/passport/login", "/task/task/userClerk"]
511 }
512
513 fn parse_route_info(&self, uri: &str) -> RouteInfo {
547 let path = uri.split('?').next().unwrap_or("");
548 let path = path.trim_start_matches('/');
549 let segments: Vec<&str> = if path.is_empty() {
550 Vec::new()
551 } else {
552 path.split('/').collect()
553 };
554
555 let (controller, action) = match segments.len() {
556 0 => (String::new(), String::new()),
557 1 => (segments[0].to_string(), String::new()),
558 _ => (
559 segments[..segments.len() - 1].join("/"),
560 segments[segments.len() - 1].to_string(),
561 ),
562 };
563
564 let group = controller.clone();
566
567 let route_uri = if controller.is_empty() && action.is_empty() {
568 "/".to_string()
569 } else {
570 format!("/{controller}/{action}")
571 };
572
573 RouteInfo {
574 controller,
575 action,
576 route_uri,
577 group,
578 }
579 }
580
581 fn check_login(&self, route_uri: &str, user_is_login: bool) -> Result<(), String> {
605 if self.allow_all_action().contains(&route_uri) {
606 return Ok(());
607 }
608 if user_is_login {
609 return Ok(());
610 }
611 Err("not_login".to_string())
612 }
613
614 fn get_token(&self, authorization: Option<&str>) -> Result<Option<UserInfo>, String> {
654 match JWT_CONFIG.as_ref() {
658 Some(config) => verify_token_with_config(authorization, config),
659 None => Ok(None),
660 }
661 }
662}
663
664#[cfg(test)]
665mod tests {
666 use super::*;
667 use axum::body::Body;
668 use axum::http::{Method, Request, StatusCode};
669 use http_body_util::BodyExt;
670 use serde_json::json;
671 use tower::ServiceExt;
672
673 struct MockController;
675
676 impl SzController for MockController {}
677
678 fn make_json_request(body: &str, query: Option<&str>) -> Request<Body> {
679 let uri = match query {
680 Some(q) => format!("/?{q}"),
681 None => "/".to_string(),
682 };
683 Request::builder()
684 .method(Method::POST)
685 .uri(&uri)
686 .header("content-type", "application/json")
687 .body(Body::from(body.to_string()))
688 .unwrap()
689 }
690
691 fn make_get_request(query: &str) -> Request<Body> {
692 Request::builder()
693 .method(Method::GET)
694 .uri(format!("/?{query}"))
695 .body(Body::empty())
696 .unwrap()
697 }
698
699 async fn collect_body(resp: Response) -> String {
700 let bytes = resp.into_body().collect().await.unwrap().to_bytes();
701 String::from_utf8(bytes.to_vec()).unwrap()
702 }
703
704 #[test]
709 fn test_render_json_field_order() {
710 let ctrl = MockController;
712 let value = ctrl.render_json(1, "ok", json!({"id": 1}));
713 let obj = value.as_object().unwrap();
714 let keys: Vec<&String> = obj.keys().collect();
715 assert_eq!(keys, vec!["code", "msg", "data"]);
716 }
717
718 #[test]
719 fn test_render_json_default_values() {
720 let ctrl = MockController;
722 let value = ctrl.render_json(1, "", Value::Object(Map::new()));
723 assert_eq!(value["code"], 1);
724 assert_eq!(value["msg"], "");
725 assert!(value["data"].is_object());
726 assert!(value["data"].as_object().unwrap().is_empty());
727 }
728
729 #[test]
730 fn test_render_json_custom_code() {
731 let ctrl = MockController;
732 let value = ctrl.render_json(-1, "未登录", json!({}));
733 assert_eq!(value["code"], -1);
734 assert_eq!(value["msg"], "未登录");
735 }
736
737 #[test]
738 fn test_render_json_compact_semantics() {
739 let ctrl = MockController;
742 let value = ctrl.render_json(0, "失败", json!({"field": "name"}));
743 let json_str = value.to_string();
744 assert_eq!(
746 json_str,
747 r#"{"code":0,"msg":"失败","data":{"field":"name"}}"#
748 );
749 }
750
751 #[test]
752 fn test_render_json_returns_value_not_response() {
753 let ctrl = MockController;
755 let value = ctrl.render_json(1, "ok", json!({}));
756 assert!(value.is_object());
758 }
759
760 #[test]
765 fn test_render_success_returns_response() {
766 let ctrl = MockController;
767 let resp = ctrl.render_success("success", json!({"id": 1}));
768 assert_eq!(resp.status(), StatusCode::OK);
769 assert_eq!(
770 resp.headers().get("content-type").unwrap(),
771 "application/json; charset=utf-8"
772 );
773 }
774
775 #[tokio::test]
776 async fn test_render_success_body_format() {
777 let ctrl = MockController;
779 let resp = ctrl.render_success("success", json!({"id": 1, "name": "alice"}));
780 let body = collect_body(resp).await;
781 assert_eq!(
782 body,
783 r#"{"code":1,"msg":"success","data":{"id":1,"name":"alice"}}"#
784 );
785 }
786
787 #[tokio::test]
788 async fn test_render_success_default_msg() {
789 let ctrl = MockController;
791 let resp = ctrl.render_success("success", json!({}));
792 let body = collect_body(resp).await;
793 assert_eq!(body, r#"{"code":1,"msg":"success","data":{}}"#);
794 }
795
796 #[tokio::test]
797 async fn test_render_success_via_axum_router() {
798 struct UserController;
800 impl SzController for UserController {}
801
802 async fn handler() -> Response {
803 let ctrl = UserController;
804 ctrl.render_success("ok", json!({"id": 1}))
805 }
806
807 let router = axum::Router::new().route("/", axum::routing::get(handler));
808 let req = Request::builder()
809 .method(Method::GET)
810 .uri("/")
811 .body(Body::empty())
812 .unwrap();
813 let resp = router.oneshot(req).await.unwrap();
814
815 assert_eq!(resp.status(), StatusCode::OK);
816 let body = collect_body(resp).await;
817 assert_eq!(body, r#"{"code":1,"msg":"ok","data":{"id":1}}"#);
818 }
819
820 #[test]
825 fn test_render_error_returns_response() {
826 let ctrl = MockController;
827 let resp = ctrl.render_error("error", json!({}), 0);
828 assert_eq!(resp.status(), StatusCode::OK); }
830
831 #[tokio::test]
832 async fn test_render_error_default_code() {
833 let ctrl = MockController;
835 let resp = ctrl.render_error("参数错误", json!({}), 0);
836 let body = collect_body(resp).await;
837 assert_eq!(body, r#"{"code":0,"msg":"参数错误","data":{}}"#);
838 }
839
840 #[tokio::test]
841 async fn test_render_error_custom_code() {
842 let ctrl = MockController;
844 let resp = ctrl.render_error("not_login", json!({}), -1);
845 let body = collect_body(resp).await;
846 assert_eq!(body, r#"{"code":-1,"msg":"not_login","data":{}}"#);
847 }
848
849 #[tokio::test]
850 async fn test_render_error_with_data() {
851 let ctrl = MockController;
852 let resp = ctrl.render_error("失败", json!({"field": "name"}), 0);
853 let body = collect_body(resp).await;
854 assert_eq!(body, r#"{"code":0,"msg":"失败","data":{"field":"name"}}"#);
855 }
856
857 #[tokio::test]
858 async fn test_render_error_via_axum_router() {
859 struct UserController;
860 impl SzController for UserController {}
861
862 async fn handler() -> Response {
863 let ctrl = UserController;
864 ctrl.render_error("参数错误", json!({}), 0)
865 }
866
867 let router = axum::Router::new().route("/", axum::routing::post(handler));
868 let req = Request::builder()
869 .method(Method::POST)
870 .uri("/")
871 .body(Body::empty())
872 .unwrap();
873 let resp = router.oneshot(req).await.unwrap();
874
875 assert_eq!(resp.status(), StatusCode::OK);
876 let body = collect_body(resp).await;
877 assert_eq!(body, r#"{"code":0,"msg":"参数错误","data":{}}"#);
878 }
879
880 #[tokio::test]
885 async fn test_post_data_json_body() {
886 let ctrl = MockController;
887 let req = make_json_request(r#"{"name":"alice","age":30}"#, None);
888 let data = ctrl.post_data(req).await.unwrap();
889 assert_eq!(data["name"], "alice");
890 assert_eq!(data["age"], 30);
891 }
892
893 #[tokio::test]
894 async fn test_post_data_query_only() {
895 let ctrl = MockController;
896 let req = make_json_request("", Some("page=1&size=10"));
897 let data = ctrl.post_data(req).await.unwrap();
898 assert_eq!(data["page"], "1");
899 assert_eq!(data["size"], "10");
900 }
901
902 #[tokio::test]
903 async fn test_post_data_body_overrides_query() {
904 let ctrl = MockController;
906 let req = make_json_request(r#"{"page":99}"#, Some("page=1&size=10"));
907 let data = ctrl.post_data(req).await.unwrap();
908 assert_eq!(data["page"], 99);
909 assert_eq!(data["size"], "10");
910 }
911
912 #[tokio::test]
913 async fn test_post_data_by_key_exists() {
914 let ctrl = MockController;
915 let req = make_json_request(r#"{"name":"alice","age":30}"#, None);
916 let name = ctrl.post_data_by_key(req, "name").await.unwrap();
917 assert_eq!(name, Some(json!("alice")));
918 }
919
920 #[tokio::test]
921 async fn test_post_data_by_key_missing() {
922 let ctrl = MockController;
923 let req = make_json_request(r#"{"name":"alice"}"#, None);
924 let age = ctrl.post_data_by_key(req, "age").await.unwrap();
925 assert_eq!(age, None);
926 }
927
928 #[test]
933 fn test_get_data_query() {
934 let ctrl = MockController;
935 let req = make_get_request("page=1&size=10");
936 let data = ctrl.get_data(&req);
937 assert_eq!(data["page"], "1");
938 assert_eq!(data["size"], "10");
939 }
940
941 #[test]
942 fn test_get_data_empty_query() {
943 let ctrl = MockController;
944 let req = make_get_request("");
945 let data = ctrl.get_data(&req);
946 assert!(data.as_object().unwrap().is_empty());
947 }
948
949 #[test]
950 fn test_get_data_by_key_exists() {
951 let ctrl = MockController;
952 let req = make_get_request("page=1&size=10");
953 let page = ctrl.get_data_by_key(&req, "page");
954 assert_eq!(page, Some(json!("1")));
955 }
956
957 #[test]
958 fn test_get_data_by_key_missing() {
959 let ctrl = MockController;
960 let req = make_get_request("page=1");
961 let size = ctrl.get_data_by_key(&req, "size");
962 assert_eq!(size, None);
963 }
964
965 #[tokio::test]
970 async fn test_multiple_controllers_independent() {
971 struct CtrlA;
973 struct CtrlB;
974 impl SzController for CtrlA {}
975 impl SzController for CtrlB {}
976
977 let a = CtrlA;
978 let b = CtrlB;
979
980 let req_a = make_json_request(r#"{"k":"a"}"#, None);
981 let req_b = make_json_request(r#"{"k":"b"}"#, None);
982
983 let data_a = a.post_data(req_a).await.unwrap();
984 let data_b = b.post_data(req_b).await.unwrap();
985
986 assert_eq!(data_a["k"], "a");
987 assert_eq!(data_b["k"], "b");
988
989 let va = a.render_json(1, "a", json!({}));
991 let vb = b.render_json(0, "b", json!({}));
992 assert_eq!(va["code"], 1);
993 assert_eq!(va["msg"], "a");
994 assert_eq!(vb["code"], 0);
995 assert_eq!(vb["msg"], "b");
996 }
997
998 #[tokio::test]
1003 async fn test_php_consistency_full_flow() {
1004 struct OrderController;
1006 impl SzController for OrderController {}
1007
1008 let ctrl = OrderController;
1009
1010 let req = make_json_request(r#"{"order_id":12345,"amount":99.5}"#, None);
1012 let data = ctrl.post_data(req).await.unwrap();
1013 let order_id = data["order_id"].as_i64().unwrap();
1014 let amount = data["amount"].as_f64().unwrap();
1015
1016 let result = json!({
1018 "order_id": order_id,
1019 "amount": amount,
1020 "status": "paid"
1021 });
1022
1023 let resp = ctrl.render_success("支付成功", result);
1025 let body = collect_body(resp).await;
1026
1027 assert_eq!(
1029 body,
1030 r#"{"code":1,"msg":"支付成功","data":{"order_id":12345,"amount":99.5,"status":"paid"}}"#
1031 );
1032 }
1033
1034 #[tokio::test]
1035 async fn test_php_consistency_error_flow() {
1036 struct UserController;
1038 impl SzController for UserController {}
1039
1040 let ctrl = UserController;
1041
1042 let req = make_json_request(r#"{"name":""}"#, None);
1043 let data = ctrl.post_data(req).await.unwrap();
1044 let name = data["name"].as_str().unwrap();
1045
1046 if name.is_empty() {
1047 let resp = ctrl.render_error("用户名不能为空", json!({"field": "name"}), 0);
1048 let body = collect_body(resp).await;
1049 assert_eq!(
1050 body,
1051 r#"{"code":0,"msg":"用户名不能为空","data":{"field":"name"}}"#
1052 );
1053 } else {
1054 panic!("should be empty");
1055 }
1056 }
1057
1058 #[tokio::test]
1059 async fn test_php_consistency_not_login_flow() {
1060 struct PassportController;
1062 impl SzController for PassportController {}
1063
1064 let ctrl = PassportController;
1065 let resp = ctrl.render_error("not_login", json!({}), -1);
1066 let body = collect_body(resp).await;
1067 assert_eq!(body, r#"{"code":-1,"msg":"not_login","data":{}}"#);
1068 }
1069
1070 struct MockBaseController;
1076
1077 impl SzController for MockBaseController {}
1078 impl BaseController for MockBaseController {}
1079
1080 #[test]
1081 fn test_base_controller_default_batch_validate() {
1082 let ctrl = MockBaseController;
1084 assert!(!ctrl.batch_validate());
1085 }
1086
1087 #[test]
1088 fn test_base_controller_default_middlewares_empty() {
1089 let ctrl = MockBaseController;
1091 assert!(ctrl.middlewares().is_empty());
1092 }
1093
1094 #[test]
1095 fn test_base_controller_default_initialize_no_panic() {
1096 let ctrl = MockBaseController;
1098 ctrl.initialize(); }
1100
1101 #[test]
1102 fn test_base_controller_default_validate_returns_ok() {
1103 let ctrl = MockBaseController;
1105 let data = json!({"name": "alice"});
1106 let rules = [("name", "require")];
1107 let messages: [(&str, &str); 0] = [];
1108 let result = ctrl.validate(&data, &rules, &messages);
1109 assert!(result.is_ok());
1110 }
1111
1112 #[test]
1113 fn test_validate_require_pass_with_value() {
1114 let ctrl = MockBaseController;
1116 let data = json!({"name": "alice", "age": 30});
1117 let rules = [("name", "require"), ("age", "require|integer")];
1118 let messages: [(&str, &str); 0] = [];
1119 assert!(ctrl.validate(&data, &rules, &messages).is_ok());
1120 }
1121
1122 #[test]
1123 fn test_validate_require_fail_when_missing() {
1124 let ctrl = MockBaseController;
1126 let data = json!({"name": "alice"});
1127 let rules = [("name", "require"), ("age", "require|integer")];
1128 let messages: [(&str, &str); 0] = [];
1129 let result = ctrl.validate(&data, &rules, &messages);
1130 assert!(result.is_err());
1131 let err = result.unwrap_err();
1132 assert!(err.contains("age"), "error: {err}");
1134 }
1135
1136 #[test]
1137 fn test_validate_integer_fail_on_string() {
1138 let ctrl = MockBaseController;
1140 let data = json!({"age": "not-a-number"});
1141 let rules = [("age", "require|integer")];
1142 let messages: [(&str, &str); 0] = [];
1143 let result = ctrl.validate(&data, &rules, &messages);
1144 assert!(result.is_err());
1145 }
1146
1147 #[test]
1148 fn test_validate_custom_message_applied() {
1149 let ctrl = MockBaseController;
1151 let data = json!({}); let rules = [("name", "require")];
1153 let messages = [("name.require", "名称必填")];
1154 let result = ctrl.validate(&data, &rules, &messages);
1155 assert!(result.is_err());
1156 assert_eq!(result.unwrap_err(), "名称必填");
1157 }
1158
1159 #[test]
1160 fn test_validate_batch_mode_returns_multiple_errors() {
1161 struct BatchController;
1163 impl SzController for BatchController {}
1164 impl BaseController for BatchController {
1165 fn batch_validate(&self) -> bool {
1166 true
1167 }
1168 }
1169
1170 let ctrl = BatchController;
1171 let data = json!({}); let rules = [("name", "require"), ("age", "require")];
1173 let messages = [("name.require", "名称必填"), ("age.require", "年龄必填")];
1174 let result = ctrl.validate(&data, &rules, &messages);
1175 assert!(result.is_err());
1176 let err = result.unwrap_err();
1177 assert!(err.contains("名称必填"), "err: {err}");
1179 assert!(err.contains("年龄必填"), "err: {err}");
1180 assert!(err.contains("; "), "err: {err}");
1182 }
1183
1184 #[test]
1185 fn test_validate_single_mode_returns_first_error_only() {
1186 let ctrl = MockBaseController;
1188 let data = json!({}); let rules = [("name", "require"), ("age", "require")];
1190 let messages = [("name.require", "名称必填"), ("age.require", "年龄必填")];
1191 let result = ctrl.validate(&data, &rules, &messages);
1192 assert!(result.is_err());
1193 let err = result.unwrap_err();
1194 assert!(err.contains("名称必填"), "err: {err}");
1196 assert!(!err.contains("年龄必填"), "err: {err}");
1197 }
1198
1199 #[test]
1200 fn test_validate_in_rule_pass() {
1201 let ctrl = MockBaseController;
1203 let data = json!({"status": "active"});
1204 let rules = [("status", "require|in:active,inactive")];
1205 let messages: [(&str, &str); 0] = [];
1206 assert!(ctrl.validate(&data, &rules, &messages).is_ok());
1207 }
1208
1209 #[test]
1210 fn test_validate_in_rule_fail() {
1211 let ctrl = MockBaseController;
1213 let data = json!({"status": "deleted"});
1214 let rules = [("status", "require|in:active,inactive")];
1215 let messages: [(&str, &str); 0] = [];
1216 assert!(ctrl.validate(&data, &rules, &messages).is_err());
1217 }
1218
1219 #[test]
1220 fn test_validate_empty_rules_always_pass() {
1221 let ctrl = MockBaseController;
1223 let data = json!({"anything": "value"});
1224 let rules: [(&str, &str); 0] = [];
1225 let messages: [(&str, &str); 0] = [];
1226 assert!(ctrl.validate(&data, &rules, &messages).is_ok());
1227 }
1228
1229 #[test]
1230 fn test_base_controller_inherits_sz_controller_methods() {
1231 let ctrl = MockBaseController;
1233 let value = ctrl.render_json(1, "ok", json!({}));
1234 assert_eq!(value["code"], 1);
1235 assert_eq!(value["msg"], "ok");
1236
1237 let resp = ctrl.render_success("ok", json!({"id": 1}));
1238 assert_eq!(resp.status(), StatusCode::OK);
1239 }
1240
1241 struct CustomController;
1243
1244 impl SzController for CustomController {}
1245 impl BaseController for CustomController {
1246 fn batch_validate(&self) -> bool {
1247 true
1248 }
1249
1250 fn middlewares(&self) -> Vec<String> {
1251 vec!["auth".to_string(), "cors".to_string()]
1252 }
1253
1254 fn initialize(&self) {
1255 }
1257 }
1258
1259 #[test]
1260 fn test_base_controller_override_batch_validate() {
1261 let ctrl = CustomController;
1262 assert!(ctrl.batch_validate());
1263 }
1264
1265 #[test]
1266 fn test_base_controller_override_middlewares() {
1267 let ctrl = CustomController;
1268 let mws = ctrl.middlewares();
1269 assert_eq!(mws, vec!["auth", "cors"]);
1270 }
1271
1272 #[test]
1273 fn test_base_controller_override_initialize() {
1274 let ctrl = CustomController;
1275 ctrl.initialize(); }
1277
1278 struct StatefulController {
1281 initialized: parking_lot::Mutex<bool>,
1282 custom_batch: bool,
1283 }
1284
1285 impl StatefulController {
1286 fn new() -> Self {
1287 Self {
1288 initialized: parking_lot::Mutex::new(false),
1289 custom_batch: false,
1290 }
1291 }
1292 }
1293
1294 impl SzController for StatefulController {}
1295 impl BaseController for StatefulController {
1296 fn batch_validate(&self) -> bool {
1297 self.custom_batch
1298 }
1299
1300 fn initialize(&self) {
1301 *self.initialized.lock() = true;
1302 }
1305 }
1306
1307 #[test]
1308 fn test_base_controller_stateful_initialize() {
1309 let ctrl = StatefulController::new();
1310 assert!(!*ctrl.initialized.lock()); ctrl.initialize(); assert!(*ctrl.initialized.lock()); }
1314
1315 #[tokio::test]
1317 async fn test_base_controller_php_full_flow() {
1318 struct UserController;
1319 impl SzController for UserController {}
1320 impl BaseController for UserController {}
1321
1322 let ctrl = UserController;
1323
1324 ctrl.initialize();
1326
1327 let req = make_json_request(r#"{"name":"alice","age":30}"#, None);
1329 let data = ctrl.post_data(req).await.unwrap();
1330
1331 let rules = [("name", "require"), ("age", "require|integer|gt:0")];
1333 let messages: [(&str, &str); 0] = [];
1334 ctrl.validate(&data, &rules, &messages).unwrap();
1335
1336 let resp =
1338 ctrl.render_success("success", json!({"name": data["name"], "age": data["age"]}));
1339 let body = collect_body(resp).await;
1340 assert_eq!(
1341 body,
1342 r#"{"code":1,"msg":"success","data":{"name":"alice","age":30}}"#
1343 );
1344 }
1345
1346 #[test]
1348 fn test_base_controller_multiple_instances_independent() {
1349 struct ControllerA;
1350 struct ControllerB;
1351
1352 impl SzController for ControllerA {}
1353 impl BaseController for ControllerA {
1354 fn middlewares(&self) -> Vec<String> {
1355 vec!["auth".to_string()]
1356 }
1357 }
1358
1359 impl SzController for ControllerB {}
1360 impl BaseController for ControllerB {
1361 fn middlewares(&self) -> Vec<String> {
1362 vec!["cors".to_string(), "log".to_string()]
1363 }
1364 }
1365
1366 let a = ControllerA;
1367 let b = ControllerB;
1368
1369 assert_eq!(a.middlewares(), vec!["auth"]);
1370 assert_eq!(b.middlewares(), vec!["cors", "log"]);
1371
1372 assert!(!a.batch_validate());
1374 assert!(!b.batch_validate());
1375 }
1376
1377 #[test]
1379 fn test_base_controller_inheritance_chain() {
1380 struct BusinessController;
1382 impl SzController for BusinessController {}
1383 impl BaseController for BusinessController {}
1384
1385 let ctrl = BusinessController;
1386
1387 let value = ctrl.render_json(0, "error", json!({}));
1389 assert!(value.is_object());
1390
1391 assert!(!ctrl.batch_validate());
1393 assert!(ctrl.middlewares().is_empty());
1394 ctrl.initialize();
1395 }
1396
1397 struct MockAddonsController;
1403
1404 impl SzController for MockAddonsController {}
1405 impl BaseController for MockAddonsController {}
1406 impl AddonsBaseController for MockAddonsController {}
1407
1408 #[test]
1409 fn test_addons_default_allow_all_action() {
1410 let ctrl = MockAddonsController;
1412 let allow = ctrl.allow_all_action();
1413 assert!(allow.contains(&"/passport/login"));
1414 assert!(allow.contains(&"/task/task/userClerk"));
1415 assert_eq!(allow.len(), 2);
1416 }
1417
1418 #[test]
1419 fn test_addons_parse_route_info_two_segments() {
1420 let ctrl = MockAddonsController;
1422 let info = ctrl.parse_route_info("/passport/login");
1423 assert_eq!(info.controller, "passport");
1424 assert_eq!(info.action, "login");
1425 assert_eq!(info.group, "passport");
1426 assert_eq!(info.route_uri, "/passport/login");
1427 }
1428
1429 #[test]
1430 fn test_addons_parse_route_info_three_segments() {
1431 let ctrl = MockAddonsController;
1434 let info = ctrl.parse_route_info("/task/task/userClerk");
1435 assert_eq!(info.controller, "task/task");
1436 assert_eq!(info.action, "userClerk");
1437 assert_eq!(info.group, "task/task"); assert_eq!(info.route_uri, "/task/task/userClerk");
1439 }
1440
1441 #[test]
1442 fn test_addons_parse_route_info_single_segment() {
1443 let ctrl = MockAddonsController;
1445 let info = ctrl.parse_route_info("/passport");
1446 assert_eq!(info.controller, "passport");
1447 assert_eq!(info.action, "");
1448 assert_eq!(info.group, "passport");
1449 assert_eq!(info.route_uri, "/passport/");
1450 }
1451
1452 #[test]
1453 fn test_addons_parse_route_info_root() {
1454 let ctrl = MockAddonsController;
1456 let info = ctrl.parse_route_info("/");
1457 assert_eq!(info.controller, "");
1458 assert_eq!(info.action, "");
1459 assert_eq!(info.group, "");
1460 assert_eq!(info.route_uri, "/");
1461 }
1462
1463 #[test]
1464 fn test_addons_parse_route_info_empty_uri() {
1465 let ctrl = MockAddonsController;
1466 let info = ctrl.parse_route_info("");
1467 assert_eq!(info.controller, "");
1468 assert_eq!(info.route_uri, "/");
1469 }
1470
1471 #[test]
1472 fn test_addons_parse_route_info_with_query_string() {
1473 let ctrl = MockAddonsController;
1475 let info = ctrl.parse_route_info("/user/info?foo=bar&baz=1");
1476 assert_eq!(info.controller, "user");
1477 assert_eq!(info.action, "info");
1478 assert_eq!(info.route_uri, "/user/info");
1479 }
1480
1481 #[test]
1482 fn test_addons_parse_route_info_trailing_slash() {
1483 let ctrl = MockAddonsController;
1485 let info = ctrl.parse_route_info("/user/info/");
1486 assert_eq!(info.controller, "user/info");
1488 assert_eq!(info.action, "");
1489 assert_eq!(info.route_uri, "/user/info/");
1490 }
1491
1492 #[test]
1493 fn test_addons_check_login_whitelist_pass() {
1494 let ctrl = MockAddonsController;
1496 let result = ctrl.check_login("/passport/login", false);
1497 assert!(result.is_ok());
1498 }
1499
1500 #[test]
1501 fn test_addons_check_login_whitelist_pass_2() {
1502 let ctrl = MockAddonsController;
1504 let result = ctrl.check_login("/task/task/userClerk", false);
1505 assert!(result.is_ok());
1506 }
1507
1508 #[test]
1509 fn test_addons_check_login_logged_in_pass() {
1510 let ctrl = MockAddonsController;
1512 let result = ctrl.check_login("/user/info", true);
1513 assert!(result.is_ok());
1514 }
1515
1516 #[test]
1517 fn test_addons_check_login_not_logged_in_fail() {
1518 let ctrl = MockAddonsController;
1520 let result = ctrl.check_login("/user/info", false);
1521 assert!(result.is_err());
1522 assert_eq!(result.unwrap_err(), "not_login");
1523 }
1524
1525 #[test]
1526 fn test_addons_get_token_default_returns_none() {
1527 let ctrl = MockAddonsController;
1530 let result = ctrl.get_token(Some("Bearer xxx.yyy.zzz"));
1531 assert!(result.is_ok());
1532 assert_eq!(result.unwrap(), None);
1533 }
1534
1535 #[test]
1536 fn test_addons_get_token_no_authorization() {
1537 let ctrl = MockAddonsController;
1538 let result = ctrl.get_token(None);
1539 assert!(result.is_ok());
1540 assert_eq!(result.unwrap(), None);
1541 }
1542
1543 #[test]
1544 fn test_addons_get_token_empty_authorization() {
1545 let ctrl = MockAddonsController;
1546 let result = ctrl.get_token(Some(""));
1547 assert!(result.is_ok());
1548 assert_eq!(result.unwrap(), None);
1549 }
1550
1551 #[test]
1552 fn test_addons_get_token_invalid_format_returns_none() {
1553 let ctrl = MockAddonsController;
1556 let result = ctrl.get_token(Some("Bearer not.a.valid.jwt.token"));
1557 assert!(result.is_ok());
1558 assert_eq!(result.unwrap(), None);
1560 }
1561
1562 #[test]
1563 fn test_strip_bearer_prefix_uppercase() {
1564 assert_eq!(strip_bearer_prefix("Bearer abc.def.ghi"), "abc.def.ghi");
1565 }
1566
1567 #[test]
1568 fn test_strip_bearer_prefix_lowercase() {
1569 assert_eq!(strip_bearer_prefix("bearer abc.def.ghi"), "abc.def.ghi");
1570 }
1571
1572 #[test]
1573 fn test_strip_bearer_prefix_mixed_case() {
1574 assert_eq!(strip_bearer_prefix("BEARER abc.def.ghi"), "abc.def.ghi");
1575 }
1576
1577 #[test]
1578 fn test_strip_bearer_prefix_no_prefix() {
1579 assert_eq!(strip_bearer_prefix("abc.def.ghi"), "abc.def.ghi");
1581 }
1582
1583 #[test]
1584 fn test_strip_bearer_prefix_empty() {
1585 assert_eq!(strip_bearer_prefix(""), "");
1586 }
1587
1588 #[test]
1589 fn test_strip_bearer_prefix_with_extra_spaces() {
1590 assert_eq!(
1591 strip_bearer_prefix(" Bearer abc.def.ghi "),
1592 "abc.def.ghi"
1593 );
1594 }
1595
1596 #[test]
1601 fn test_get_token_valid_jwt_returns_user_info() {
1602 let config = JwtConfig {
1603 secret: "test-secret".to_string(),
1604 issuer: String::new(), audience: String::new(),
1606 };
1607
1608 let encoder = sz_orm_auth::jwt::JwtEncoder::new(&config.secret);
1610 let exp = std::time::SystemTime::now()
1611 .duration_since(std::time::UNIX_EPOCH)
1612 .unwrap()
1613 .as_secs() as i64
1614 + 3600; let claims = sz_orm_auth::jwt::JwtClaims::new("user123", exp).with_user_id(12345);
1616 let token = encoder.encode(&claims).unwrap();
1617
1618 let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1619 assert!(result.is_ok());
1620 let user = result.unwrap();
1621 assert!(user.is_some());
1622 let user = user.unwrap();
1623 assert_eq!(user.user_id, 12345);
1624 assert!(user.is_login);
1625 }
1626
1627 #[test]
1629 fn test_get_token_wrong_secret_returns_none() {
1630 let config = JwtConfig {
1631 secret: "correct-secret".to_string(),
1632 issuer: String::new(),
1633 audience: String::new(),
1634 };
1635
1636 let encoder = sz_orm_auth::jwt::JwtEncoder::new("wrong-secret");
1638 let exp = std::time::SystemTime::now()
1639 .duration_since(std::time::UNIX_EPOCH)
1640 .unwrap()
1641 .as_secs() as i64
1642 + 3600;
1643 let claims = sz_orm_auth::jwt::JwtClaims::new("user123", exp).with_user_id(12345);
1644 let token = encoder.encode(&claims).unwrap();
1645
1646 let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1647 assert!(result.is_ok());
1648 assert_eq!(result.unwrap(), None); }
1650
1651 #[test]
1653 fn test_get_token_expired_returns_none() {
1654 let config = JwtConfig {
1655 secret: "test-secret".to_string(),
1656 issuer: String::new(),
1657 audience: String::new(),
1658 };
1659
1660 let encoder = sz_orm_auth::jwt::JwtEncoder::new(&config.secret);
1661 let exp = std::time::SystemTime::now()
1663 .duration_since(std::time::UNIX_EPOCH)
1664 .unwrap()
1665 .as_secs() as i64
1666 - 3600;
1667 let claims = sz_orm_auth::jwt::JwtClaims::new("user123", exp).with_user_id(12345);
1668 let token = encoder.encode(&claims).unwrap();
1669
1670 let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1671 assert!(result.is_ok());
1672 assert_eq!(result.unwrap(), None); }
1674
1675 #[test]
1677 fn test_get_token_no_user_id_claim_returns_none() {
1678 let config = JwtConfig {
1679 secret: "test-secret".to_string(),
1680 issuer: String::new(),
1681 audience: String::new(),
1682 };
1683
1684 let encoder = sz_orm_auth::jwt::JwtEncoder::new(&config.secret);
1685 let exp = std::time::SystemTime::now()
1686 .duration_since(std::time::UNIX_EPOCH)
1687 .unwrap()
1688 .as_secs() as i64
1689 + 3600;
1690 let claims = sz_orm_auth::jwt::JwtClaims::new("user123", exp);
1692 let token = encoder.encode(&claims).unwrap();
1693
1694 let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1695 assert!(result.is_ok());
1696 assert_eq!(result.unwrap(), None); }
1698
1699 #[test]
1701 fn test_get_token_iss_mismatch_returns_none() {
1702 let config = JwtConfig {
1703 secret: "test-secret".to_string(),
1704 issuer: "https://expected-issuer.com".to_string(),
1705 audience: String::new(),
1706 };
1707
1708 let encoder = sz_orm_auth::jwt::JwtEncoder::new(&config.secret);
1709 let exp = std::time::SystemTime::now()
1710 .duration_since(std::time::UNIX_EPOCH)
1711 .unwrap()
1712 .as_secs() as i64
1713 + 3600;
1714 let claims = sz_orm_auth::jwt::JwtClaims::new("user123", exp)
1716 .with_issuer("https://wrong-issuer.com")
1717 .with_user_id(12345);
1718 let token = encoder.encode(&claims).unwrap();
1719
1720 let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1721 assert!(result.is_ok());
1722 assert_eq!(result.unwrap(), None); }
1724
1725 #[test]
1727 fn test_get_token_iss_match_returns_user_info() {
1728 let config = JwtConfig {
1729 secret: "test-secret".to_string(),
1730 issuer: "https://mall.ljclz.shop".to_string(),
1731 audience: String::new(),
1732 };
1733
1734 let encoder = sz_orm_auth::jwt::JwtEncoder::new(&config.secret);
1735 let exp = std::time::SystemTime::now()
1736 .duration_since(std::time::UNIX_EPOCH)
1737 .unwrap()
1738 .as_secs() as i64
1739 + 3600;
1740 let claims = sz_orm_auth::jwt::JwtClaims::new("user123", exp)
1741 .with_issuer(&config.issuer)
1742 .with_user_id(67890);
1743 let token = encoder.encode(&claims).unwrap();
1744
1745 let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1746 assert!(result.is_ok());
1747 let user = result.unwrap().unwrap();
1748 assert_eq!(user.user_id, 67890);
1749 assert!(user.is_login);
1750 }
1751
1752 #[test]
1754 fn test_get_token_empty_secret_returns_none() {
1755 let config = JwtConfig::default(); let result = verify_token_with_config(Some("Bearer any.token.here"), &config);
1758 assert!(result.is_ok());
1759 assert_eq!(result.unwrap(), None); }
1761
1762 #[test]
1764 fn test_get_token_without_bearer_prefix() {
1765 let config = JwtConfig {
1766 secret: "test-secret".to_string(),
1767 issuer: String::new(),
1768 audience: String::new(),
1769 };
1770
1771 let encoder = sz_orm_auth::jwt::JwtEncoder::new(&config.secret);
1772 let exp = std::time::SystemTime::now()
1773 .duration_since(std::time::UNIX_EPOCH)
1774 .unwrap()
1775 .as_secs() as i64
1776 + 3600;
1777 let claims = sz_orm_auth::jwt::JwtClaims::new("user123", exp).with_user_id(99999);
1778 let token = encoder.encode(&claims).unwrap();
1779
1780 let result = verify_token_with_config(Some(&token), &config);
1782 assert!(result.is_ok());
1783 let user = result.unwrap().unwrap();
1784 assert_eq!(user.user_id, 99999);
1785 }
1786
1787 struct CustomAddonsController;
1789
1790 impl SzController for CustomAddonsController {}
1791 impl BaseController for CustomAddonsController {}
1792 impl AddonsBaseController for CustomAddonsController {
1793 fn allow_all_action(&self) -> Vec<&'static str> {
1794 vec!["/custom/public", "/custom/health"]
1795 }
1796 }
1797
1798 #[test]
1799 fn test_addons_override_allow_all_action() {
1800 let ctrl = CustomAddonsController;
1801 let allow = ctrl.allow_all_action();
1802 assert_eq!(allow, vec!["/custom/public", "/custom/health"]);
1803 assert!(!allow.contains(&"/passport/login"));
1805 }
1806
1807 #[test]
1808 fn test_addons_override_check_login_uses_new_whitelist() {
1809 let ctrl = CustomAddonsController;
1810 assert!(ctrl.check_login("/custom/public", false).is_ok());
1812 assert!(ctrl.check_login("/passport/login", false).is_err());
1814 }
1815
1816 #[tokio::test]
1818 async fn test_addons_php_full_flow_whitelist() {
1819 struct PassportController;
1820 impl SzController for PassportController {}
1821 impl BaseController for PassportController {}
1822 impl AddonsBaseController for PassportController {}
1823
1824 let ctrl = PassportController;
1825
1826 let info = ctrl.parse_route_info("/passport/login");
1828 assert_eq!(info.route_uri, "/passport/login");
1829
1830 let user = ctrl.get_token(None).unwrap();
1832
1833 let is_login = user.as_ref().is_some_and(|u| u.is_login);
1835 ctrl.check_login(&info.route_uri, is_login).unwrap();
1836
1837 let resp = ctrl.render_success("登录成功", json!({"token": "fake.jwt.token"}));
1839 let body = collect_body(resp).await;
1840 assert_eq!(
1841 body,
1842 r#"{"code":1,"msg":"登录成功","data":{"token":"fake.jwt.token"}}"#
1843 );
1844 }
1845
1846 #[tokio::test]
1847 async fn test_addons_php_full_flow_not_login() {
1848 struct UserController;
1849 impl SzController for UserController {}
1850 impl BaseController for UserController {}
1851 impl AddonsBaseController for UserController {}
1852
1853 let ctrl = UserController;
1854
1855 let info = ctrl.parse_route_info("/user/info");
1857
1858 let user = ctrl.get_token(None).unwrap();
1860 let is_login = user.as_ref().is_some_and(|u| u.is_login);
1861
1862 let result = ctrl.check_login(&info.route_uri, is_login);
1864 assert!(result.is_err());
1865 assert_eq!(result.unwrap_err(), "not_login");
1866
1867 let resp = ctrl.render_error("not_login", json!({}), -1);
1869 let body = collect_body(resp).await;
1870 assert_eq!(body, r#"{"code":-1,"msg":"not_login","data":{}}"#);
1871 }
1872
1873 #[tokio::test]
1874 async fn test_addons_php_full_flow_logged_in() {
1875 struct OrderController;
1876 impl SzController for OrderController {}
1877 impl BaseController for OrderController {}
1878 impl AddonsBaseController for OrderController {
1879 fn get_token(&self, _authorization: Option<&str>) -> Result<Option<UserInfo>, String> {
1881 Ok(Some(UserInfo {
1882 user_id: 12345,
1883 is_login: true,
1884 }))
1885 }
1886 }
1887
1888 let ctrl = OrderController;
1889
1890 let info = ctrl.parse_route_info("/order/list");
1892
1893 let user = ctrl.get_token(None).unwrap();
1895 let is_login = user.as_ref().is_some_and(|u| u.is_login);
1896
1897 ctrl.check_login(&info.route_uri, is_login).unwrap();
1899
1900 let resp = ctrl.render_success(
1902 "success",
1903 json!({"list": [{"id": 1}, {"id": 2}], "total": 2}),
1904 );
1905 let body = collect_body(resp).await;
1906 assert_eq!(
1907 body,
1908 r#"{"code":1,"msg":"success","data":{"list":[{"id":1},{"id":2}],"total":2}}"#
1909 );
1910 }
1911
1912 #[test]
1914 fn test_route_info_debug_clone_eq() {
1915 let info1 = RouteInfo {
1916 controller: "user".to_string(),
1917 action: "info".to_string(),
1918 route_uri: "/user/info".to_string(),
1919 group: "user".to_string(),
1920 };
1921 let info2 = info1.clone();
1922 assert_eq!(info1, info2);
1923 let debug_str = format!("{info1:?}");
1924 assert!(debug_str.contains("RouteInfo"));
1925 assert!(debug_str.contains("user"));
1926 }
1927
1928 #[test]
1929 fn test_user_info_debug_clone_eq() {
1930 let user1 = UserInfo {
1931 user_id: 12345,
1932 is_login: true,
1933 };
1934 let user2 = user1.clone();
1935 assert_eq!(user1, user2);
1936 let debug_str = format!("{user1:?}");
1937 assert!(debug_str.contains("UserInfo"));
1938 assert!(debug_str.contains("12345"));
1939 }
1940
1941 #[test]
1943 fn test_addons_inheritance_chain_all_methods() {
1944 struct BusinessController;
1945 impl SzController for BusinessController {}
1946 impl BaseController for BusinessController {}
1947 impl AddonsBaseController for BusinessController {}
1948
1949 let ctrl = BusinessController;
1950
1951 let value = ctrl.render_json(1, "ok", json!({}));
1953 assert_eq!(value["code"], 1);
1954
1955 assert!(!ctrl.batch_validate());
1957 assert!(ctrl.middlewares().is_empty());
1958 ctrl.initialize();
1959
1960 let info = ctrl.parse_route_info("/test/action");
1962 assert_eq!(info.route_uri, "/test/action");
1963 assert!(ctrl.check_login("/passport/login", false).is_ok());
1964 assert!(ctrl.get_token(None).unwrap().is_none());
1965 }
1966
1967 #[test]
1976 fn test_p0_sec_02_empty_secret_returns_none_not_accept_token() {
1977 let config = JwtConfig {
1978 secret: String::new(), issuer: String::new(),
1980 audience: String::new(),
1981 };
1982
1983 let result = verify_token_with_config(Some("any.token.here"), &config);
1985 assert!(
1986 result.is_ok(),
1987 "空密钥不应导致 panic 或 Err(应优雅降级为 None)"
1988 );
1989 assert_eq!(
1990 result.unwrap(),
1991 None,
1992 "P0-SEC-02: 空密钥时必须返回 None,绝不能接受任何 token"
1993 );
1994 }
1995
1996 #[test]
2001 fn test_p0_sec_02_rejects_alg_none_token_with_empty_secret() {
2002 let config = JwtConfig::default(); let alg_none_token = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.\
2009 eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImhhY2tlciIsImlhdCI6MCwiZXhwIjo5OTk5OTk5OTk5fQ.";
2010
2011 let result = verify_token_with_config(Some(&alg_none_token), &config);
2012 assert!(
2013 result.is_ok(),
2014 "空密钥下不应 panic"
2015 );
2016 assert_eq!(
2017 result.unwrap(),
2018 None,
2019 "P0-SEC-02: 空密钥下 alg=none 伪造 token 必须被拒绝(返回 None)"
2020 );
2021 }
2022
2023 #[test]
2029 fn test_p1_sec_10_rejects_token_with_wrong_audience() {
2030 let config = JwtConfig {
2031 secret: "test-secret".to_string(),
2032 issuer: String::new(),
2033 audience: "https://my-app.com".to_string(),
2034 };
2035
2036 let encoder = sz_orm_auth::jwt::JwtEncoder::new(&config.secret);
2037 let exp = std::time::SystemTime::now()
2038 .duration_since(std::time::UNIX_EPOCH)
2039 .unwrap()
2040 .as_secs() as i64
2041 + 3600;
2042 let claims = sz_orm_auth::jwt::JwtClaims::new("user123", exp)
2043 .with_user_id(42)
2044 .with_audience("https://other-app.com");
2045 let token = encoder.encode(&claims).unwrap();
2046
2047 let result = verify_token_with_config(Some(&token), &config);
2048 assert!(result.is_ok());
2049 assert_eq!(result.unwrap(), None, "P1-SEC-10: aud 不匹配的 token 必须被拒绝");
2050 }
2051
2052 #[test]
2054 fn test_p1_sec_10_accepts_token_with_matching_audience() {
2055 let config = JwtConfig {
2056 secret: "test-secret".to_string(),
2057 issuer: String::new(),
2058 audience: "https://my-app.com".to_string(),
2059 };
2060
2061 let encoder = sz_orm_auth::jwt::JwtEncoder::new(&config.secret);
2062 let exp = std::time::SystemTime::now()
2063 .duration_since(std::time::UNIX_EPOCH)
2064 .unwrap()
2065 .as_secs() as i64
2066 + 3600;
2067 let claims = sz_orm_auth::jwt::JwtClaims::new("user123", exp)
2068 .with_user_id(42)
2069 .with_audience("https://my-app.com");
2070 let token = encoder.encode(&claims).unwrap();
2071
2072 let result = verify_token_with_config(Some(&token), &config);
2073 assert!(result.is_ok());
2074 assert!(result.unwrap().is_some(), "aud 匹配的 token 应通过验证");
2075 }
2076
2077 #[test]
2079 fn test_p1_sec_10_skips_audience_check_when_not_configured() {
2080 let config = JwtConfig {
2081 secret: "test-secret".to_string(),
2082 issuer: String::new(),
2083 audience: String::new(),
2084 };
2085
2086 let encoder = sz_orm_auth::jwt::JwtEncoder::new(&config.secret);
2087 let exp = std::time::SystemTime::now()
2088 .duration_since(std::time::UNIX_EPOCH)
2089 .unwrap()
2090 .as_secs() as i64
2091 + 3600;
2092 let claims = sz_orm_auth::jwt::JwtClaims::new("user123", exp)
2093 .with_user_id(42);
2094 let token = encoder.encode(&claims).unwrap();
2095
2096 let result = verify_token_with_config(Some(&token), &config);
2097 assert!(result.is_ok());
2098 assert!(result.unwrap().is_some(), "未配置 audience 时应跳过 aud 验证");
2099 }
2100
2101 #[test]
2110 fn test_p1_sec_12_jwt_config_debug_redacts_secret() {
2111 let config = JwtConfig {
2112 secret: "super-secret-key-12345".to_string(),
2113 issuer: "https://example.com".to_string(),
2114 audience: String::new(),
2115 };
2116
2117 let debug_output = format!("{:?}", config);
2118
2119 assert!(
2121 !debug_output.contains("super-secret-key-12345"),
2122 "P1-SEC-12: JwtConfig::Debug 泄漏了 secret 字段: {debug_output}"
2123 );
2124 assert!(
2126 debug_output.contains("[REDACTED]"),
2127 "P1-SEC-12: JwtConfig::Debug 应包含 [REDACTED] 标记: {debug_output}"
2128 );
2129 assert!(
2131 debug_output.contains("https://example.com"),
2132 "issuer 字段应正常显示: {debug_output}"
2133 );
2134 }
2135}