Files
imks/svc/tests.rs
T
zhenyi 821537186e refactor(tests): reformat code and update dependency management
- Reorganized import statements in adapter tests for better readability
- Replaced or_insert_with(Vec::new) with or_default() in test closures
- Updated Cargo.lock with new dependency versions and checksums
- Added TLS features to tonic dependency configuration
- Included sqlx, chrono, and uuid dependencies with specific features
- Added jsonwebtoken and arc-swap as project dependencies
- Reformatted assertion statements to comply with line length limits
- Adjusted base64 import order in engine codec module
- Updated protobuf include statement formatting
2026-06-11 12:11:05 +08:00

167 lines
5.7 KiB
Rust

//! Service layer unit tests for `MessageService`.
//!
//! Tests parsing, nonce dedup, rate limiting — all in-memory without
//! requiring a real database or gRPC connection.
#[cfg(test)]
#[allow(clippy::module_inception)]
mod tests {
use std::sync::Arc;
use std::time::{Duration, Instant};
use uuid::Uuid;
use crate::svc::message::MessageService;
#[test]
fn test_parse_field_valid() {
let json = serde_json::json!({"message_id": "01909abc-def0-7000-8000-000000000001"});
let id: Uuid = MessageService::parse_field(&json, "message_id").unwrap();
assert_eq!(id.to_string(), "01909abc-def0-7000-8000-000000000001");
}
#[test]
fn test_parse_field_missing() {
let json = serde_json::json!({});
let result: crate::ImksResult<String> = MessageService::parse_field(&json, "missing");
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("Missing required field")
);
}
#[test]
fn test_parse_optional_present() {
let json = serde_json::json!({"name": "alice"});
let val: Option<String> = MessageService::parse_optional(&json, "name").unwrap();
assert_eq!(val, Some("alice".into()));
}
#[test]
fn test_parse_optional_null() {
let json = serde_json::json!({"name": null});
let val: Option<String> = MessageService::parse_optional(&json, "name").unwrap();
assert_eq!(val, None);
}
#[test]
fn test_parse_optional_missing() {
let json = serde_json::json!({});
let val: Option<String> = MessageService::parse_optional(&json, "name").unwrap();
assert_eq!(val, None);
}
#[test]
fn test_parse_send_payload_basic_shape() {
let json = serde_json::json!([{
"channel_id": "01909abc-def0-7000-8000-000000000001",
"body": "hello world"
}]);
let arr = json.as_array().unwrap();
let payload = &arr[0];
assert_eq!(payload["body"], "hello world");
}
#[test]
fn test_parse_send_payload_with_rich_content() {
let json = serde_json::json!([{
"channel_id": "01909abc-def0-7000-8000-000000000001",
"body": "hey @alice",
"thread_id": "01909def-abc0-7000-8000-000000000002",
"nonce": "nonce-001",
"mentioned_user_ids": ["01909abc-def0-7000-8000-000000000003"],
"attachments": [{"filename": "img.png", "url": "https://cdn/img.png", "size": 1024, "content_type": "image/png"}],
"embeds": [{"embed_type": "link", "title": "Example", "url": "https://example.com", "fields": [{"name": "k", "value": "v", "inline": true}]}],
"sticker": {"sticker_id": "01909abc-def0-7000-8000-000000000004", "name": "Hype!", "image_url": "https://cdn/sticker.png"},
"forward": {"source_message_id": "01909abc-def0-7000-8000-000000000005", "source_channel_id": "01909abc-def0-7000-8000-000000000006"}
}]);
let arr = json.as_array().unwrap();
let payload = &arr[0];
assert_eq!(payload["body"], "hey @alice");
assert!(payload["attachments"].is_array());
assert!(payload["embeds"].is_array());
assert!(payload["sticker"].is_object());
assert!(payload["forward"].is_object());
}
#[test]
fn test_nonce_dedup_first_accepted() {
use dashmap::DashMap;
let nonces = Arc::new(DashMap::<String, Instant>::new());
assert!(!nonces.contains_key("nonce-1"));
nonces.insert("nonce-1".to_string(), Instant::now());
assert!(nonces.contains_key("nonce-1"));
}
#[test]
fn test_nonce_dedup_rejects_duplicate() {
use dashmap::DashMap;
let nonces = Arc::new(DashMap::<String, Instant>::new());
nonces.insert("nonce-1".to_string(), Instant::now());
// After insert, it should exist
assert!(nonces.contains_key("nonce-1"));
}
#[test]
fn test_rate_limit_within_window() {
use dashmap::DashMap;
let limits = Arc::new(DashMap::<(Uuid, Uuid), Vec<Instant>>::new());
let user = Uuid::now_v7();
let channel = Uuid::now_v7();
// Should be empty initially
assert!(limits.get(&(user, channel)).is_none());
}
#[test]
fn test_rate_limit_approaches_threshold() {
use dashmap::DashMap;
let limits = Arc::new(DashMap::<(Uuid, Uuid), Vec<Instant>>::new());
let now = Instant::now();
let user = Uuid::now_v7();
let channel = Uuid::now_v7();
let mut entry = limits.entry((user, channel)).or_default();
for _ in 0..9 {
entry.push(now);
}
assert_eq!(entry.len(), 9);
}
#[test]
fn test_rate_limit_exceeded() {
use dashmap::DashMap;
let limits = Arc::new(DashMap::<(Uuid, Uuid), Vec<Instant>>::new());
let now = Instant::now();
let user = Uuid::now_v7();
let channel = Uuid::now_v7();
let mut entry = limits.entry((user, channel)).or_default();
for _ in 0..10 {
entry.push(now);
}
assert!(entry.len() >= 10);
}
#[test]
fn test_rate_limit_window_expiry_eviction() {
use dashmap::DashMap;
let limits = Arc::new(DashMap::<(Uuid, Uuid), Vec<Instant>>::new());
let window = Duration::from_secs(10);
let user = Uuid::now_v7();
let channel = Uuid::now_v7();
let old = Instant::now() - Duration::from_secs(15);
let now = Instant::now();
let mut entry = limits.entry((user, channel)).or_default();
entry.push(old);
entry.push(now);
entry.retain(|t| now.duration_since(*t) < window);
assert_eq!(entry.len(), 1);
}
}