mod common;
use common::fuzz::Rng;
use serde_json::{Map, Value};
use sz_rust_core::config::AppConfig;
use sz_rust_core::error::{BaseException, ErrorCode};
use sz_rust_core::response::ApiResponse;
use sz_rust_core::router::parse_path;
use sz_rust_core::routing::{load_routes_from_yaml_str, HandlerRef};
use sz_rust_core::validate::Validate;
const FUZZ_ITERATIONS: usize = 1000;
#[test]
fn fuzz_parse_path_safety() {
let mut rng = Rng::new(42);
for _ in 0..FUZZ_ITERATIONS {
let len = rng.next_usize(200);
let uri = rng.next_string(len);
let parsed = parse_path(&uri);
assert!(!parsed.app.is_empty(), "app 不应为空");
assert!(!parsed.controller.is_empty(), "controller 不应为空");
assert!(!parsed.action.is_empty(), "action 不应为空");
}
let boundary_inputs = ["", "/", "?", "/?", "/a?b=c", "/a/b?c=d&e=f", "//", "///"];
for input in &boundary_inputs {
let parsed = parse_path(input);
assert!(!parsed.app.is_empty());
assert!(!parsed.controller.is_empty());
assert!(!parsed.action.is_empty());
}
let traversal_inputs = [
"/../etc/passwd",
"/..%2F..%2Fetc",
"/%2e%2e/%2e%2e/etc",
"/common/foo/bar",
"/oapc/../admin",
];
for input in &traversal_inputs {
let parsed = parse_path(input);
assert!(!parsed.app.is_empty());
assert!(!parsed.controller.is_empty());
assert!(!parsed.action.is_empty());
}
}
#[test]
fn fuzz_handler_ref_parse_safety() {
let mut rng = Rng::new(123);
for _ in 0..FUZZ_ITERATIONS {
let len = rng.next_usize(50);
let input = rng.next_string(len);
let result = HandlerRef::parse(&input);
if let Ok(handler) = result {
let s = handler.to_handler_string();
assert!(!s.is_empty(), "to_handler_string 不应返回空");
}
}
let boundary_inputs = ["", " ", "@", "/", "User@", "@action", "/action"];
for input in &boundary_inputs {
let _ = HandlerRef::parse(input);
}
let valid_inputs = ["User", "User@list", "User/list", "Customer@index"];
for input in &valid_inputs {
let handler = HandlerRef::parse(input).expect("合法输入应解析成功");
let _ = handler.to_handler_string();
}
}
#[test]
fn fuzz_route_config_load_safety() {
let mut rng = Rng::new(456);
for _ in 0..FUZZ_ITERATIONS {
let len = rng.next_usize(200);
let yaml = rng.next_string(len);
let _ = load_routes_from_yaml_str(&yaml);
}
let boundary_inputs = ["", " ", "\n", "\t", "routes: []", "routes:", "---"];
for input in &boundary_inputs {
let _ = load_routes_from_yaml_str(input);
}
for _ in 0..100 {
let method = rng.next_string(10);
let path = rng.next_string(20);
let handler = rng.next_string(30);
let yaml = format!(
"routes:\n - method: {}\n path: {}\n handler: {}\n",
method, path, handler
);
let _ = load_routes_from_yaml_str(&yaml);
}
let valid_yaml = r#"
routes:
- method: GET
path: /users
handler: User@list
- method: POST
path: /users
handler: User@create
"#;
let config = load_routes_from_yaml_str(valid_yaml).expect("合法 YAML 应解析成功");
assert_eq!(config.routes.len(), 2);
}
#[test]
fn fuzz_json_response_serialization() {
let mut rng = Rng::new(789);
for _ in 0..FUZZ_ITERATIONS {
let code = rng.next_i64() as i32;
let msg_len = rng.next_usize(100);
let msg = rng.next_string(msg_len);
let data = generate_random_json_value(&mut rng, 3);
let resp = ApiResponse::new(code, msg.clone(), data.clone());
let value = resp.to_value();
assert_eq!(value["code"], code);
let json_str = resp.to_json_string();
let reparsed: Value =
serde_json::from_str(&json_str).expect("to_json_string 输出应可反序列化");
assert_eq!(reparsed["code"], code);
let _ = ApiResponse::success(data.clone(), msg.clone());
let _ = ApiResponse::error(msg.clone());
let _ = ApiResponse::error_with_code(code, msg, data);
}
}
#[test]
fn fuzz_error_code_conversion() {
let mut rng = Rng::new(101);
for _ in 0..FUZZ_ITERATIONS {
let code_int = rng.next_i64() as i32;
let code_enum = ErrorCode::from(code_int);
let _ = code_enum.as_i32();
let _ = code_enum.http_status();
let msg_len = rng.next_usize(50);
let msg = rng.next_string(msg_len);
let ex = BaseException::new(code_enum, msg.clone());
assert_eq!(ex.code, code_enum.as_i32());
assert_eq!(ex.msg, msg);
let json = ex.to_json();
assert_eq!(json["code"], code_enum.as_i32());
assert_eq!(json["msg"], msg);
}
let boundary_codes = [
i32::MIN,
i32::MIN + 1,
-1,
0,
1,
100,
403,
404,
422,
500,
i32::MAX - 1,
i32::MAX,
];
for &code_int in &boundary_codes {
let code_enum = ErrorCode::from(code_int);
let _ = code_enum.as_i32();
let _ = code_enum.http_status();
let ex = BaseException::new(code_enum, "boundary");
let _ = ex.to_json();
}
let _ = BaseException::not_login("test");
let _ = BaseException::user_not_found("test");
let _ = BaseException::user_disabled("test");
let _ = BaseException::failed("test");
let _ = BaseException::forbidden("test");
let _ = BaseException::not_found("test");
let _ = BaseException::validate_failed("test");
let _ = BaseException::db_error("test");
}
#[test]
fn fuzz_config_parse_safety() {
let mut rng = Rng::new(202);
for _ in 0..FUZZ_ITERATIONS {
let len = rng.next_usize(200);
let yaml = rng.next_string(len);
let _ = serde_yml::from_str::<AppConfig>(&yaml);
}
let boundary_inputs = ["", " ", "\n", "\t", "---", "..."];
for input in &boundary_inputs {
let _ = serde_yml::from_str::<AppConfig>(input);
}
for _ in 0..100 {
let app_host = rng.next_string(20);
let default_app = rng.next_string(10);
let auto_multi_app = rng.next_bool();
let yaml = format!(
"app:\n app_host: {}\n default_app: {}\n auto_multi_app: {}\n",
app_host, default_app, auto_multi_app
);
let _ = serde_yml::from_str::<AppConfig>(&yaml);
}
let valid_yaml = r#"
app:
app_host: "https://example.com"
default_app: "api"
auto_multi_app: true
database:
default: "mysql"
auto_timestamp: true
"#;
let config = serde_yml::from_str::<AppConfig>(valid_yaml).expect("合法 YAML 应解析成功");
assert_eq!(config.app.app_host, "https://example.com");
assert_eq!(config.app.default_app, "api");
assert!(config.app.auto_multi_app);
assert_eq!(config.database.default, "mysql");
}
#[test]
fn fuzz_validate_rules_safety() {
let mut rng = Rng::new(303);
for _ in 0..FUZZ_ITERATIONS {
let validate = Validate::new();
let rules_len = rng.next_usize(30);
let rules = rng.next_string(rules_len);
let value = generate_random_json_value(&mut rng, 2);
let _ = validate.check_rule(&value, &rules);
}
let boundary_rules = ["", " ", "|", ":", "||", "|:", ":|"];
let test_value = Value::String("test".to_string());
for rule in &boundary_rules {
let validate = Validate::new();
let _ = validate.check_rule(&test_value, rule);
}
let common_rules = [
"require",
"must",
"email",
"mobile",
"url",
"in:1,2,3",
"notIn:1,2,3",
"max:100",
"min:1",
"length:1,10",
"require|in:1,2,3",
"require|email",
];
for rule in &common_rules {
let validate = Validate::new();
let value = generate_random_json_value(&mut rng, 2);
let _ = validate.check_rule(&value, rule);
}
let mut validate = Validate::new()
.rule("name", "require|length:1,10")
.rule("email", "require|email")
.rule("age", "require|integer");
let mut data = Map::new();
let name_len = rng.next_usize(5);
data.insert("name".to_string(), Value::String(rng.next_string(name_len)));
let email_len = rng.next_usize(10);
data.insert(
"email".to_string(),
Value::String(rng.next_string(email_len)),
);
data.insert("age".to_string(), Value::Number(rng.next_i64().into()));
let _ = validate.check(&Value::Object(data));
}
fn generate_random_json_value(rng: &mut Rng, max_depth: usize) -> Value {
if max_depth == 0 {
match rng.next_usize(6) {
0 => Value::Null,
1 => Value::Bool(rng.next_bool()),
2 => Value::Number(rng.next_i64().into()),
3 => Value::Number(
serde_json::Number::from_f64(rng.next_f64())
.unwrap_or_else(|| serde_json::Number::from(0)),
),
4 => {
let len = rng.next_usize(50);
Value::String(rng.next_string(len))
}
_ => Value::Array(vec![]),
}
} else {
match rng.next_usize(4) {
0 => Value::Null,
1 => Value::Bool(rng.next_bool()),
2 => {
let len = rng.next_usize(50);
Value::String(rng.next_string(len))
}
3 => {
let count = rng.next_usize(5) + 1;
let arr: Vec<Value> = (0..count)
.map(|_| generate_random_json_value(rng, max_depth - 1))
.collect();
Value::Array(arr)
}
_ => {
let count = rng.next_usize(5) + 1;
let mut map = Map::new();
for _ in 0..count {
let key_len = rng.next_usize(10);
let key = rng.next_string(key_len);
let value = generate_random_json_value(rng, max_depth - 1);
map.insert(key, value);
}
Value::Object(map)
}
}
}
}