mod common;
use common::fuzz::Rng;
use serde_json::{Map, Value};
use std::sync::Arc;
use sz_rust_core::config::AppConfig;
use sz_rust_core::cookie::{CookieJar, CookieOptions};
use sz_rust_core::error::{BaseException, ErrorCode};
use sz_rust_core::event::{ClosureListener, EventDispatcher, EventError, Listener};
use sz_rust_core::middleware::chain::MiddlewareChain;
use sz_rust_core::middleware::order::MiddlewareKind;
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_yaml::from_str::<AppConfig>(&yaml);
}
let boundary_inputs = ["", " ", "\n", "\t", "---", "..."];
for input in &boundary_inputs {
let _ = serde_yaml::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_yaml::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_yaml::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));
}
#[test]
fn fuzz_cookie_jar_safety() {
let mut rng = Rng::new(20260730);
for _ in 0..FUZZ_ITERATIONS {
let name_len = rng.next_usize(50);
let value_len = rng.next_usize(200);
let name = rng.next_string(name_len);
let value = rng.next_string(value_len);
let path_len = rng.next_usize(20);
let domain_len = rng.next_usize(20);
let samesite_len = rng.next_usize(10);
let options = CookieOptions {
expire: rng.next_i64(),
path: rng.next_string(path_len),
domain: rng.next_string(domain_len),
secure: rng.next_bool(),
httponly: rng.next_bool(),
samesite: rng.next_string(samesite_len),
};
let jar = CookieJar::new().set(&name, &value, options.clone());
let _ = jar.get(&name);
let _ = jar.get_with_default(&name, "default");
let _ = jar.has(&name);
let jar = CookieJar::new().forever(&name, &value, options.clone());
let _ = jar.delete(&name, options.clone());
let jar = CookieJar::with_config(options);
let _ = jar.config();
let _ = jar.get_response_cookies();
}
let boundary_names: Vec<String> = vec![
"".to_string(),
" ".to_string(),
"name=value".to_string(),
"name; path=/".to_string(),
"\x00".to_string(),
"a".repeat(1000),
];
let boundary_values: Vec<String> = vec![
"".to_string(),
" ".to_string(),
"value; malicious".to_string(),
"\x00\x01\x02".to_string(),
"a".repeat(2000),
];
for name in &boundary_names {
for value in &boundary_values {
let options = CookieOptions::with_expire(0);
let jar = CookieJar::new().set(name, value, options);
let _ = jar.get(name);
let _ = jar.has(name);
}
}
}
#[test]
fn fuzz_middleware_chain_safety() {
let mut rng = Rng::new(20260731);
let all_kinds = [
MiddlewareKind::Trace,
MiddlewareKind::Cors,
MiddlewareKind::Log,
MiddlewareKind::RateLimit,
MiddlewareKind::Auth,
];
for _ in 0..FUZZ_ITERATIONS {
let mut chain = MiddlewareChain::new();
let push_count = rng.next_usize(20);
for _ in 0..push_count {
let kind = all_kinds[rng.next_usize(all_kinds.len())];
chain = chain.push(kind);
}
let insert_count = rng.next_usize(10);
for _ in 0..insert_count {
let kind = all_kinds[rng.next_usize(all_kinds.len())];
let index = rng.next_usize(push_count + insert_count + 5);
if index <= chain.len() {
let backup = chain.clone();
chain = chain.insert(index, kind).unwrap_or(backup);
}
}
let remove_count = rng.next_usize(10);
for _ in 0..remove_count {
let index = rng.next_usize(push_count + insert_count + 5);
let _ = chain.remove(index);
}
let query_count = rng.next_usize(10);
for _ in 0..query_count {
let kind = all_kinds[rng.next_usize(all_kinds.len())];
let _ = chain.contains(kind);
let _ = chain.position(kind);
}
let kind = all_kinds[rng.next_usize(all_kinds.len())];
let _ = chain.remove_kind(kind);
let _ = chain.remove_from(kind);
let _ = chain.len();
let _ = chain.is_empty();
let _ = chain.has_duplicates();
let _ = chain.order();
let _ = chain.service_builder_order();
}
let mut empty_chain = MiddlewareChain::new();
assert!(empty_chain.is_empty());
assert!(empty_chain.clone().insert(1, MiddlewareKind::Auth).is_err());
let updated = empty_chain.clone().insert(0, MiddlewareKind::Auth);
assert!(updated.is_ok());
assert!(empty_chain.remove(999).is_none());
}
#[test]
fn fuzz_event_dispatcher_safety() {
let mut rng = Rng::new(20260801);
for _ in 0..FUZZ_ITERATIONS {
let dispatcher = EventDispatcher::new();
let event_count = rng.next_usize(10);
for _ in 0..event_count {
let event_name_len = rng.next_usize(30);
let event_name = rng.next_string(event_name_len);
let first = rng.next_bool();
let listener: Arc<dyn Listener> = Arc::new(ClosureListener::new(
move |_params: &Value| -> Result<Value, EventError> { Ok(Value::Null) },
));
dispatcher.listen(&event_name, listener, first);
}
let trigger_count = rng.next_usize(5);
for _ in 0..trigger_count {
let event_name_len = rng.next_usize(30);
let event_name = rng.next_string(event_name_len);
let params_len = rng.next_usize(50);
let params = Value::String(rng.next_string(params_len));
let once = rng.next_bool();
let _ = dispatcher.trigger(&event_name, ¶ms, once);
}
let query_count = rng.next_usize(10);
for _ in 0..query_count {
let event_name_len = rng.next_usize(30);
let event_name = rng.next_string(event_name_len);
let _ = dispatcher.has_listener(&event_name);
let _ = dispatcher.listener_count(&event_name);
}
let remove_count = rng.next_usize(5);
for _ in 0..remove_count {
let event_name_len = rng.next_usize(30);
let event_name = rng.next_string(event_name_len);
dispatcher.remove(&event_name);
}
}
let dispatcher = EventDispatcher::new();
let listener: Arc<dyn Listener> =
Arc::new(ClosureListener::new(|_params: &Value| Ok(Value::Null)));
dispatcher.listen("", listener.clone(), false);
dispatcher.listen(&"a".repeat(1000), listener.clone(), false);
let _ = dispatcher.trigger("", &Value::Null, false);
let _ = dispatcher.trigger(&"a".repeat(1000), &Value::Null, false);
let _ = dispatcher.listener_count("");
}
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)
}
}
}
}