- Add gitignore and prettier configuration files for project scaffolding - Implement room access control service with project member verification - Create user access key management with CRUD operations and activity logging - Add accordion UI component for frontend expandable sections - Implement room AI configuration with list, upsert, and delete operations - Add AI event types for agent join/leave/status change tracking - Create streaming AI processing services for mode and react patterns - Build room AI service with model detection and idempotency handling - Integrate chat service orchestration for AI message processing - Add typing indicators and stream cancellation for AI interactions - Implement mention parsing and context extraction for AI agents
176 lines
6.4 KiB
Rust
176 lines
6.4 KiB
Rust
#[cfg(test)]
|
|
mod tests {
|
|
use crate::service::RoomService;
|
|
use models::rooms::MessageContentType;
|
|
|
|
#[test]
|
|
fn test_parse_message_content_type_valid() {
|
|
assert!(matches!(RoomService::parse_message_content_type(Some("text".into())).unwrap(), MessageContentType::Text));
|
|
assert!(matches!(RoomService::parse_message_content_type(Some("image".into())).unwrap(), MessageContentType::Image));
|
|
assert!(matches!(RoomService::parse_message_content_type(Some("audio".into())).unwrap(), MessageContentType::Audio));
|
|
assert!(matches!(RoomService::parse_message_content_type(Some("video".into())).unwrap(), MessageContentType::Video));
|
|
assert!(matches!(RoomService::parse_message_content_type(Some("file".into())).unwrap(), MessageContentType::File));
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_message_content_type_case_insensitive() {
|
|
assert!(matches!(RoomService::parse_message_content_type(Some("TEXT".into())).unwrap(), MessageContentType::Text));
|
|
assert!(matches!(RoomService::parse_message_content_type(Some("Image".into())).unwrap(), MessageContentType::Image));
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_message_content_type_none_defaults_to_text() {
|
|
assert!(matches!(RoomService::parse_message_content_type(None).unwrap(), MessageContentType::Text));
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_message_content_type_invalid() {
|
|
assert!(RoomService::parse_message_content_type(Some("pdf".into())).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_validate_name_valid() {
|
|
assert!(RoomService::validate_name("test-room", 100).is_ok());
|
|
assert!(RoomService::validate_name("a", 100).is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_validate_name_empty() {
|
|
assert!(RoomService::validate_name("", 100).is_err());
|
|
assert!(RoomService::validate_name(" ", 100).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_validate_name_too_long() {
|
|
let long = "x".repeat(101);
|
|
assert!(RoomService::validate_name(&long, 100).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_validate_content_valid() {
|
|
assert!(RoomService::validate_content("hello", 10000).is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_validate_content_empty() {
|
|
assert!(RoomService::validate_content("", 10000).is_err());
|
|
assert!(RoomService::validate_content(" ", 10000).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_validate_content_too_long() {
|
|
let long = "x".repeat(10001);
|
|
assert!(RoomService::validate_content(&long, 10000).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_sanitize_content_removes_script_tag() {
|
|
let input = "<script>alert('xss')</script>";
|
|
let result = RoomService::sanitize_content(input);
|
|
assert!(!result.contains("<script>"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_sanitize_content_blocks_javascript_uri() {
|
|
let input = "javascript:alert(1)";
|
|
let result = RoomService::sanitize_content(input);
|
|
assert_eq!(result, "javascript:alert(1)");
|
|
}
|
|
|
|
#[test]
|
|
fn test_sanitize_content_blocks_onerror() {
|
|
let input = r#"<img src=x onerror="alert(1)">"#;
|
|
let result = RoomService::sanitize_content(input);
|
|
assert!(!result.contains("onerror"));
|
|
assert!(result.contains("<img"));
|
|
assert!(!result.contains("alert"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_sanitize_content_preserves_safe_content() {
|
|
let input = "Hello <strong>world</strong>";
|
|
let result = RoomService::sanitize_content(input);
|
|
assert!(result.contains("Hello"));
|
|
assert!(result.contains("<strong>"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_room_event_type_from_str_roundtrip() {
|
|
for variant in [
|
|
crate::RoomEventType::RoomCreated,
|
|
crate::RoomEventType::RoomDeleted,
|
|
crate::RoomEventType::NewMessage,
|
|
crate::RoomEventType::MessageEdited,
|
|
crate::RoomEventType::MessageRevoked,
|
|
crate::RoomEventType::MemberJoined,
|
|
] {
|
|
let s = variant.as_str();
|
|
let parsed = crate::RoomEventType::from_str(s);
|
|
assert_eq!(parsed, Some(variant));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_room_event_type_from_str_unknown() {
|
|
assert_eq!(crate::RoomEventType::from_str("unknown_event"), None);
|
|
}
|
|
|
|
#[test]
|
|
fn test_mention_bracket_re_matches_ai_model() {
|
|
let re = crate::service::mention_bracket_re();
|
|
let caps: Vec<_> = re.captures_iter("@[ai:550e8400-0000-0000-0000-000000000001:GPT-4]").collect();
|
|
assert_eq!(caps.len(), 1);
|
|
assert_eq!(&caps[0][1], "ai");
|
|
assert_eq!(&caps[0][2], "550e8400-0000-0000-0000-000000000001");
|
|
}
|
|
|
|
#[test]
|
|
fn test_mention_bracket_re_matches_user() {
|
|
let re = crate::service::mention_bracket_re();
|
|
let caps: Vec<_> = re.captures_iter("@[user:850e8400-0000-0000-0000-000000000002:John]").collect();
|
|
assert_eq!(caps.len(), 1);
|
|
assert_eq!(&caps[0][1], "user");
|
|
}
|
|
|
|
#[test]
|
|
fn test_mention_bracket_re_matches_repo() {
|
|
let re = crate::service::mention_bracket_re();
|
|
let caps: Vec<_> = re.captures_iter("@[repo:my-repo:My Repository]").collect();
|
|
assert_eq!(caps.len(), 1);
|
|
assert_eq!(&caps[0][1], "repo");
|
|
}
|
|
|
|
#[test]
|
|
fn test_mention_bracket_re_no_match_plain_text() {
|
|
let re = crate::service::mention_bracket_re();
|
|
let caps: Vec<_> = re.captures_iter("Hello world").collect();
|
|
assert_eq!(caps.len(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_mention_multiple_in_same_message() {
|
|
let re = crate::service::mention_bracket_re();
|
|
let content = "@[ai:uuid1:Model1] and @[user:uuid2:User2]";
|
|
let caps: Vec<_> = re.captures_iter(content).collect();
|
|
assert_eq!(caps.len(), 2);
|
|
}
|
|
|
|
#[test]
|
|
fn test_mention_tag_re_legacy_format() {
|
|
let re = crate::service::mention_tag_re();
|
|
let content = r#"<mention type="ai" id="model-uuid">GPT-4</mention>"#;
|
|
let caps: Vec<_> = re.captures_iter(content).collect();
|
|
assert_eq!(caps.len(), 1);
|
|
assert_eq!(&caps[0][1], "ai");
|
|
assert_eq!(&caps[0][2], "model-uuid");
|
|
}
|
|
|
|
#[test]
|
|
fn test_mention_combined_brackets_and_tags() {
|
|
let bracket_re = crate::service::mention_bracket_re();
|
|
let tag_re = crate::service::mention_tag_re();
|
|
let content = r#"@[ai:uuid1:A] <mention type="ai" id="uuid2">B</mention>"#;
|
|
assert_eq!(bracket_re.captures_iter(content).count(), 1);
|
|
assert_eq!(tag_re.captures_iter(content).count(), 1);
|
|
}
|
|
} |