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(Debug, Clone, Default)]
64struct JwtConfig {
65 secret: String,
67 issuer: String,
69}
70
71static JWT_CONFIG: Lazy<JwtConfig> = Lazy::new(|| JwtConfig {
76 secret: std::env::var("SZ_JWT_SECRET").unwrap_or_default(),
77 issuer: std::env::var("SZ_JWT_ISSUER").unwrap_or_default(),
78});
79
80fn strip_bearer_prefix(header: &str) -> &str {
89 let trimmed = header.trim();
90 if trimmed.len() >= 6 {
92 let prefix = &trimmed[..6];
93 if prefix.eq_ignore_ascii_case("bearer") {
94 return trimmed[6..].trim_start();
96 }
97 }
98 trimmed
99}
100
101fn verify_token_with_config(
116 authorization: Option<&str>,
117 config: &JwtConfig,
118) -> Result<Option<UserInfo>, String> {
119 let header_value = match authorization {
121 Some(v) if !v.is_empty() => v,
122 _ => return Ok(None),
123 };
124
125 let token = strip_bearer_prefix(header_value).trim();
127 if token.is_empty() {
128 return Ok(None);
129 }
130
131 if config.secret.is_empty() {
133 return Ok(None);
134 }
135
136 let encoder = sz_orm_auth::jwt::JwtEncoder::new(&config.secret);
138 let claims = match encoder.decode(token) {
139 Ok(c) => c,
140 Err(_) => return Ok(None),
141 };
142
143 if !config.issuer.is_empty() {
145 match &claims.iss {
146 Some(iss) if iss == &config.issuer => { }
147 _ => return Ok(None),
148 }
149 }
150
151 let user_id = match claims.user_id {
153 Some(id) => id,
154 None => return Ok(None),
155 };
156
157 Ok(Some(UserInfo {
158 user_id,
159 is_login: true,
160 }))
161}
162
163pub trait SzController: Send + Sync {
191 fn render_json(&self, code: i32, msg: impl Into<String>, data: Value) -> Value {
204 let mut map = Map::new();
205 map.insert("code".to_string(), Value::Number(code.into()));
206 map.insert("msg".to_string(), Value::String(msg.into()));
207 map.insert("data".to_string(), data);
208 Value::Object(map)
209 }
210
211 fn render_success(&self, msg: impl Into<String>, data: Value) -> Response {
224 ApiResponse::success(data, msg).into_response()
225 }
226
227 fn render_error(&self, msg: impl Into<String>, data: Value, code: i32) -> Response {
240 ApiResponse::error_with_code(code, msg, data).into_response()
241 }
242
243 fn post_data(&self, req: Request<Body>) -> impl Future<Output = Result<Value, String>> + Send {
256 async move { fetch_post_data(req).await }
257 }
258
259 fn post_data_by_key(
267 &self,
268 req: Request<Body>,
269 key: &str,
270 ) -> impl Future<Output = Result<Option<Value>, String>> + Send {
271 async move { fetch_post_data_by_key(req, key).await }
272 }
273
274 fn get_data(&self, req: &Request<Body>) -> Value {
278 fetch_query_data(req)
279 }
280
281 fn get_data_by_key(&self, req: &Request<Body>, key: &str) -> Option<Value> {
288 fetch_query_data_by_key(req, key)
289 }
290}
291
292pub trait BaseController: SzController {
314 fn batch_validate(&self) -> bool {
319 false
320 }
321
322 fn middlewares(&self) -> Vec<String> {
327 Vec::new()
328 }
329
330 fn initialize(&self) {}
343
344 fn validate(
373 &self,
374 data: &Value,
375 rules: &[(&str, &str)],
376 messages: &[(&str, &str)],
377 ) -> Result<(), String> {
378 let mut validator = Validate::new();
380 for (name, rule) in rules {
381 validator = validator.rule(name, rule);
382 }
383
384 let mut msg_map = IndexMap::new();
386 for (key, msg) in messages {
387 msg_map.insert(key.to_string(), msg.to_string());
388 }
389 validator = validator.message(msg_map);
390
391 if self.batch_validate() {
393 validator = validator.batch(true);
394 }
395
396 match validator.check(data) {
398 Ok(()) => Ok(()),
399 Err(err) => Err(err.to_string()),
400 }
401 }
402}
403
404#[derive(Debug, Clone, PartialEq, Eq)]
408pub struct RouteInfo {
409 pub controller: String,
411 pub action: String,
413 pub route_uri: String,
415 pub group: String,
417}
418
419#[derive(Debug, Clone, PartialEq, Eq)]
423pub struct UserInfo {
424 pub user_id: i64,
426 pub is_login: bool,
428}
429
430pub trait AddonsBaseController: BaseController {
456 fn allow_all_action(&self) -> Vec<&'static str> {
461 vec!["/passport/login", "/task/task/userClerk"]
462 }
463
464 fn parse_route_info(&self, uri: &str) -> RouteInfo {
498 let path = uri.split('?').next().unwrap_or("");
499 let path = path.trim_start_matches('/');
500 let segments: Vec<&str> = if path.is_empty() {
501 Vec::new()
502 } else {
503 path.split('/').collect()
504 };
505
506 let (controller, action) = match segments.len() {
507 0 => (String::new(), String::new()),
508 1 => (segments[0].to_string(), String::new()),
509 _ => (
510 segments[..segments.len() - 1].join("/"),
511 segments[segments.len() - 1].to_string(),
512 ),
513 };
514
515 let group = controller.clone();
517
518 let route_uri = if controller.is_empty() && action.is_empty() {
519 "/".to_string()
520 } else {
521 format!("/{controller}/{action}")
522 };
523
524 RouteInfo {
525 controller,
526 action,
527 route_uri,
528 group,
529 }
530 }
531
532 fn check_login(&self, route_uri: &str, user_is_login: bool) -> Result<(), String> {
556 if self.allow_all_action().contains(&route_uri) {
557 return Ok(());
558 }
559 if user_is_login {
560 return Ok(());
561 }
562 Err("not_login".to_string())
563 }
564
565 fn get_token(&self, authorization: Option<&str>) -> Result<Option<UserInfo>, String> {
605 verify_token_with_config(authorization, &JWT_CONFIG)
608 }
609}
610
611#[cfg(test)]
612mod tests {
613 use super::*;
614 use axum::body::Body;
615 use axum::http::{Method, Request, StatusCode};
616 use http_body_util::BodyExt;
617 use serde_json::json;
618 use tower::ServiceExt;
619
620 struct MockController;
622
623 impl SzController for MockController {}
624
625 fn make_json_request(body: &str, query: Option<&str>) -> Request<Body> {
626 let uri = match query {
627 Some(q) => format!("/?{q}"),
628 None => "/".to_string(),
629 };
630 Request::builder()
631 .method(Method::POST)
632 .uri(&uri)
633 .header("content-type", "application/json")
634 .body(Body::from(body.to_string()))
635 .unwrap()
636 }
637
638 fn make_get_request(query: &str) -> Request<Body> {
639 Request::builder()
640 .method(Method::GET)
641 .uri(format!("/?{query}"))
642 .body(Body::empty())
643 .unwrap()
644 }
645
646 async fn collect_body(resp: Response) -> String {
647 let bytes = resp.into_body().collect().await.unwrap().to_bytes();
648 String::from_utf8(bytes.to_vec()).unwrap()
649 }
650
651 #[test]
656 fn test_render_json_field_order() {
657 let ctrl = MockController;
659 let value = ctrl.render_json(1, "ok", json!({"id": 1}));
660 let obj = value.as_object().unwrap();
661 let keys: Vec<&String> = obj.keys().collect();
662 assert_eq!(keys, vec!["code", "msg", "data"]);
663 }
664
665 #[test]
666 fn test_render_json_default_values() {
667 let ctrl = MockController;
669 let value = ctrl.render_json(1, "", Value::Object(Map::new()));
670 assert_eq!(value["code"], 1);
671 assert_eq!(value["msg"], "");
672 assert!(value["data"].is_object());
673 assert!(value["data"].as_object().unwrap().is_empty());
674 }
675
676 #[test]
677 fn test_render_json_custom_code() {
678 let ctrl = MockController;
679 let value = ctrl.render_json(-1, "未登录", json!({}));
680 assert_eq!(value["code"], -1);
681 assert_eq!(value["msg"], "未登录");
682 }
683
684 #[test]
685 fn test_render_json_compact_semantics() {
686 let ctrl = MockController;
689 let value = ctrl.render_json(0, "失败", json!({"field": "name"}));
690 let json_str = value.to_string();
691 assert_eq!(
693 json_str,
694 r#"{"code":0,"msg":"失败","data":{"field":"name"}}"#
695 );
696 }
697
698 #[test]
699 fn test_render_json_returns_value_not_response() {
700 let ctrl = MockController;
702 let value = ctrl.render_json(1, "ok", json!({}));
703 assert!(value.is_object());
705 }
706
707 #[test]
712 fn test_render_success_returns_response() {
713 let ctrl = MockController;
714 let resp = ctrl.render_success("success", json!({"id": 1}));
715 assert_eq!(resp.status(), StatusCode::OK);
716 assert_eq!(
717 resp.headers().get("content-type").unwrap(),
718 "application/json; charset=utf-8"
719 );
720 }
721
722 #[tokio::test]
723 async fn test_render_success_body_format() {
724 let ctrl = MockController;
726 let resp = ctrl.render_success("success", json!({"id": 1, "name": "alice"}));
727 let body = collect_body(resp).await;
728 assert_eq!(
729 body,
730 r#"{"code":1,"msg":"success","data":{"id":1,"name":"alice"}}"#
731 );
732 }
733
734 #[tokio::test]
735 async fn test_render_success_default_msg() {
736 let ctrl = MockController;
738 let resp = ctrl.render_success("success", json!({}));
739 let body = collect_body(resp).await;
740 assert_eq!(body, r#"{"code":1,"msg":"success","data":{}}"#);
741 }
742
743 #[tokio::test]
744 async fn test_render_success_via_axum_router() {
745 struct UserController;
747 impl SzController for UserController {}
748
749 async fn handler() -> Response {
750 let ctrl = UserController;
751 ctrl.render_success("ok", json!({"id": 1}))
752 }
753
754 let router = axum::Router::new().route("/", axum::routing::get(handler));
755 let req = Request::builder()
756 .method(Method::GET)
757 .uri("/")
758 .body(Body::empty())
759 .unwrap();
760 let resp = router.oneshot(req).await.unwrap();
761
762 assert_eq!(resp.status(), StatusCode::OK);
763 let body = collect_body(resp).await;
764 assert_eq!(body, r#"{"code":1,"msg":"ok","data":{"id":1}}"#);
765 }
766
767 #[test]
772 fn test_render_error_returns_response() {
773 let ctrl = MockController;
774 let resp = ctrl.render_error("error", json!({}), 0);
775 assert_eq!(resp.status(), StatusCode::OK); }
777
778 #[tokio::test]
779 async fn test_render_error_default_code() {
780 let ctrl = MockController;
782 let resp = ctrl.render_error("参数错误", json!({}), 0);
783 let body = collect_body(resp).await;
784 assert_eq!(body, r#"{"code":0,"msg":"参数错误","data":{}}"#);
785 }
786
787 #[tokio::test]
788 async fn test_render_error_custom_code() {
789 let ctrl = MockController;
791 let resp = ctrl.render_error("not_login", json!({}), -1);
792 let body = collect_body(resp).await;
793 assert_eq!(body, r#"{"code":-1,"msg":"not_login","data":{}}"#);
794 }
795
796 #[tokio::test]
797 async fn test_render_error_with_data() {
798 let ctrl = MockController;
799 let resp = ctrl.render_error("失败", json!({"field": "name"}), 0);
800 let body = collect_body(resp).await;
801 assert_eq!(body, r#"{"code":0,"msg":"失败","data":{"field":"name"}}"#);
802 }
803
804 #[tokio::test]
805 async fn test_render_error_via_axum_router() {
806 struct UserController;
807 impl SzController for UserController {}
808
809 async fn handler() -> Response {
810 let ctrl = UserController;
811 ctrl.render_error("参数错误", json!({}), 0)
812 }
813
814 let router = axum::Router::new().route("/", axum::routing::post(handler));
815 let req = Request::builder()
816 .method(Method::POST)
817 .uri("/")
818 .body(Body::empty())
819 .unwrap();
820 let resp = router.oneshot(req).await.unwrap();
821
822 assert_eq!(resp.status(), StatusCode::OK);
823 let body = collect_body(resp).await;
824 assert_eq!(body, r#"{"code":0,"msg":"参数错误","data":{}}"#);
825 }
826
827 #[tokio::test]
832 async fn test_post_data_json_body() {
833 let ctrl = MockController;
834 let req = make_json_request(r#"{"name":"alice","age":30}"#, None);
835 let data = ctrl.post_data(req).await.unwrap();
836 assert_eq!(data["name"], "alice");
837 assert_eq!(data["age"], 30);
838 }
839
840 #[tokio::test]
841 async fn test_post_data_query_only() {
842 let ctrl = MockController;
843 let req = make_json_request("", Some("page=1&size=10"));
844 let data = ctrl.post_data(req).await.unwrap();
845 assert_eq!(data["page"], "1");
846 assert_eq!(data["size"], "10");
847 }
848
849 #[tokio::test]
850 async fn test_post_data_body_overrides_query() {
851 let ctrl = MockController;
853 let req = make_json_request(r#"{"page":99}"#, Some("page=1&size=10"));
854 let data = ctrl.post_data(req).await.unwrap();
855 assert_eq!(data["page"], 99);
856 assert_eq!(data["size"], "10");
857 }
858
859 #[tokio::test]
860 async fn test_post_data_by_key_exists() {
861 let ctrl = MockController;
862 let req = make_json_request(r#"{"name":"alice","age":30}"#, None);
863 let name = ctrl.post_data_by_key(req, "name").await.unwrap();
864 assert_eq!(name, Some(json!("alice")));
865 }
866
867 #[tokio::test]
868 async fn test_post_data_by_key_missing() {
869 let ctrl = MockController;
870 let req = make_json_request(r#"{"name":"alice"}"#, None);
871 let age = ctrl.post_data_by_key(req, "age").await.unwrap();
872 assert_eq!(age, None);
873 }
874
875 #[test]
880 fn test_get_data_query() {
881 let ctrl = MockController;
882 let req = make_get_request("page=1&size=10");
883 let data = ctrl.get_data(&req);
884 assert_eq!(data["page"], "1");
885 assert_eq!(data["size"], "10");
886 }
887
888 #[test]
889 fn test_get_data_empty_query() {
890 let ctrl = MockController;
891 let req = make_get_request("");
892 let data = ctrl.get_data(&req);
893 assert!(data.as_object().unwrap().is_empty());
894 }
895
896 #[test]
897 fn test_get_data_by_key_exists() {
898 let ctrl = MockController;
899 let req = make_get_request("page=1&size=10");
900 let page = ctrl.get_data_by_key(&req, "page");
901 assert_eq!(page, Some(json!("1")));
902 }
903
904 #[test]
905 fn test_get_data_by_key_missing() {
906 let ctrl = MockController;
907 let req = make_get_request("page=1");
908 let size = ctrl.get_data_by_key(&req, "size");
909 assert_eq!(size, None);
910 }
911
912 #[tokio::test]
917 async fn test_multiple_controllers_independent() {
918 struct CtrlA;
920 struct CtrlB;
921 impl SzController for CtrlA {}
922 impl SzController for CtrlB {}
923
924 let a = CtrlA;
925 let b = CtrlB;
926
927 let req_a = make_json_request(r#"{"k":"a"}"#, None);
928 let req_b = make_json_request(r#"{"k":"b"}"#, None);
929
930 let data_a = a.post_data(req_a).await.unwrap();
931 let data_b = b.post_data(req_b).await.unwrap();
932
933 assert_eq!(data_a["k"], "a");
934 assert_eq!(data_b["k"], "b");
935
936 let va = a.render_json(1, "a", json!({}));
938 let vb = b.render_json(0, "b", json!({}));
939 assert_eq!(va["code"], 1);
940 assert_eq!(va["msg"], "a");
941 assert_eq!(vb["code"], 0);
942 assert_eq!(vb["msg"], "b");
943 }
944
945 #[tokio::test]
950 async fn test_php_consistency_full_flow() {
951 struct OrderController;
953 impl SzController for OrderController {}
954
955 let ctrl = OrderController;
956
957 let req = make_json_request(r#"{"order_id":12345,"amount":99.5}"#, None);
959 let data = ctrl.post_data(req).await.unwrap();
960 let order_id = data["order_id"].as_i64().unwrap();
961 let amount = data["amount"].as_f64().unwrap();
962
963 let result = json!({
965 "order_id": order_id,
966 "amount": amount,
967 "status": "paid"
968 });
969
970 let resp = ctrl.render_success("支付成功", result);
972 let body = collect_body(resp).await;
973
974 assert_eq!(
976 body,
977 r#"{"code":1,"msg":"支付成功","data":{"order_id":12345,"amount":99.5,"status":"paid"}}"#
978 );
979 }
980
981 #[tokio::test]
982 async fn test_php_consistency_error_flow() {
983 struct UserController;
985 impl SzController for UserController {}
986
987 let ctrl = UserController;
988
989 let req = make_json_request(r#"{"name":""}"#, None);
990 let data = ctrl.post_data(req).await.unwrap();
991 let name = data["name"].as_str().unwrap();
992
993 if name.is_empty() {
994 let resp = ctrl.render_error("用户名不能为空", json!({"field": "name"}), 0);
995 let body = collect_body(resp).await;
996 assert_eq!(
997 body,
998 r#"{"code":0,"msg":"用户名不能为空","data":{"field":"name"}}"#
999 );
1000 } else {
1001 panic!("should be empty");
1002 }
1003 }
1004
1005 #[tokio::test]
1006 async fn test_php_consistency_not_login_flow() {
1007 struct PassportController;
1009 impl SzController for PassportController {}
1010
1011 let ctrl = PassportController;
1012 let resp = ctrl.render_error("not_login", json!({}), -1);
1013 let body = collect_body(resp).await;
1014 assert_eq!(body, r#"{"code":-1,"msg":"not_login","data":{}}"#);
1015 }
1016
1017 struct MockBaseController;
1023
1024 impl SzController for MockBaseController {}
1025 impl BaseController for MockBaseController {}
1026
1027 #[test]
1028 fn test_base_controller_default_batch_validate() {
1029 let ctrl = MockBaseController;
1031 assert!(!ctrl.batch_validate());
1032 }
1033
1034 #[test]
1035 fn test_base_controller_default_middlewares_empty() {
1036 let ctrl = MockBaseController;
1038 assert!(ctrl.middlewares().is_empty());
1039 }
1040
1041 #[test]
1042 fn test_base_controller_default_initialize_no_panic() {
1043 let ctrl = MockBaseController;
1045 ctrl.initialize(); }
1047
1048 #[test]
1049 fn test_base_controller_default_validate_returns_ok() {
1050 let ctrl = MockBaseController;
1052 let data = json!({"name": "alice"});
1053 let rules = [("name", "require")];
1054 let messages: [(&str, &str); 0] = [];
1055 let result = ctrl.validate(&data, &rules, &messages);
1056 assert!(result.is_ok());
1057 }
1058
1059 #[test]
1060 fn test_validate_require_pass_with_value() {
1061 let ctrl = MockBaseController;
1063 let data = json!({"name": "alice", "age": 30});
1064 let rules = [("name", "require"), ("age", "require|integer")];
1065 let messages: [(&str, &str); 0] = [];
1066 assert!(ctrl.validate(&data, &rules, &messages).is_ok());
1067 }
1068
1069 #[test]
1070 fn test_validate_require_fail_when_missing() {
1071 let ctrl = MockBaseController;
1073 let data = json!({"name": "alice"});
1074 let rules = [("name", "require"), ("age", "require|integer")];
1075 let messages: [(&str, &str); 0] = [];
1076 let result = ctrl.validate(&data, &rules, &messages);
1077 assert!(result.is_err());
1078 let err = result.unwrap_err();
1079 assert!(err.contains("age"), "error: {err}");
1081 }
1082
1083 #[test]
1084 fn test_validate_integer_fail_on_string() {
1085 let ctrl = MockBaseController;
1087 let data = json!({"age": "not-a-number"});
1088 let rules = [("age", "require|integer")];
1089 let messages: [(&str, &str); 0] = [];
1090 let result = ctrl.validate(&data, &rules, &messages);
1091 assert!(result.is_err());
1092 }
1093
1094 #[test]
1095 fn test_validate_custom_message_applied() {
1096 let ctrl = MockBaseController;
1098 let data = json!({}); let rules = [("name", "require")];
1100 let messages = [("name.require", "名称必填")];
1101 let result = ctrl.validate(&data, &rules, &messages);
1102 assert!(result.is_err());
1103 assert_eq!(result.unwrap_err(), "名称必填");
1104 }
1105
1106 #[test]
1107 fn test_validate_batch_mode_returns_multiple_errors() {
1108 struct BatchController;
1110 impl SzController for BatchController {}
1111 impl BaseController for BatchController {
1112 fn batch_validate(&self) -> bool {
1113 true
1114 }
1115 }
1116
1117 let ctrl = BatchController;
1118 let data = json!({}); let rules = [("name", "require"), ("age", "require")];
1120 let messages = [("name.require", "名称必填"), ("age.require", "年龄必填")];
1121 let result = ctrl.validate(&data, &rules, &messages);
1122 assert!(result.is_err());
1123 let err = result.unwrap_err();
1124 assert!(err.contains("名称必填"), "err: {err}");
1126 assert!(err.contains("年龄必填"), "err: {err}");
1127 assert!(err.contains("; "), "err: {err}");
1129 }
1130
1131 #[test]
1132 fn test_validate_single_mode_returns_first_error_only() {
1133 let ctrl = MockBaseController;
1135 let data = json!({}); let rules = [("name", "require"), ("age", "require")];
1137 let messages = [("name.require", "名称必填"), ("age.require", "年龄必填")];
1138 let result = ctrl.validate(&data, &rules, &messages);
1139 assert!(result.is_err());
1140 let err = result.unwrap_err();
1141 assert!(err.contains("名称必填"), "err: {err}");
1143 assert!(!err.contains("年龄必填"), "err: {err}");
1144 }
1145
1146 #[test]
1147 fn test_validate_in_rule_pass() {
1148 let ctrl = MockBaseController;
1150 let data = json!({"status": "active"});
1151 let rules = [("status", "require|in:active,inactive")];
1152 let messages: [(&str, &str); 0] = [];
1153 assert!(ctrl.validate(&data, &rules, &messages).is_ok());
1154 }
1155
1156 #[test]
1157 fn test_validate_in_rule_fail() {
1158 let ctrl = MockBaseController;
1160 let data = json!({"status": "deleted"});
1161 let rules = [("status", "require|in:active,inactive")];
1162 let messages: [(&str, &str); 0] = [];
1163 assert!(ctrl.validate(&data, &rules, &messages).is_err());
1164 }
1165
1166 #[test]
1167 fn test_validate_empty_rules_always_pass() {
1168 let ctrl = MockBaseController;
1170 let data = json!({"anything": "value"});
1171 let rules: [(&str, &str); 0] = [];
1172 let messages: [(&str, &str); 0] = [];
1173 assert!(ctrl.validate(&data, &rules, &messages).is_ok());
1174 }
1175
1176 #[test]
1177 fn test_base_controller_inherits_sz_controller_methods() {
1178 let ctrl = MockBaseController;
1180 let value = ctrl.render_json(1, "ok", json!({}));
1181 assert_eq!(value["code"], 1);
1182 assert_eq!(value["msg"], "ok");
1183
1184 let resp = ctrl.render_success("ok", json!({"id": 1}));
1185 assert_eq!(resp.status(), StatusCode::OK);
1186 }
1187
1188 struct CustomController;
1190
1191 impl SzController for CustomController {}
1192 impl BaseController for CustomController {
1193 fn batch_validate(&self) -> bool {
1194 true
1195 }
1196
1197 fn middlewares(&self) -> Vec<String> {
1198 vec!["auth".to_string(), "cors".to_string()]
1199 }
1200
1201 fn initialize(&self) {
1202 }
1204 }
1205
1206 #[test]
1207 fn test_base_controller_override_batch_validate() {
1208 let ctrl = CustomController;
1209 assert!(ctrl.batch_validate());
1210 }
1211
1212 #[test]
1213 fn test_base_controller_override_middlewares() {
1214 let ctrl = CustomController;
1215 let mws = ctrl.middlewares();
1216 assert_eq!(mws, vec!["auth", "cors"]);
1217 }
1218
1219 #[test]
1220 fn test_base_controller_override_initialize() {
1221 let ctrl = CustomController;
1222 ctrl.initialize(); }
1224
1225 struct StatefulController {
1228 initialized: parking_lot::Mutex<bool>,
1229 custom_batch: bool,
1230 }
1231
1232 impl StatefulController {
1233 fn new() -> Self {
1234 Self {
1235 initialized: parking_lot::Mutex::new(false),
1236 custom_batch: false,
1237 }
1238 }
1239 }
1240
1241 impl SzController for StatefulController {}
1242 impl BaseController for StatefulController {
1243 fn batch_validate(&self) -> bool {
1244 self.custom_batch
1245 }
1246
1247 fn initialize(&self) {
1248 *self.initialized.lock() = true;
1249 }
1252 }
1253
1254 #[test]
1255 fn test_base_controller_stateful_initialize() {
1256 let ctrl = StatefulController::new();
1257 assert!(!*ctrl.initialized.lock()); ctrl.initialize(); assert!(*ctrl.initialized.lock()); }
1261
1262 #[tokio::test]
1264 async fn test_base_controller_php_full_flow() {
1265 struct UserController;
1266 impl SzController for UserController {}
1267 impl BaseController for UserController {}
1268
1269 let ctrl = UserController;
1270
1271 ctrl.initialize();
1273
1274 let req = make_json_request(r#"{"name":"alice","age":30}"#, None);
1276 let data = ctrl.post_data(req).await.unwrap();
1277
1278 let rules = [("name", "require"), ("age", "require|integer|gt:0")];
1280 let messages: [(&str, &str); 0] = [];
1281 ctrl.validate(&data, &rules, &messages).unwrap();
1282
1283 let resp =
1285 ctrl.render_success("success", json!({"name": data["name"], "age": data["age"]}));
1286 let body = collect_body(resp).await;
1287 assert_eq!(
1288 body,
1289 r#"{"code":1,"msg":"success","data":{"name":"alice","age":30}}"#
1290 );
1291 }
1292
1293 #[test]
1295 fn test_base_controller_multiple_instances_independent() {
1296 struct ControllerA;
1297 struct ControllerB;
1298
1299 impl SzController for ControllerA {}
1300 impl BaseController for ControllerA {
1301 fn middlewares(&self) -> Vec<String> {
1302 vec!["auth".to_string()]
1303 }
1304 }
1305
1306 impl SzController for ControllerB {}
1307 impl BaseController for ControllerB {
1308 fn middlewares(&self) -> Vec<String> {
1309 vec!["cors".to_string(), "log".to_string()]
1310 }
1311 }
1312
1313 let a = ControllerA;
1314 let b = ControllerB;
1315
1316 assert_eq!(a.middlewares(), vec!["auth"]);
1317 assert_eq!(b.middlewares(), vec!["cors", "log"]);
1318
1319 assert!(!a.batch_validate());
1321 assert!(!b.batch_validate());
1322 }
1323
1324 #[test]
1326 fn test_base_controller_inheritance_chain() {
1327 struct BusinessController;
1329 impl SzController for BusinessController {}
1330 impl BaseController for BusinessController {}
1331
1332 let ctrl = BusinessController;
1333
1334 let value = ctrl.render_json(0, "error", json!({}));
1336 assert!(value.is_object());
1337
1338 assert!(!ctrl.batch_validate());
1340 assert!(ctrl.middlewares().is_empty());
1341 ctrl.initialize();
1342 }
1343
1344 struct MockAddonsController;
1350
1351 impl SzController for MockAddonsController {}
1352 impl BaseController for MockAddonsController {}
1353 impl AddonsBaseController for MockAddonsController {}
1354
1355 #[test]
1356 fn test_addons_default_allow_all_action() {
1357 let ctrl = MockAddonsController;
1359 let allow = ctrl.allow_all_action();
1360 assert!(allow.contains(&"/passport/login"));
1361 assert!(allow.contains(&"/task/task/userClerk"));
1362 assert_eq!(allow.len(), 2);
1363 }
1364
1365 #[test]
1366 fn test_addons_parse_route_info_two_segments() {
1367 let ctrl = MockAddonsController;
1369 let info = ctrl.parse_route_info("/passport/login");
1370 assert_eq!(info.controller, "passport");
1371 assert_eq!(info.action, "login");
1372 assert_eq!(info.group, "passport");
1373 assert_eq!(info.route_uri, "/passport/login");
1374 }
1375
1376 #[test]
1377 fn test_addons_parse_route_info_three_segments() {
1378 let ctrl = MockAddonsController;
1381 let info = ctrl.parse_route_info("/task/task/userClerk");
1382 assert_eq!(info.controller, "task/task");
1383 assert_eq!(info.action, "userClerk");
1384 assert_eq!(info.group, "task/task"); assert_eq!(info.route_uri, "/task/task/userClerk");
1386 }
1387
1388 #[test]
1389 fn test_addons_parse_route_info_single_segment() {
1390 let ctrl = MockAddonsController;
1392 let info = ctrl.parse_route_info("/passport");
1393 assert_eq!(info.controller, "passport");
1394 assert_eq!(info.action, "");
1395 assert_eq!(info.group, "passport");
1396 assert_eq!(info.route_uri, "/passport/");
1397 }
1398
1399 #[test]
1400 fn test_addons_parse_route_info_root() {
1401 let ctrl = MockAddonsController;
1403 let info = ctrl.parse_route_info("/");
1404 assert_eq!(info.controller, "");
1405 assert_eq!(info.action, "");
1406 assert_eq!(info.group, "");
1407 assert_eq!(info.route_uri, "/");
1408 }
1409
1410 #[test]
1411 fn test_addons_parse_route_info_empty_uri() {
1412 let ctrl = MockAddonsController;
1413 let info = ctrl.parse_route_info("");
1414 assert_eq!(info.controller, "");
1415 assert_eq!(info.route_uri, "/");
1416 }
1417
1418 #[test]
1419 fn test_addons_parse_route_info_with_query_string() {
1420 let ctrl = MockAddonsController;
1422 let info = ctrl.parse_route_info("/user/info?foo=bar&baz=1");
1423 assert_eq!(info.controller, "user");
1424 assert_eq!(info.action, "info");
1425 assert_eq!(info.route_uri, "/user/info");
1426 }
1427
1428 #[test]
1429 fn test_addons_parse_route_info_trailing_slash() {
1430 let ctrl = MockAddonsController;
1432 let info = ctrl.parse_route_info("/user/info/");
1433 assert_eq!(info.controller, "user/info");
1435 assert_eq!(info.action, "");
1436 assert_eq!(info.route_uri, "/user/info/");
1437 }
1438
1439 #[test]
1440 fn test_addons_check_login_whitelist_pass() {
1441 let ctrl = MockAddonsController;
1443 let result = ctrl.check_login("/passport/login", false);
1444 assert!(result.is_ok());
1445 }
1446
1447 #[test]
1448 fn test_addons_check_login_whitelist_pass_2() {
1449 let ctrl = MockAddonsController;
1451 let result = ctrl.check_login("/task/task/userClerk", false);
1452 assert!(result.is_ok());
1453 }
1454
1455 #[test]
1456 fn test_addons_check_login_logged_in_pass() {
1457 let ctrl = MockAddonsController;
1459 let result = ctrl.check_login("/user/info", true);
1460 assert!(result.is_ok());
1461 }
1462
1463 #[test]
1464 fn test_addons_check_login_not_logged_in_fail() {
1465 let ctrl = MockAddonsController;
1467 let result = ctrl.check_login("/user/info", false);
1468 assert!(result.is_err());
1469 assert_eq!(result.unwrap_err(), "not_login");
1470 }
1471
1472 #[test]
1473 fn test_addons_get_token_default_returns_none() {
1474 let ctrl = MockAddonsController;
1477 let result = ctrl.get_token(Some("Bearer xxx.yyy.zzz"));
1478 assert!(result.is_ok());
1479 assert_eq!(result.unwrap(), None);
1480 }
1481
1482 #[test]
1483 fn test_addons_get_token_no_authorization() {
1484 let ctrl = MockAddonsController;
1485 let result = ctrl.get_token(None);
1486 assert!(result.is_ok());
1487 assert_eq!(result.unwrap(), None);
1488 }
1489
1490 #[test]
1491 fn test_addons_get_token_empty_authorization() {
1492 let ctrl = MockAddonsController;
1493 let result = ctrl.get_token(Some(""));
1494 assert!(result.is_ok());
1495 assert_eq!(result.unwrap(), None);
1496 }
1497
1498 #[test]
1499 fn test_addons_get_token_invalid_format_returns_none() {
1500 let ctrl = MockAddonsController;
1503 let result = ctrl.get_token(Some("Bearer not.a.valid.jwt.token"));
1504 assert!(result.is_ok());
1505 assert_eq!(result.unwrap(), None);
1507 }
1508
1509 #[test]
1510 fn test_strip_bearer_prefix_uppercase() {
1511 assert_eq!(strip_bearer_prefix("Bearer abc.def.ghi"), "abc.def.ghi");
1512 }
1513
1514 #[test]
1515 fn test_strip_bearer_prefix_lowercase() {
1516 assert_eq!(strip_bearer_prefix("bearer abc.def.ghi"), "abc.def.ghi");
1517 }
1518
1519 #[test]
1520 fn test_strip_bearer_prefix_mixed_case() {
1521 assert_eq!(strip_bearer_prefix("BEARER abc.def.ghi"), "abc.def.ghi");
1522 }
1523
1524 #[test]
1525 fn test_strip_bearer_prefix_no_prefix() {
1526 assert_eq!(strip_bearer_prefix("abc.def.ghi"), "abc.def.ghi");
1528 }
1529
1530 #[test]
1531 fn test_strip_bearer_prefix_empty() {
1532 assert_eq!(strip_bearer_prefix(""), "");
1533 }
1534
1535 #[test]
1536 fn test_strip_bearer_prefix_with_extra_spaces() {
1537 assert_eq!(
1538 strip_bearer_prefix(" Bearer abc.def.ghi "),
1539 "abc.def.ghi"
1540 );
1541 }
1542
1543 #[test]
1548 fn test_get_token_valid_jwt_returns_user_info() {
1549 let config = JwtConfig {
1550 secret: "test-secret".to_string(),
1551 issuer: String::new(), };
1553
1554 let encoder = sz_orm_auth::jwt::JwtEncoder::new(&config.secret);
1556 let exp = std::time::SystemTime::now()
1557 .duration_since(std::time::UNIX_EPOCH)
1558 .unwrap()
1559 .as_secs() as i64
1560 + 3600; let claims = sz_orm_auth::jwt::JwtClaims::new("user123", exp).with_user_id(12345);
1562 let token = encoder.encode(&claims).unwrap();
1563
1564 let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1565 assert!(result.is_ok());
1566 let user = result.unwrap();
1567 assert!(user.is_some());
1568 let user = user.unwrap();
1569 assert_eq!(user.user_id, 12345);
1570 assert!(user.is_login);
1571 }
1572
1573 #[test]
1575 fn test_get_token_wrong_secret_returns_none() {
1576 let config = JwtConfig {
1577 secret: "correct-secret".to_string(),
1578 issuer: String::new(),
1579 };
1580
1581 let encoder = sz_orm_auth::jwt::JwtEncoder::new("wrong-secret");
1583 let exp = std::time::SystemTime::now()
1584 .duration_since(std::time::UNIX_EPOCH)
1585 .unwrap()
1586 .as_secs() as i64
1587 + 3600;
1588 let claims = sz_orm_auth::jwt::JwtClaims::new("user123", exp).with_user_id(12345);
1589 let token = encoder.encode(&claims).unwrap();
1590
1591 let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1592 assert!(result.is_ok());
1593 assert_eq!(result.unwrap(), None); }
1595
1596 #[test]
1598 fn test_get_token_expired_returns_none() {
1599 let config = JwtConfig {
1600 secret: "test-secret".to_string(),
1601 issuer: String::new(),
1602 };
1603
1604 let encoder = sz_orm_auth::jwt::JwtEncoder::new(&config.secret);
1605 let exp = std::time::SystemTime::now()
1607 .duration_since(std::time::UNIX_EPOCH)
1608 .unwrap()
1609 .as_secs() as i64
1610 - 3600;
1611 let claims = sz_orm_auth::jwt::JwtClaims::new("user123", exp).with_user_id(12345);
1612 let token = encoder.encode(&claims).unwrap();
1613
1614 let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1615 assert!(result.is_ok());
1616 assert_eq!(result.unwrap(), None); }
1618
1619 #[test]
1621 fn test_get_token_no_user_id_claim_returns_none() {
1622 let config = JwtConfig {
1623 secret: "test-secret".to_string(),
1624 issuer: String::new(),
1625 };
1626
1627 let encoder = sz_orm_auth::jwt::JwtEncoder::new(&config.secret);
1628 let exp = std::time::SystemTime::now()
1629 .duration_since(std::time::UNIX_EPOCH)
1630 .unwrap()
1631 .as_secs() as i64
1632 + 3600;
1633 let claims = sz_orm_auth::jwt::JwtClaims::new("user123", exp);
1635 let token = encoder.encode(&claims).unwrap();
1636
1637 let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1638 assert!(result.is_ok());
1639 assert_eq!(result.unwrap(), None); }
1641
1642 #[test]
1644 fn test_get_token_iss_mismatch_returns_none() {
1645 let config = JwtConfig {
1646 secret: "test-secret".to_string(),
1647 issuer: "https://expected-issuer.com".to_string(),
1648 };
1649
1650 let encoder = sz_orm_auth::jwt::JwtEncoder::new(&config.secret);
1651 let exp = std::time::SystemTime::now()
1652 .duration_since(std::time::UNIX_EPOCH)
1653 .unwrap()
1654 .as_secs() as i64
1655 + 3600;
1656 let claims = sz_orm_auth::jwt::JwtClaims::new("user123", exp)
1658 .with_issuer("https://wrong-issuer.com")
1659 .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_iss_match_returns_user_info() {
1670 let config = JwtConfig {
1671 secret: "test-secret".to_string(),
1672 issuer: "https://mall.ljclz.shop".to_string(),
1673 };
1674
1675 let encoder = sz_orm_auth::jwt::JwtEncoder::new(&config.secret);
1676 let exp = std::time::SystemTime::now()
1677 .duration_since(std::time::UNIX_EPOCH)
1678 .unwrap()
1679 .as_secs() as i64
1680 + 3600;
1681 let claims = sz_orm_auth::jwt::JwtClaims::new("user123", exp)
1682 .with_issuer(&config.issuer)
1683 .with_user_id(67890);
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 let user = result.unwrap().unwrap();
1689 assert_eq!(user.user_id, 67890);
1690 assert!(user.is_login);
1691 }
1692
1693 #[test]
1695 fn test_get_token_empty_secret_returns_none() {
1696 let config = JwtConfig::default(); let result = verify_token_with_config(Some("Bearer any.token.here"), &config);
1699 assert!(result.is_ok());
1700 assert_eq!(result.unwrap(), None); }
1702
1703 #[test]
1705 fn test_get_token_without_bearer_prefix() {
1706 let config = JwtConfig {
1707 secret: "test-secret".to_string(),
1708 issuer: String::new(),
1709 };
1710
1711 let encoder = sz_orm_auth::jwt::JwtEncoder::new(&config.secret);
1712 let exp = std::time::SystemTime::now()
1713 .duration_since(std::time::UNIX_EPOCH)
1714 .unwrap()
1715 .as_secs() as i64
1716 + 3600;
1717 let claims = sz_orm_auth::jwt::JwtClaims::new("user123", exp).with_user_id(99999);
1718 let token = encoder.encode(&claims).unwrap();
1719
1720 let result = verify_token_with_config(Some(&token), &config);
1722 assert!(result.is_ok());
1723 let user = result.unwrap().unwrap();
1724 assert_eq!(user.user_id, 99999);
1725 }
1726
1727 struct CustomAddonsController;
1729
1730 impl SzController for CustomAddonsController {}
1731 impl BaseController for CustomAddonsController {}
1732 impl AddonsBaseController for CustomAddonsController {
1733 fn allow_all_action(&self) -> Vec<&'static str> {
1734 vec!["/custom/public", "/custom/health"]
1735 }
1736 }
1737
1738 #[test]
1739 fn test_addons_override_allow_all_action() {
1740 let ctrl = CustomAddonsController;
1741 let allow = ctrl.allow_all_action();
1742 assert_eq!(allow, vec!["/custom/public", "/custom/health"]);
1743 assert!(!allow.contains(&"/passport/login"));
1745 }
1746
1747 #[test]
1748 fn test_addons_override_check_login_uses_new_whitelist() {
1749 let ctrl = CustomAddonsController;
1750 assert!(ctrl.check_login("/custom/public", false).is_ok());
1752 assert!(ctrl.check_login("/passport/login", false).is_err());
1754 }
1755
1756 #[tokio::test]
1758 async fn test_addons_php_full_flow_whitelist() {
1759 struct PassportController;
1760 impl SzController for PassportController {}
1761 impl BaseController for PassportController {}
1762 impl AddonsBaseController for PassportController {}
1763
1764 let ctrl = PassportController;
1765
1766 let info = ctrl.parse_route_info("/passport/login");
1768 assert_eq!(info.route_uri, "/passport/login");
1769
1770 let user = ctrl.get_token(None).unwrap();
1772
1773 let is_login = user.as_ref().is_some_and(|u| u.is_login);
1775 ctrl.check_login(&info.route_uri, is_login).unwrap();
1776
1777 let resp = ctrl.render_success("登录成功", json!({"token": "fake.jwt.token"}));
1779 let body = collect_body(resp).await;
1780 assert_eq!(
1781 body,
1782 r#"{"code":1,"msg":"登录成功","data":{"token":"fake.jwt.token"}}"#
1783 );
1784 }
1785
1786 #[tokio::test]
1787 async fn test_addons_php_full_flow_not_login() {
1788 struct UserController;
1789 impl SzController for UserController {}
1790 impl BaseController for UserController {}
1791 impl AddonsBaseController for UserController {}
1792
1793 let ctrl = UserController;
1794
1795 let info = ctrl.parse_route_info("/user/info");
1797
1798 let user = ctrl.get_token(None).unwrap();
1800 let is_login = user.as_ref().is_some_and(|u| u.is_login);
1801
1802 let result = ctrl.check_login(&info.route_uri, is_login);
1804 assert!(result.is_err());
1805 assert_eq!(result.unwrap_err(), "not_login");
1806
1807 let resp = ctrl.render_error("not_login", json!({}), -1);
1809 let body = collect_body(resp).await;
1810 assert_eq!(body, r#"{"code":-1,"msg":"not_login","data":{}}"#);
1811 }
1812
1813 #[tokio::test]
1814 async fn test_addons_php_full_flow_logged_in() {
1815 struct OrderController;
1816 impl SzController for OrderController {}
1817 impl BaseController for OrderController {}
1818 impl AddonsBaseController for OrderController {
1819 fn get_token(&self, _authorization: Option<&str>) -> Result<Option<UserInfo>, String> {
1821 Ok(Some(UserInfo {
1822 user_id: 12345,
1823 is_login: true,
1824 }))
1825 }
1826 }
1827
1828 let ctrl = OrderController;
1829
1830 let info = ctrl.parse_route_info("/order/list");
1832
1833 let user = ctrl.get_token(None).unwrap();
1835 let is_login = user.as_ref().is_some_and(|u| u.is_login);
1836
1837 ctrl.check_login(&info.route_uri, is_login).unwrap();
1839
1840 let resp = ctrl.render_success(
1842 "success",
1843 json!({"list": [{"id": 1}, {"id": 2}], "total": 2}),
1844 );
1845 let body = collect_body(resp).await;
1846 assert_eq!(
1847 body,
1848 r#"{"code":1,"msg":"success","data":{"list":[{"id":1},{"id":2}],"total":2}}"#
1849 );
1850 }
1851
1852 #[test]
1854 fn test_route_info_debug_clone_eq() {
1855 let info1 = RouteInfo {
1856 controller: "user".to_string(),
1857 action: "info".to_string(),
1858 route_uri: "/user/info".to_string(),
1859 group: "user".to_string(),
1860 };
1861 let info2 = info1.clone();
1862 assert_eq!(info1, info2);
1863 let debug_str = format!("{info1:?}");
1864 assert!(debug_str.contains("RouteInfo"));
1865 assert!(debug_str.contains("user"));
1866 }
1867
1868 #[test]
1869 fn test_user_info_debug_clone_eq() {
1870 let user1 = UserInfo {
1871 user_id: 12345,
1872 is_login: true,
1873 };
1874 let user2 = user1.clone();
1875 assert_eq!(user1, user2);
1876 let debug_str = format!("{user1:?}");
1877 assert!(debug_str.contains("UserInfo"));
1878 assert!(debug_str.contains("12345"));
1879 }
1880
1881 #[test]
1883 fn test_addons_inheritance_chain_all_methods() {
1884 struct BusinessController;
1885 impl SzController for BusinessController {}
1886 impl BaseController for BusinessController {}
1887 impl AddonsBaseController for BusinessController {}
1888
1889 let ctrl = BusinessController;
1890
1891 let value = ctrl.render_json(1, "ok", json!({}));
1893 assert_eq!(value["code"], 1);
1894
1895 assert!(!ctrl.batch_validate());
1897 assert!(ctrl.middlewares().is_empty());
1898 ctrl.initialize();
1899
1900 let info = ctrl.parse_route_info("/test/action");
1902 assert_eq!(info.route_uri, "/test/action");
1903 assert!(ctrl.check_login("/passport/login", false).is_ok());
1904 assert!(ctrl.get_token(None).unwrap().is_none());
1905 }
1906}