f7a720204a
- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用 - 添加所有子项目的完整源代码 - 保留原始 .git 为 .git.bak 备份
203 lines
6.0 KiB
Rust
203 lines
6.0 KiB
Rust
use nomifun_common::*;
|
|
|
|
// --- ID generation ---
|
|
|
|
#[test]
|
|
fn test_generate_id_returns_uuid() {
|
|
let id = generate_id();
|
|
assert_eq!(id.len(), 36); // UUID string length
|
|
assert!(id.contains('-'));
|
|
}
|
|
|
|
#[test]
|
|
fn test_generate_prefixed_id_has_prefix() {
|
|
let id = generate_prefixed_id("cron");
|
|
assert!(id.starts_with("cron_"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_generate_prefixed_id_is_prefix_plus_short_id() {
|
|
// Entity ID convention: `{prefix}_{shortId}` — a 16-char base32 body
|
|
// (9-char millisecond timestamp + 7-char random), time-ordered and far
|
|
// shorter than the former UUIDv7 tail.
|
|
const SHORT_ID_ALPHABET: &str = "0123456789abcdefghjkmnpqrstvwxyz";
|
|
let id = generate_prefixed_id("conv");
|
|
let body = id.strip_prefix("conv_").expect("prefix with underscore separator");
|
|
assert_eq!(body.len(), 16);
|
|
assert!(
|
|
body.chars().all(|c| SHORT_ID_ALPHABET.contains(c)),
|
|
"short-id body {body} must use only the base32 alphabet"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_prefixed_id_is_time_ordered() {
|
|
// The timestamp is the high-order prefix of the body, so a later mint
|
|
// sorts lexicographically after an earlier one.
|
|
let earlier = generate_prefixed_id("conv");
|
|
std::thread::sleep(std::time::Duration::from_millis(2));
|
|
let later = generate_prefixed_id("conv");
|
|
assert!(later > earlier, "{later} should sort after {earlier}");
|
|
}
|
|
|
|
#[test]
|
|
fn test_id_uniqueness_across_calls() {
|
|
let ids: std::collections::HashSet<String> = (0..1000).map(|_| generate_id()).collect();
|
|
assert_eq!(ids.len(), 1000);
|
|
}
|
|
|
|
#[test]
|
|
fn test_id_time_ordering() {
|
|
let id1 = generate_id();
|
|
let id2 = generate_id();
|
|
assert!(id2 >= id1, "UUID v7 should be time-ordered");
|
|
}
|
|
|
|
// --- Timestamp ---
|
|
|
|
#[test]
|
|
fn test_now_ms_returns_positive() {
|
|
assert!(now_ms() > 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_now_ms_monotonic() {
|
|
let t1 = now_ms();
|
|
let t2 = now_ms();
|
|
assert!(t2 >= t1);
|
|
}
|
|
|
|
// --- Crypto ---
|
|
|
|
#[test]
|
|
fn test_encrypt_decrypt_roundtrip() {
|
|
let key = [0xAB_u8; 32];
|
|
let encrypted = encrypt_string("hello world", &key).unwrap();
|
|
let decrypted = decrypt_string(&encrypted, &key).unwrap();
|
|
assert_eq!(decrypted, "hello world");
|
|
}
|
|
|
|
#[test]
|
|
fn test_encrypt_decrypt_empty_string() {
|
|
let key = [0xCD_u8; 32];
|
|
let encrypted = encrypt_string("", &key).unwrap();
|
|
let decrypted = decrypt_string(&encrypted, &key).unwrap();
|
|
assert_eq!(decrypted, "");
|
|
}
|
|
|
|
#[test]
|
|
fn test_encrypt_decrypt_unicode() {
|
|
let key = [0xEF_u8; 32];
|
|
let encrypted = encrypt_string("你好世界🌍", &key).unwrap();
|
|
let decrypted = decrypt_string(&encrypted, &key).unwrap();
|
|
assert_eq!(decrypted, "你好世界🌍");
|
|
}
|
|
|
|
#[test]
|
|
fn test_decrypt_wrong_key_fails() {
|
|
let key = [0x11_u8; 32];
|
|
let encrypted = encrypt_string("secret", &key).unwrap();
|
|
let wrong_key = [0x22_u8; 32];
|
|
assert!(decrypt_string(&encrypted, &wrong_key).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_encrypt_same_plaintext_different_ciphertext() {
|
|
let key = [0x33_u8; 32];
|
|
let e1 = encrypt_string("test", &key).unwrap();
|
|
let e2 = encrypt_string("test", &key).unwrap();
|
|
assert_ne!(e1, e2, "random nonce should produce different ciphertexts");
|
|
}
|
|
|
|
#[test]
|
|
fn test_encrypt_large_text() {
|
|
let key = [0x44_u8; 32];
|
|
let large = "x".repeat(1_000_000);
|
|
let encrypted = encrypt_string(&large, &key).unwrap();
|
|
let decrypted = decrypt_string(&encrypted, &key).unwrap();
|
|
assert_eq!(decrypted, large);
|
|
}
|
|
|
|
// --- AppError ---
|
|
|
|
#[test]
|
|
fn test_app_error_status_codes() {
|
|
use axum::http::StatusCode;
|
|
|
|
assert_eq!(AppError::NotFound("x".into()).status_code(), StatusCode::NOT_FOUND);
|
|
assert_eq!(AppError::BadRequest("x".into()).status_code(), StatusCode::BAD_REQUEST);
|
|
assert_eq!(
|
|
AppError::Unauthorized("x".into()).status_code(),
|
|
StatusCode::UNAUTHORIZED
|
|
);
|
|
assert_eq!(AppError::Forbidden("x".into()).status_code(), StatusCode::FORBIDDEN);
|
|
assert_eq!(AppError::Conflict("x".into()).status_code(), StatusCode::CONFLICT);
|
|
assert_eq!(AppError::RateLimited.status_code(), StatusCode::TOO_MANY_REQUESTS);
|
|
assert_eq!(
|
|
AppError::Internal("x".into()).status_code(),
|
|
StatusCode::INTERNAL_SERVER_ERROR
|
|
);
|
|
assert_eq!(AppError::BadGateway("x".into()).status_code(), StatusCode::BAD_GATEWAY);
|
|
assert_eq!(AppError::Timeout("x".into()).status_code(), StatusCode::BAD_GATEWAY);
|
|
}
|
|
|
|
#[test]
|
|
fn test_app_error_json_format() {
|
|
use axum::response::IntoResponse;
|
|
let resp = AppError::NotFound("user 123".into()).into_response();
|
|
assert_eq!(resp.status(), axum::http::StatusCode::NOT_FOUND);
|
|
}
|
|
|
|
// --- PaginatedResult ---
|
|
|
|
#[test]
|
|
fn test_paginated_result_serialize() {
|
|
let result = PaginatedResult {
|
|
items: vec!["a", "b"],
|
|
total: 100,
|
|
has_more: true,
|
|
};
|
|
let json = serde_json::to_value(&result).unwrap();
|
|
assert_eq!(json["has_more"], true);
|
|
assert_eq!(json["total"], 100);
|
|
}
|
|
|
|
#[test]
|
|
fn test_paginated_result_empty() {
|
|
let result: PaginatedResult<i32> = PaginatedResult {
|
|
items: vec![],
|
|
total: 0,
|
|
has_more: false,
|
|
};
|
|
let json = serde_json::to_value(&result).unwrap();
|
|
assert_eq!(json["items"], serde_json::json!([]));
|
|
}
|
|
|
|
// --- Enums ---
|
|
|
|
#[test]
|
|
fn test_enum_serde_roundtrip() {
|
|
let roundtrip_cases: Vec<(&str, AgentType)> = vec![
|
|
(r#""acp""#, AgentType::Acp),
|
|
(r#""nanobot""#, AgentType::Nanobot),
|
|
(r#""openclaw-gateway""#, AgentType::OpenclawGateway),
|
|
];
|
|
for (json_str, expected) in roundtrip_cases {
|
|
let parsed: AgentType = serde_json::from_str(json_str).unwrap();
|
|
assert_eq!(parsed, expected);
|
|
let serialized = serde_json::to_string(&expected).unwrap();
|
|
assert_eq!(serialized, json_str);
|
|
}
|
|
}
|
|
|
|
// --- Constants ---
|
|
|
|
#[test]
|
|
fn test_constants_values() {
|
|
assert_eq!(constants::DEFAULT_PORT, 25808);
|
|
assert_eq!(constants::HEARTBEAT_INTERVAL_MS, 30_000);
|
|
assert_eq!(constants::BODY_LIMIT, 10 * 1024 * 1024);
|
|
assert_eq!(constants::COOKIE_NAME, "nomifun-session");
|
|
assert!(constants::SUPPORTED_IMAGE_EXTENSIONS.contains(&".png"));
|
|
}
|