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
656use parking_lot::RwLock;
661use sha2::{Digest, Sha256};
662use std::time::Duration;
663
664pub struct KeyRotation {
669 current: RwLock<String>,
671 previous: RwLock<Vec<(String, std::time::Instant)>>,
673 rotation_interval: Duration,
675 grace_period: Duration,
677 max_previous: usize,
679}
680
681impl std::fmt::Debug for KeyRotation {
682 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
683 f.debug_struct("KeyRotation")
684 .field("current", &"[REDACTED]")
685 .field("previous", &"[REDACTED]")
686 .field("rotation_interval", &self.rotation_interval)
687 .field("grace_period", &self.grace_period)
688 .field("max_previous", &self.max_previous)
689 .finish()
690 }
691}
692
693#[derive(Debug, thiserror::Error)]
695pub enum KeyRotationError {
696 #[error("SZ300_JWT_SECRET 环境变量未设置")]
698 SecretMissing,
699 #[error("Token 验证失败:所有密钥均无法解码")]
701 InvalidToken,
702 #[error("Token 签发失败:{0}")]
704 SignError(String),
705}
706
707impl KeyRotation {
708 pub fn from_env() -> Result<Self, KeyRotationError> {
714 let current =
715 std::env::var("SZ300_JWT_SECRET").map_err(|_| KeyRotationError::SecretMissing)?;
716 if current.is_empty() {
717 return Err(KeyRotationError::SecretMissing);
718 }
719
720 let rotation_interval = std::env::var("SZ300_JWT_ROTATION_INTERVAL")
721 .ok()
722 .and_then(|s| s.parse().ok())
723 .map(Duration::from_secs)
724 .unwrap_or(Duration::from_secs(86400));
725
726 let grace_period = std::env::var("SZ300_JWT_GRACE_PERIOD")
727 .ok()
728 .and_then(|s| s.parse().ok())
729 .map(Duration::from_secs)
730 .unwrap_or(Duration::from_secs(3600));
731
732 Ok(Self {
733 current: RwLock::new(current),
734 previous: RwLock::new(Vec::new()),
735 rotation_interval,
736 grace_period,
737 max_previous: 3,
738 })
739 }
740
741 pub fn new(
743 current: String,
744 rotation_interval: Duration,
745 grace_period: Duration,
746 max_previous: usize,
747 ) -> Self {
748 Self {
749 current: RwLock::new(current),
750 previous: RwLock::new(Vec::new()),
751 rotation_interval,
752 grace_period,
753 max_previous,
754 }
755 }
756
757 pub fn sign_token(
759 &self,
760 claims: &sz_rust_orm_facade::jwt::JwtClaims,
761 ) -> Result<String, KeyRotationError> {
762 let secret = self.current.read().clone();
763 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&secret);
764 encoder
765 .encode(claims)
766 .map_err(|e| KeyRotationError::SignError(e.to_string()))
767 }
768
769 pub fn verify_token(
771 &self,
772 token: &str,
773 ) -> Result<sz_rust_orm_facade::jwt::JwtClaims, KeyRotationError> {
774 let current_secret = self.current.read().clone();
776 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(¤t_secret);
777 if let Ok(claims) = encoder.decode(token) {
778 return Ok(claims);
779 }
780
781 let now = std::time::Instant::now();
783 let previous = self.previous.read();
784 for (key, expires_at) in previous.iter() {
785 if now < *expires_at {
786 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(key);
787 if let Ok(claims) = encoder.decode(token) {
788 return Ok(claims);
789 }
790 }
791 }
792
793 Err(KeyRotationError::InvalidToken)
794 }
795
796 pub fn spawn_rotation_task(self: std::sync::Arc<Self>) -> tokio::task::JoinHandle<()> {
798 let interval = self.rotation_interval;
799 let grace_period = self.grace_period;
800 let max_previous = self.max_previous;
801
802 tokio::spawn(async move {
803 let mut ticker = tokio::time::interval(interval);
804 ticker.tick().await; loop {
807 ticker.tick().await;
808 if let Err(e) = Self::do_rotation(&self, grace_period, max_previous).await {
809 tracing::error!("JWT_KEY_ROTATION_FAILED: {e}");
810 }
811 }
812 })
813 }
814
815 pub async fn do_rotation(
817 &self,
818 grace_period: Duration,
819 max_previous: usize,
820 ) -> Result<(), String> {
821 let new_key = {
823 use rand::Rng;
824 let mut rng = rand::thread_rng();
825 let bytes: [u8; 32] = rng.gen();
826 hex::encode(bytes)
827 };
828
829 let now = std::time::Instant::now();
830 let expires_at = now + grace_period;
831
832 let old_current = {
834 let mut current = self.current.write();
835 let old = current.clone();
836 *current = new_key.clone();
837 old
838 };
839
840 {
841 let mut previous = self.previous.write();
842 previous.push((old_current.clone(), expires_at));
843 while previous.len() > max_previous {
845 previous.remove(0);
846 }
847 previous.retain(|(_, exp)| now < *exp);
849 }
850
851 let old_fp = Self::fingerprint(&old_current);
852 let new_fp = Self::fingerprint(&new_key);
853 tracing::info!("JWT_KEY_ROTATED: old_fingerprint={old_fp}, new_fingerprint={new_fp}");
854
855 Ok(())
856 }
857
858 pub fn fingerprint(key: &str) -> String {
860 let mut hasher = Sha256::new();
861 hasher.update(key.as_bytes());
862 let result = hasher.finalize();
863 hex::encode(&result[..4])
864 }
865
866 pub fn current_fingerprint(&self) -> String {
868 let current = self.current.read();
869 Self::fingerprint(¤t)
870 }
871}
872
873#[cfg(test)]
874mod tests {
875 use super::*;
876 use axum::body::Body;
877 use axum::http::{Method, Request, StatusCode};
878 use http_body_util::BodyExt;
879 use serde_json::json;
880 use tower::ServiceExt;
881
882 struct MockController;
884
885 impl SzController for MockController {}
886
887 fn make_json_request(body: &str, query: Option<&str>) -> Request<Body> {
888 let uri = match query {
889 Some(q) => format!("/?{q}"),
890 None => "/".to_string(),
891 };
892 Request::builder()
893 .method(Method::POST)
894 .uri(&uri)
895 .header("content-type", "application/json")
896 .body(Body::from(body.to_string()))
897 .unwrap()
898 }
899
900 fn make_get_request(query: &str) -> Request<Body> {
901 Request::builder()
902 .method(Method::GET)
903 .uri(format!("/?{query}"))
904 .body(Body::empty())
905 .unwrap()
906 }
907
908 async fn collect_body(resp: Response) -> String {
909 let bytes = resp.into_body().collect().await.unwrap().to_bytes();
910 String::from_utf8(bytes.to_vec()).unwrap()
911 }
912
913 #[test]
918 fn test_render_json_field_order() {
919 let ctrl = MockController;
921 let value = ctrl.render_json(1, "ok", json!({"id": 1}));
922 let obj = value.as_object().unwrap();
923 let keys: Vec<&String> = obj.keys().collect();
924 assert_eq!(keys, vec!["code", "msg", "data"]);
925 }
926
927 #[test]
928 fn test_render_json_default_values() {
929 let ctrl = MockController;
931 let value = ctrl.render_json(1, "", Value::Object(Map::new()));
932 assert_eq!(value["code"], 1);
933 assert_eq!(value["msg"], "");
934 assert!(value["data"].is_object());
935 assert!(value["data"].as_object().unwrap().is_empty());
936 }
937
938 #[test]
939 fn test_render_json_custom_code() {
940 let ctrl = MockController;
941 let value = ctrl.render_json(-1, "未登录", json!({}));
942 assert_eq!(value["code"], -1);
943 assert_eq!(value["msg"], "未登录");
944 }
945
946 #[test]
947 fn test_render_json_compact_semantics() {
948 let ctrl = MockController;
951 let value = ctrl.render_json(0, "失败", json!({"field": "name"}));
952 let json_str = value.to_string();
953 assert_eq!(
955 json_str,
956 r#"{"code":0,"msg":"失败","data":{"field":"name"}}"#
957 );
958 }
959
960 #[test]
961 fn test_render_json_returns_value_not_response() {
962 let ctrl = MockController;
964 let value = ctrl.render_json(1, "ok", json!({}));
965 assert!(value.is_object());
967 }
968
969 #[test]
974 fn test_render_success_returns_response() {
975 let ctrl = MockController;
976 let resp = ctrl.render_success("success", json!({"id": 1}));
977 assert_eq!(resp.status(), StatusCode::OK);
978 assert_eq!(
979 resp.headers().get("content-type").unwrap(),
980 "application/json; charset=utf-8"
981 );
982 }
983
984 #[tokio::test]
985 async fn test_render_success_body_format() {
986 let ctrl = MockController;
988 let resp = ctrl.render_success("success", json!({"id": 1, "name": "alice"}));
989 let body = collect_body(resp).await;
990 assert_eq!(
991 body,
992 r#"{"code":1,"msg":"success","data":{"id":1,"name":"alice"}}"#
993 );
994 }
995
996 #[tokio::test]
997 async fn test_render_success_default_msg() {
998 let ctrl = MockController;
1000 let resp = ctrl.render_success("success", json!({}));
1001 let body = collect_body(resp).await;
1002 assert_eq!(body, r#"{"code":1,"msg":"success","data":{}}"#);
1003 }
1004
1005 #[tokio::test]
1006 async fn test_render_success_via_axum_router() {
1007 struct UserController;
1009 impl SzController for UserController {}
1010
1011 async fn handler() -> Response {
1012 let ctrl = UserController;
1013 ctrl.render_success("ok", json!({"id": 1}))
1014 }
1015
1016 let router = axum::Router::new().route("/", axum::routing::get(handler));
1017 let req = Request::builder()
1018 .method(Method::GET)
1019 .uri("/")
1020 .body(Body::empty())
1021 .unwrap();
1022 let resp = router.oneshot(req).await.unwrap();
1023
1024 assert_eq!(resp.status(), StatusCode::OK);
1025 let body = collect_body(resp).await;
1026 assert_eq!(body, r#"{"code":1,"msg":"ok","data":{"id":1}}"#);
1027 }
1028
1029 #[test]
1034 fn test_render_error_returns_response() {
1035 let ctrl = MockController;
1036 let resp = ctrl.render_error("error", json!({}), 0);
1037 assert_eq!(resp.status(), StatusCode::OK); }
1039
1040 #[tokio::test]
1041 async fn test_render_error_default_code() {
1042 let ctrl = MockController;
1044 let resp = ctrl.render_error("参数错误", json!({}), 0);
1045 let body = collect_body(resp).await;
1046 assert_eq!(body, r#"{"code":0,"msg":"参数错误","data":{}}"#);
1047 }
1048
1049 #[tokio::test]
1050 async fn test_render_error_custom_code() {
1051 let ctrl = MockController;
1053 let resp = ctrl.render_error("not_login", json!({}), -1);
1054 let body = collect_body(resp).await;
1055 assert_eq!(body, r#"{"code":-1,"msg":"not_login","data":{}}"#);
1056 }
1057
1058 #[tokio::test]
1059 async fn test_render_error_with_data() {
1060 let ctrl = MockController;
1061 let resp = ctrl.render_error("失败", json!({"field": "name"}), 0);
1062 let body = collect_body(resp).await;
1063 assert_eq!(body, r#"{"code":0,"msg":"失败","data":{"field":"name"}}"#);
1064 }
1065
1066 #[tokio::test]
1067 async fn test_render_error_via_axum_router() {
1068 struct UserController;
1069 impl SzController for UserController {}
1070
1071 async fn handler() -> Response {
1072 let ctrl = UserController;
1073 ctrl.render_error("参数错误", json!({}), 0)
1074 }
1075
1076 let router = axum::Router::new().route("/", axum::routing::post(handler));
1077 let req = Request::builder()
1078 .method(Method::POST)
1079 .uri("/")
1080 .body(Body::empty())
1081 .unwrap();
1082 let resp = router.oneshot(req).await.unwrap();
1083
1084 assert_eq!(resp.status(), StatusCode::OK);
1085 let body = collect_body(resp).await;
1086 assert_eq!(body, r#"{"code":0,"msg":"参数错误","data":{}}"#);
1087 }
1088
1089 #[tokio::test]
1094 async fn test_post_data_json_body() {
1095 let ctrl = MockController;
1096 let req = make_json_request(r#"{"name":"alice","age":30}"#, None);
1097 let data = ctrl.post_data(req).await.unwrap();
1098 assert_eq!(data["name"], "alice");
1099 assert_eq!(data["age"], 30);
1100 }
1101
1102 #[tokio::test]
1103 async fn test_post_data_query_only() {
1104 let ctrl = MockController;
1105 let req = make_json_request("", Some("page=1&size=10"));
1106 let data = ctrl.post_data(req).await.unwrap();
1107 assert_eq!(data["page"], "1");
1108 assert_eq!(data["size"], "10");
1109 }
1110
1111 #[tokio::test]
1112 async fn test_post_data_body_overrides_query() {
1113 let ctrl = MockController;
1115 let req = make_json_request(r#"{"page":99}"#, Some("page=1&size=10"));
1116 let data = ctrl.post_data(req).await.unwrap();
1117 assert_eq!(data["page"], 99);
1118 assert_eq!(data["size"], "10");
1119 }
1120
1121 #[tokio::test]
1122 async fn test_post_data_by_key_exists() {
1123 let ctrl = MockController;
1124 let req = make_json_request(r#"{"name":"alice","age":30}"#, None);
1125 let name = ctrl.post_data_by_key(req, "name").await.unwrap();
1126 assert_eq!(name, Some(json!("alice")));
1127 }
1128
1129 #[tokio::test]
1130 async fn test_post_data_by_key_missing() {
1131 let ctrl = MockController;
1132 let req = make_json_request(r#"{"name":"alice"}"#, None);
1133 let age = ctrl.post_data_by_key(req, "age").await.unwrap();
1134 assert_eq!(age, None);
1135 }
1136
1137 #[test]
1142 fn test_get_data_query() {
1143 let ctrl = MockController;
1144 let req = make_get_request("page=1&size=10");
1145 let data = ctrl.get_data(&req);
1146 assert_eq!(data["page"], "1");
1147 assert_eq!(data["size"], "10");
1148 }
1149
1150 #[test]
1151 fn test_get_data_empty_query() {
1152 let ctrl = MockController;
1153 let req = make_get_request("");
1154 let data = ctrl.get_data(&req);
1155 assert!(data.as_object().unwrap().is_empty());
1156 }
1157
1158 #[test]
1159 fn test_get_data_by_key_exists() {
1160 let ctrl = MockController;
1161 let req = make_get_request("page=1&size=10");
1162 let page = ctrl.get_data_by_key(&req, "page");
1163 assert_eq!(page, Some(json!("1")));
1164 }
1165
1166 #[test]
1167 fn test_get_data_by_key_missing() {
1168 let ctrl = MockController;
1169 let req = make_get_request("page=1");
1170 let size = ctrl.get_data_by_key(&req, "size");
1171 assert_eq!(size, None);
1172 }
1173
1174 #[tokio::test]
1179 async fn test_multiple_controllers_independent() {
1180 struct CtrlA;
1182 struct CtrlB;
1183 impl SzController for CtrlA {}
1184 impl SzController for CtrlB {}
1185
1186 let a = CtrlA;
1187 let b = CtrlB;
1188
1189 let req_a = make_json_request(r#"{"k":"a"}"#, None);
1190 let req_b = make_json_request(r#"{"k":"b"}"#, None);
1191
1192 let data_a = a.post_data(req_a).await.unwrap();
1193 let data_b = b.post_data(req_b).await.unwrap();
1194
1195 assert_eq!(data_a["k"], "a");
1196 assert_eq!(data_b["k"], "b");
1197
1198 let va = a.render_json(1, "a", json!({}));
1200 let vb = b.render_json(0, "b", json!({}));
1201 assert_eq!(va["code"], 1);
1202 assert_eq!(va["msg"], "a");
1203 assert_eq!(vb["code"], 0);
1204 assert_eq!(vb["msg"], "b");
1205 }
1206
1207 #[tokio::test]
1212 async fn test_php_consistency_full_flow() {
1213 struct OrderController;
1215 impl SzController for OrderController {}
1216
1217 let ctrl = OrderController;
1218
1219 let req = make_json_request(r#"{"order_id":12345,"amount":99.5}"#, None);
1221 let data = ctrl.post_data(req).await.unwrap();
1222 let order_id = data["order_id"].as_i64().unwrap();
1223 let amount = data["amount"].as_f64().unwrap();
1224
1225 let result = json!({
1227 "order_id": order_id,
1228 "amount": amount,
1229 "status": "paid"
1230 });
1231
1232 let resp = ctrl.render_success("支付成功", result);
1234 let body = collect_body(resp).await;
1235
1236 assert_eq!(
1238 body,
1239 r#"{"code":1,"msg":"支付成功","data":{"order_id":12345,"amount":99.5,"status":"paid"}}"#
1240 );
1241 }
1242
1243 #[tokio::test]
1244 async fn test_php_consistency_error_flow() {
1245 struct UserController;
1247 impl SzController for UserController {}
1248
1249 let ctrl = UserController;
1250
1251 let req = make_json_request(r#"{"name":""}"#, None);
1252 let data = ctrl.post_data(req).await.unwrap();
1253 let name = data["name"].as_str().unwrap();
1254
1255 if name.is_empty() {
1256 let resp = ctrl.render_error("用户名不能为空", json!({"field": "name"}), 0);
1257 let body = collect_body(resp).await;
1258 assert_eq!(
1259 body,
1260 r#"{"code":0,"msg":"用户名不能为空","data":{"field":"name"}}"#
1261 );
1262 } else {
1263 panic!("should be empty");
1264 }
1265 }
1266
1267 #[tokio::test]
1268 async fn test_php_consistency_not_login_flow() {
1269 struct PassportController;
1271 impl SzController for PassportController {}
1272
1273 let ctrl = PassportController;
1274 let resp = ctrl.render_error("not_login", json!({}), -1);
1275 let body = collect_body(resp).await;
1276 assert_eq!(body, r#"{"code":-1,"msg":"not_login","data":{}}"#);
1277 }
1278
1279 struct MockBaseController;
1285
1286 impl SzController for MockBaseController {}
1287 impl BaseController for MockBaseController {}
1288
1289 #[test]
1290 fn test_base_controller_default_batch_validate() {
1291 let ctrl = MockBaseController;
1293 assert!(!ctrl.batch_validate());
1294 }
1295
1296 #[test]
1297 fn test_base_controller_default_middlewares_empty() {
1298 let ctrl = MockBaseController;
1300 assert!(ctrl.middlewares().is_empty());
1301 }
1302
1303 #[test]
1304 fn test_base_controller_default_initialize_no_panic() {
1305 let ctrl = MockBaseController;
1307 ctrl.initialize(); }
1309
1310 #[test]
1311 fn test_base_controller_default_validate_returns_ok() {
1312 let ctrl = MockBaseController;
1314 let data = json!({"name": "alice"});
1315 let rules = [("name", "require")];
1316 let messages: [(&str, &str); 0] = [];
1317 let result = ctrl.validate(&data, &rules, &messages);
1318 assert!(result.is_ok());
1319 }
1320
1321 #[test]
1322 fn test_validate_require_pass_with_value() {
1323 let ctrl = MockBaseController;
1325 let data = json!({"name": "alice", "age": 30});
1326 let rules = [("name", "require"), ("age", "require|integer")];
1327 let messages: [(&str, &str); 0] = [];
1328 assert!(ctrl.validate(&data, &rules, &messages).is_ok());
1329 }
1330
1331 #[test]
1332 fn test_validate_require_fail_when_missing() {
1333 let ctrl = MockBaseController;
1335 let data = json!({"name": "alice"});
1336 let rules = [("name", "require"), ("age", "require|integer")];
1337 let messages: [(&str, &str); 0] = [];
1338 let result = ctrl.validate(&data, &rules, &messages);
1339 assert!(result.is_err());
1340 let err = result.unwrap_err();
1341 assert!(err.contains("age"), "error: {err}");
1343 }
1344
1345 #[test]
1346 fn test_validate_integer_fail_on_string() {
1347 let ctrl = MockBaseController;
1349 let data = json!({"age": "not-a-number"});
1350 let rules = [("age", "require|integer")];
1351 let messages: [(&str, &str); 0] = [];
1352 let result = ctrl.validate(&data, &rules, &messages);
1353 assert!(result.is_err());
1354 }
1355
1356 #[test]
1357 fn test_validate_custom_message_applied() {
1358 let ctrl = MockBaseController;
1360 let data = json!({}); let rules = [("name", "require")];
1362 let messages = [("name.require", "名称必填")];
1363 let result = ctrl.validate(&data, &rules, &messages);
1364 assert!(result.is_err());
1365 assert_eq!(result.unwrap_err(), "名称必填");
1366 }
1367
1368 #[test]
1369 fn test_validate_batch_mode_returns_multiple_errors() {
1370 struct BatchController;
1372 impl SzController for BatchController {}
1373 impl BaseController for BatchController {
1374 fn batch_validate(&self) -> bool {
1375 true
1376 }
1377 }
1378
1379 let ctrl = BatchController;
1380 let data = json!({}); let rules = [("name", "require"), ("age", "require")];
1382 let messages = [("name.require", "名称必填"), ("age.require", "年龄必填")];
1383 let result = ctrl.validate(&data, &rules, &messages);
1384 assert!(result.is_err());
1385 let err = result.unwrap_err();
1386 assert!(err.contains("名称必填"), "err: {err}");
1388 assert!(err.contains("年龄必填"), "err: {err}");
1389 assert!(err.contains("; "), "err: {err}");
1391 }
1392
1393 #[test]
1394 fn test_validate_single_mode_returns_first_error_only() {
1395 let ctrl = MockBaseController;
1397 let data = json!({}); let rules = [("name", "require"), ("age", "require")];
1399 let messages = [("name.require", "名称必填"), ("age.require", "年龄必填")];
1400 let result = ctrl.validate(&data, &rules, &messages);
1401 assert!(result.is_err());
1402 let err = result.unwrap_err();
1403 assert!(err.contains("名称必填"), "err: {err}");
1405 assert!(!err.contains("年龄必填"), "err: {err}");
1406 }
1407
1408 #[test]
1409 fn test_validate_in_rule_pass() {
1410 let ctrl = MockBaseController;
1412 let data = json!({"status": "active"});
1413 let rules = [("status", "require|in:active,inactive")];
1414 let messages: [(&str, &str); 0] = [];
1415 assert!(ctrl.validate(&data, &rules, &messages).is_ok());
1416 }
1417
1418 #[test]
1419 fn test_validate_in_rule_fail() {
1420 let ctrl = MockBaseController;
1422 let data = json!({"status": "deleted"});
1423 let rules = [("status", "require|in:active,inactive")];
1424 let messages: [(&str, &str); 0] = [];
1425 assert!(ctrl.validate(&data, &rules, &messages).is_err());
1426 }
1427
1428 #[test]
1429 fn test_validate_empty_rules_always_pass() {
1430 let ctrl = MockBaseController;
1432 let data = json!({"anything": "value"});
1433 let rules: [(&str, &str); 0] = [];
1434 let messages: [(&str, &str); 0] = [];
1435 assert!(ctrl.validate(&data, &rules, &messages).is_ok());
1436 }
1437
1438 #[test]
1439 fn test_base_controller_inherits_sz_controller_methods() {
1440 let ctrl = MockBaseController;
1442 let value = ctrl.render_json(1, "ok", json!({}));
1443 assert_eq!(value["code"], 1);
1444 assert_eq!(value["msg"], "ok");
1445
1446 let resp = ctrl.render_success("ok", json!({"id": 1}));
1447 assert_eq!(resp.status(), StatusCode::OK);
1448 }
1449
1450 struct CustomController;
1452
1453 impl SzController for CustomController {}
1454 impl BaseController for CustomController {
1455 fn batch_validate(&self) -> bool {
1456 true
1457 }
1458
1459 fn middlewares(&self) -> Vec<String> {
1460 vec!["auth".to_string(), "cors".to_string()]
1461 }
1462
1463 fn initialize(&self) {
1464 }
1466 }
1467
1468 #[test]
1469 fn test_base_controller_override_batch_validate() {
1470 let ctrl = CustomController;
1471 assert!(ctrl.batch_validate());
1472 }
1473
1474 #[test]
1475 fn test_base_controller_override_middlewares() {
1476 let ctrl = CustomController;
1477 let mws = ctrl.middlewares();
1478 assert_eq!(mws, vec!["auth", "cors"]);
1479 }
1480
1481 #[test]
1482 fn test_base_controller_override_initialize() {
1483 let ctrl = CustomController;
1484 ctrl.initialize(); }
1486
1487 struct StatefulController {
1490 initialized: parking_lot::Mutex<bool>,
1491 custom_batch: bool,
1492 }
1493
1494 impl StatefulController {
1495 fn new() -> Self {
1496 Self {
1497 initialized: parking_lot::Mutex::new(false),
1498 custom_batch: false,
1499 }
1500 }
1501 }
1502
1503 impl SzController for StatefulController {}
1504 impl BaseController for StatefulController {
1505 fn batch_validate(&self) -> bool {
1506 self.custom_batch
1507 }
1508
1509 fn initialize(&self) {
1510 *self.initialized.lock() = true;
1511 }
1514 }
1515
1516 #[test]
1517 fn test_base_controller_stateful_initialize() {
1518 let ctrl = StatefulController::new();
1519 assert!(!*ctrl.initialized.lock()); ctrl.initialize(); assert!(*ctrl.initialized.lock()); }
1523
1524 #[tokio::test]
1526 async fn test_base_controller_php_full_flow() {
1527 struct UserController;
1528 impl SzController for UserController {}
1529 impl BaseController for UserController {}
1530
1531 let ctrl = UserController;
1532
1533 ctrl.initialize();
1535
1536 let req = make_json_request(r#"{"name":"alice","age":30}"#, None);
1538 let data = ctrl.post_data(req).await.unwrap();
1539
1540 let rules = [("name", "require"), ("age", "require|integer|gt:0")];
1542 let messages: [(&str, &str); 0] = [];
1543 ctrl.validate(&data, &rules, &messages).unwrap();
1544
1545 let resp =
1547 ctrl.render_success("success", json!({"name": data["name"], "age": data["age"]}));
1548 let body = collect_body(resp).await;
1549 assert_eq!(
1550 body,
1551 r#"{"code":1,"msg":"success","data":{"name":"alice","age":30}}"#
1552 );
1553 }
1554
1555 #[test]
1557 fn test_base_controller_multiple_instances_independent() {
1558 struct ControllerA;
1559 struct ControllerB;
1560
1561 impl SzController for ControllerA {}
1562 impl BaseController for ControllerA {
1563 fn middlewares(&self) -> Vec<String> {
1564 vec!["auth".to_string()]
1565 }
1566 }
1567
1568 impl SzController for ControllerB {}
1569 impl BaseController for ControllerB {
1570 fn middlewares(&self) -> Vec<String> {
1571 vec!["cors".to_string(), "log".to_string()]
1572 }
1573 }
1574
1575 let a = ControllerA;
1576 let b = ControllerB;
1577
1578 assert_eq!(a.middlewares(), vec!["auth"]);
1579 assert_eq!(b.middlewares(), vec!["cors", "log"]);
1580
1581 assert!(!a.batch_validate());
1583 assert!(!b.batch_validate());
1584 }
1585
1586 #[test]
1588 fn test_base_controller_inheritance_chain() {
1589 struct BusinessController;
1591 impl SzController for BusinessController {}
1592 impl BaseController for BusinessController {}
1593
1594 let ctrl = BusinessController;
1595
1596 let value = ctrl.render_json(0, "error", json!({}));
1598 assert!(value.is_object());
1599
1600 assert!(!ctrl.batch_validate());
1602 assert!(ctrl.middlewares().is_empty());
1603 ctrl.initialize();
1604 }
1605
1606 struct MockAddonsController;
1612
1613 impl SzController for MockAddonsController {}
1614 impl BaseController for MockAddonsController {}
1615 impl AddonsBaseController for MockAddonsController {}
1616
1617 #[test]
1618 fn test_addons_default_allow_all_action() {
1619 let ctrl = MockAddonsController;
1621 let allow = ctrl.allow_all_action();
1622 assert!(allow.contains(&"/passport/login"));
1623 assert!(allow.contains(&"/task/task/userClerk"));
1624 assert_eq!(allow.len(), 2);
1625 }
1626
1627 #[test]
1628 fn test_addons_parse_route_info_two_segments() {
1629 let ctrl = MockAddonsController;
1631 let info = ctrl.parse_route_info("/passport/login");
1632 assert_eq!(info.controller, "passport");
1633 assert_eq!(info.action, "login");
1634 assert_eq!(info.group, "passport");
1635 assert_eq!(info.route_uri, "/passport/login");
1636 }
1637
1638 #[test]
1639 fn test_addons_parse_route_info_three_segments() {
1640 let ctrl = MockAddonsController;
1643 let info = ctrl.parse_route_info("/task/task/userClerk");
1644 assert_eq!(info.controller, "task/task");
1645 assert_eq!(info.action, "userClerk");
1646 assert_eq!(info.group, "task/task"); assert_eq!(info.route_uri, "/task/task/userClerk");
1648 }
1649
1650 #[test]
1651 fn test_addons_parse_route_info_single_segment() {
1652 let ctrl = MockAddonsController;
1654 let info = ctrl.parse_route_info("/passport");
1655 assert_eq!(info.controller, "passport");
1656 assert_eq!(info.action, "");
1657 assert_eq!(info.group, "passport");
1658 assert_eq!(info.route_uri, "/passport/");
1659 }
1660
1661 #[test]
1662 fn test_addons_parse_route_info_root() {
1663 let ctrl = MockAddonsController;
1665 let info = ctrl.parse_route_info("/");
1666 assert_eq!(info.controller, "");
1667 assert_eq!(info.action, "");
1668 assert_eq!(info.group, "");
1669 assert_eq!(info.route_uri, "/");
1670 }
1671
1672 #[test]
1673 fn test_addons_parse_route_info_empty_uri() {
1674 let ctrl = MockAddonsController;
1675 let info = ctrl.parse_route_info("");
1676 assert_eq!(info.controller, "");
1677 assert_eq!(info.route_uri, "/");
1678 }
1679
1680 #[test]
1681 fn test_addons_parse_route_info_with_query_string() {
1682 let ctrl = MockAddonsController;
1684 let info = ctrl.parse_route_info("/user/info?foo=bar&baz=1");
1685 assert_eq!(info.controller, "user");
1686 assert_eq!(info.action, "info");
1687 assert_eq!(info.route_uri, "/user/info");
1688 }
1689
1690 #[test]
1691 fn test_addons_parse_route_info_trailing_slash() {
1692 let ctrl = MockAddonsController;
1694 let info = ctrl.parse_route_info("/user/info/");
1695 assert_eq!(info.controller, "user/info");
1697 assert_eq!(info.action, "");
1698 assert_eq!(info.route_uri, "/user/info/");
1699 }
1700
1701 #[test]
1702 fn test_addons_check_login_whitelist_pass() {
1703 let ctrl = MockAddonsController;
1705 let result = ctrl.check_login("/passport/login", false);
1706 assert!(result.is_ok());
1707 }
1708
1709 #[test]
1710 fn test_addons_check_login_whitelist_pass_2() {
1711 let ctrl = MockAddonsController;
1713 let result = ctrl.check_login("/task/task/userClerk", false);
1714 assert!(result.is_ok());
1715 }
1716
1717 #[test]
1718 fn test_addons_check_login_logged_in_pass() {
1719 let ctrl = MockAddonsController;
1721 let result = ctrl.check_login("/user/info", true);
1722 assert!(result.is_ok());
1723 }
1724
1725 #[test]
1726 fn test_addons_check_login_not_logged_in_fail() {
1727 let ctrl = MockAddonsController;
1729 let result = ctrl.check_login("/user/info", false);
1730 assert!(result.is_err());
1731 assert_eq!(result.unwrap_err(), "not_login");
1732 }
1733
1734 #[test]
1735 fn test_addons_get_token_default_returns_none() {
1736 let ctrl = MockAddonsController;
1739 let result = ctrl.get_token(Some("Bearer xxx.yyy.zzz"));
1740 assert!(result.is_ok());
1741 assert_eq!(result.unwrap(), None);
1742 }
1743
1744 #[test]
1745 fn test_addons_get_token_no_authorization() {
1746 let ctrl = MockAddonsController;
1747 let result = ctrl.get_token(None);
1748 assert!(result.is_ok());
1749 assert_eq!(result.unwrap(), None);
1750 }
1751
1752 #[test]
1753 fn test_addons_get_token_empty_authorization() {
1754 let ctrl = MockAddonsController;
1755 let result = ctrl.get_token(Some(""));
1756 assert!(result.is_ok());
1757 assert_eq!(result.unwrap(), None);
1758 }
1759
1760 #[test]
1761 fn test_addons_get_token_invalid_format_returns_none() {
1762 let ctrl = MockAddonsController;
1765 let result = ctrl.get_token(Some("Bearer not.a.valid.jwt.token"));
1766 assert!(result.is_ok());
1767 assert_eq!(result.unwrap(), None);
1769 }
1770
1771 #[test]
1772 fn test_strip_bearer_prefix_uppercase() {
1773 assert_eq!(strip_bearer_prefix("Bearer abc.def.ghi"), "abc.def.ghi");
1774 }
1775
1776 #[test]
1777 fn test_strip_bearer_prefix_lowercase() {
1778 assert_eq!(strip_bearer_prefix("bearer abc.def.ghi"), "abc.def.ghi");
1779 }
1780
1781 #[test]
1782 fn test_strip_bearer_prefix_mixed_case() {
1783 assert_eq!(strip_bearer_prefix("BEARER abc.def.ghi"), "abc.def.ghi");
1784 }
1785
1786 #[test]
1787 fn test_strip_bearer_prefix_no_prefix() {
1788 assert_eq!(strip_bearer_prefix("abc.def.ghi"), "abc.def.ghi");
1790 }
1791
1792 #[test]
1793 fn test_strip_bearer_prefix_empty() {
1794 assert_eq!(strip_bearer_prefix(""), "");
1795 }
1796
1797 #[test]
1798 fn test_strip_bearer_prefix_with_extra_spaces() {
1799 assert_eq!(
1800 strip_bearer_prefix(" Bearer abc.def.ghi "),
1801 "abc.def.ghi"
1802 );
1803 }
1804
1805 #[test]
1810 fn test_get_token_valid_jwt_returns_user_info() {
1811 let config = JwtConfig {
1812 secret: "test-secret".to_string(),
1813 issuer: String::new(), audience: String::new(),
1815 };
1816
1817 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&config.secret);
1819 let exp = std::time::SystemTime::now()
1820 .duration_since(std::time::UNIX_EPOCH)
1821 .unwrap()
1822 .as_secs() as i64
1823 + 3600; let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp).with_user_id(12345);
1825 let token = encoder.encode(&claims).unwrap();
1826
1827 let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1828 assert!(result.is_ok());
1829 let user = result.unwrap();
1830 assert!(user.is_some());
1831 let user = user.unwrap();
1832 assert_eq!(user.user_id, 12345);
1833 assert!(user.is_login);
1834 }
1835
1836 #[test]
1838 fn test_get_token_wrong_secret_returns_none() {
1839 let config = JwtConfig {
1840 secret: "correct-secret".to_string(),
1841 issuer: String::new(),
1842 audience: String::new(),
1843 };
1844
1845 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new("wrong-secret");
1847 let exp = std::time::SystemTime::now()
1848 .duration_since(std::time::UNIX_EPOCH)
1849 .unwrap()
1850 .as_secs() as i64
1851 + 3600;
1852 let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp).with_user_id(12345);
1853 let token = encoder.encode(&claims).unwrap();
1854
1855 let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1856 assert!(result.is_ok());
1857 assert_eq!(result.unwrap(), None); }
1859
1860 #[test]
1862 fn test_get_token_expired_returns_none() {
1863 let config = JwtConfig {
1864 secret: "test-secret".to_string(),
1865 issuer: String::new(),
1866 audience: String::new(),
1867 };
1868
1869 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&config.secret);
1870 let exp = std::time::SystemTime::now()
1872 .duration_since(std::time::UNIX_EPOCH)
1873 .unwrap()
1874 .as_secs() as i64
1875 - 3600;
1876 let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp).with_user_id(12345);
1877 let token = encoder.encode(&claims).unwrap();
1878
1879 let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1880 assert!(result.is_ok());
1881 assert_eq!(result.unwrap(), None); }
1883
1884 #[test]
1886 fn test_get_token_no_user_id_claim_returns_none() {
1887 let config = JwtConfig {
1888 secret: "test-secret".to_string(),
1889 issuer: String::new(),
1890 audience: String::new(),
1891 };
1892
1893 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&config.secret);
1894 let exp = std::time::SystemTime::now()
1895 .duration_since(std::time::UNIX_EPOCH)
1896 .unwrap()
1897 .as_secs() as i64
1898 + 3600;
1899 let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp);
1901 let token = encoder.encode(&claims).unwrap();
1902
1903 let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1904 assert!(result.is_ok());
1905 assert_eq!(result.unwrap(), None); }
1907
1908 #[test]
1910 fn test_get_token_iss_mismatch_returns_none() {
1911 let config = JwtConfig {
1912 secret: "test-secret".to_string(),
1913 issuer: "https://expected-issuer.com".to_string(),
1914 audience: String::new(),
1915 };
1916
1917 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&config.secret);
1918 let exp = std::time::SystemTime::now()
1919 .duration_since(std::time::UNIX_EPOCH)
1920 .unwrap()
1921 .as_secs() as i64
1922 + 3600;
1923 let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp)
1925 .with_issuer("https://wrong-issuer.com")
1926 .with_user_id(12345);
1927 let token = encoder.encode(&claims).unwrap();
1928
1929 let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1930 assert!(result.is_ok());
1931 assert_eq!(result.unwrap(), None); }
1933
1934 #[test]
1936 fn test_get_token_iss_match_returns_user_info() {
1937 let config = JwtConfig {
1938 secret: "test-secret".to_string(),
1939 issuer: "https://mall.ljclz.shop".to_string(),
1940 audience: String::new(),
1941 };
1942
1943 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&config.secret);
1944 let exp = std::time::SystemTime::now()
1945 .duration_since(std::time::UNIX_EPOCH)
1946 .unwrap()
1947 .as_secs() as i64
1948 + 3600;
1949 let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp)
1950 .with_issuer(&config.issuer)
1951 .with_user_id(67890);
1952 let token = encoder.encode(&claims).unwrap();
1953
1954 let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1955 assert!(result.is_ok());
1956 let user = result.unwrap().unwrap();
1957 assert_eq!(user.user_id, 67890);
1958 assert!(user.is_login);
1959 }
1960
1961 #[test]
1963 fn test_get_token_empty_secret_returns_none() {
1964 let config = JwtConfig::default(); let result = verify_token_with_config(Some("Bearer any.token.here"), &config);
1967 assert!(result.is_ok());
1968 assert_eq!(result.unwrap(), None); }
1970
1971 #[test]
1973 fn test_get_token_without_bearer_prefix() {
1974 let config = JwtConfig {
1975 secret: "test-secret".to_string(),
1976 issuer: String::new(),
1977 audience: String::new(),
1978 };
1979
1980 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&config.secret);
1981 let exp = std::time::SystemTime::now()
1982 .duration_since(std::time::UNIX_EPOCH)
1983 .unwrap()
1984 .as_secs() as i64
1985 + 3600;
1986 let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp).with_user_id(99999);
1987 let token = encoder.encode(&claims).unwrap();
1988
1989 let result = verify_token_with_config(Some(&token), &config);
1991 assert!(result.is_ok());
1992 let user = result.unwrap().unwrap();
1993 assert_eq!(user.user_id, 99999);
1994 }
1995
1996 struct CustomAddonsController;
1998
1999 impl SzController for CustomAddonsController {}
2000 impl BaseController for CustomAddonsController {}
2001 impl AddonsBaseController for CustomAddonsController {
2002 fn allow_all_action(&self) -> Vec<&'static str> {
2003 vec!["/custom/public", "/custom/health"]
2004 }
2005 }
2006
2007 #[test]
2008 fn test_addons_override_allow_all_action() {
2009 let ctrl = CustomAddonsController;
2010 let allow = ctrl.allow_all_action();
2011 assert_eq!(allow, vec!["/custom/public", "/custom/health"]);
2012 assert!(!allow.contains(&"/passport/login"));
2014 }
2015
2016 #[test]
2017 fn test_addons_override_check_login_uses_new_whitelist() {
2018 let ctrl = CustomAddonsController;
2019 assert!(ctrl.check_login("/custom/public", false).is_ok());
2021 assert!(ctrl.check_login("/passport/login", false).is_err());
2023 }
2024
2025 #[tokio::test]
2027 async fn test_addons_php_full_flow_whitelist() {
2028 struct PassportController;
2029 impl SzController for PassportController {}
2030 impl BaseController for PassportController {}
2031 impl AddonsBaseController for PassportController {}
2032
2033 let ctrl = PassportController;
2034
2035 let info = ctrl.parse_route_info("/passport/login");
2037 assert_eq!(info.route_uri, "/passport/login");
2038
2039 let user = ctrl.get_token(None).unwrap();
2041
2042 let is_login = user.as_ref().is_some_and(|u| u.is_login);
2044 ctrl.check_login(&info.route_uri, is_login).unwrap();
2045
2046 let resp = ctrl.render_success("登录成功", json!({"token": "fake.jwt.token"}));
2048 let body = collect_body(resp).await;
2049 assert_eq!(
2050 body,
2051 r#"{"code":1,"msg":"登录成功","data":{"token":"fake.jwt.token"}}"#
2052 );
2053 }
2054
2055 #[tokio::test]
2056 async fn test_addons_php_full_flow_not_login() {
2057 struct UserController;
2058 impl SzController for UserController {}
2059 impl BaseController for UserController {}
2060 impl AddonsBaseController for UserController {}
2061
2062 let ctrl = UserController;
2063
2064 let info = ctrl.parse_route_info("/user/info");
2066
2067 let user = ctrl.get_token(None).unwrap();
2069 let is_login = user.as_ref().is_some_and(|u| u.is_login);
2070
2071 let result = ctrl.check_login(&info.route_uri, is_login);
2073 assert!(result.is_err());
2074 assert_eq!(result.unwrap_err(), "not_login");
2075
2076 let resp = ctrl.render_error("not_login", json!({}), -1);
2078 let body = collect_body(resp).await;
2079 assert_eq!(body, r#"{"code":-1,"msg":"not_login","data":{}}"#);
2080 }
2081
2082 #[tokio::test]
2083 async fn test_addons_php_full_flow_logged_in() {
2084 struct OrderController;
2085 impl SzController for OrderController {}
2086 impl BaseController for OrderController {}
2087 impl AddonsBaseController for OrderController {
2088 fn get_token(&self, _authorization: Option<&str>) -> Result<Option<UserInfo>, String> {
2090 Ok(Some(UserInfo {
2091 user_id: 12345,
2092 is_login: true,
2093 }))
2094 }
2095 }
2096
2097 let ctrl = OrderController;
2098
2099 let info = ctrl.parse_route_info("/order/list");
2101
2102 let user = ctrl.get_token(None).unwrap();
2104 let is_login = user.as_ref().is_some_and(|u| u.is_login);
2105
2106 ctrl.check_login(&info.route_uri, is_login).unwrap();
2108
2109 let resp = ctrl.render_success(
2111 "success",
2112 json!({"list": [{"id": 1}, {"id": 2}], "total": 2}),
2113 );
2114 let body = collect_body(resp).await;
2115 assert_eq!(
2116 body,
2117 r#"{"code":1,"msg":"success","data":{"list":[{"id":1},{"id":2}],"total":2}}"#
2118 );
2119 }
2120
2121 #[test]
2123 fn test_route_info_debug_clone_eq() {
2124 let info1 = RouteInfo {
2125 controller: "user".to_string(),
2126 action: "info".to_string(),
2127 route_uri: "/user/info".to_string(),
2128 group: "user".to_string(),
2129 };
2130 let info2 = info1.clone();
2131 assert_eq!(info1, info2);
2132 let debug_str = format!("{info1:?}");
2133 assert!(debug_str.contains("RouteInfo"));
2134 assert!(debug_str.contains("user"));
2135 }
2136
2137 #[test]
2138 fn test_user_info_debug_clone_eq() {
2139 let user1 = UserInfo {
2140 user_id: 12345,
2141 is_login: true,
2142 };
2143 let user2 = user1.clone();
2144 assert_eq!(user1, user2);
2145 let debug_str = format!("{user1:?}");
2146 assert!(debug_str.contains("UserInfo"));
2147 assert!(debug_str.contains("12345"));
2148 }
2149
2150 #[test]
2152 fn test_addons_inheritance_chain_all_methods() {
2153 struct BusinessController;
2154 impl SzController for BusinessController {}
2155 impl BaseController for BusinessController {}
2156 impl AddonsBaseController for BusinessController {}
2157
2158 let ctrl = BusinessController;
2159
2160 let value = ctrl.render_json(1, "ok", json!({}));
2162 assert_eq!(value["code"], 1);
2163
2164 assert!(!ctrl.batch_validate());
2166 assert!(ctrl.middlewares().is_empty());
2167 ctrl.initialize();
2168
2169 let info = ctrl.parse_route_info("/test/action");
2171 assert_eq!(info.route_uri, "/test/action");
2172 assert!(ctrl.check_login("/passport/login", false).is_ok());
2173 assert!(ctrl.get_token(None).unwrap().is_none());
2174 }
2175
2176 #[test]
2185 fn test_p0_sec_02_empty_secret_returns_none_not_accept_token() {
2186 let config = JwtConfig {
2187 secret: String::new(), issuer: String::new(),
2189 audience: String::new(),
2190 };
2191
2192 let result = verify_token_with_config(Some("any.token.here"), &config);
2194 assert!(
2195 result.is_ok(),
2196 "空密钥不应导致 panic 或 Err(应优雅降级为 None)"
2197 );
2198 assert_eq!(
2199 result.unwrap(),
2200 None,
2201 "P0-SEC-02: 空密钥时必须返回 None,绝不能接受任何 token"
2202 );
2203 }
2204
2205 #[test]
2210 fn test_p0_sec_02_rejects_alg_none_token_with_empty_secret() {
2211 let config = JwtConfig::default(); let alg_none_token = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.\
2218 eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImhhY2tlciIsImlhdCI6MCwiZXhwIjo5OTk5OTk5OTk5fQ.";
2219
2220 let result = verify_token_with_config(Some(alg_none_token), &config);
2221 assert!(result.is_ok(), "空密钥下不应 panic");
2222 assert_eq!(
2223 result.unwrap(),
2224 None,
2225 "P0-SEC-02: 空密钥下 alg=none 伪造 token 必须被拒绝(返回 None)"
2226 );
2227 }
2228
2229 #[test]
2237 fn test_p1_sec_10_skips_audience_check_when_not_configured() {
2238 let config = JwtConfig {
2239 secret: "test-secret".to_string(),
2240 issuer: String::new(),
2241 audience: String::new(),
2242 };
2243
2244 let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&config.secret);
2245 let exp = std::time::SystemTime::now()
2246 .duration_since(std::time::UNIX_EPOCH)
2247 .unwrap()
2248 .as_secs() as i64
2249 + 3600;
2250 let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp).with_user_id(42);
2251 let token = encoder.encode(&claims).unwrap();
2252
2253 let result = verify_token_with_config(Some(&token), &config);
2254 assert!(result.is_ok());
2255 assert!(
2256 result.unwrap().is_some(),
2257 "未配置 audience 时应跳过 aud 验证"
2258 );
2259 }
2260
2261 #[test]
2270 fn test_p1_sec_12_jwt_config_debug_redacts_secret() {
2271 let config = JwtConfig {
2272 secret: "super-secret-key-12345".to_string(),
2273 issuer: "https://example.com".to_string(),
2274 audience: String::new(),
2275 };
2276
2277 let debug_output = format!("{:?}", config);
2278
2279 assert!(
2281 !debug_output.contains("super-secret-key-12345"),
2282 "P1-SEC-12: JwtConfig::Debug 泄漏了 secret 字段: {debug_output}"
2283 );
2284 assert!(
2286 debug_output.contains("[REDACTED]"),
2287 "P1-SEC-12: JwtConfig::Debug 应包含 [REDACTED] 标记: {debug_output}"
2288 );
2289 assert!(
2291 debug_output.contains("https://example.com"),
2292 "issuer 字段应正常显示: {debug_output}"
2293 );
2294 }
2295}