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
This commit is contained in:
zhenyi
2026-06-11 12:11:05 +08:00
parent 06e8ee96a5
commit 821537186e
111 changed files with 10458 additions and 385 deletions
+123
View File
@@ -0,0 +1,123 @@
//! @mention tracking — maps to `message_mention` table.
//!
//! Parsed from message body on send. Used for notification and
//! "mentions" feed.
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
/// An @mention of a user in a message.
///
/// Maps to the `message_mention` table. Parsed from message body on send
/// and used for notifications and the mentions feed.
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
pub struct MessageMention {
pub id: Uuid,
pub message_id: Uuid,
pub channel_id: Uuid,
pub mentioned_user_id: Uuid,
pub mentioned_by: Uuid,
/// When the mentioned user read the notification.
pub read_at: Option<DateTime<Utc>>,
pub created_at: DateTime<Utc>,
}
/// Parse @username mentions from a message body.
/// Returns unique usernames (without the `@` prefix).
///
/// Matches `@word` where word is `[a-zA-Z0-9_-]+`, min 2 chars.
/// Ignores `@@` (escaped) and mentions inside code spans/blocks.
pub fn parse_mentions(body: &str) -> Vec<String> {
let mut seen = std::collections::HashSet::new();
let mut mentions = Vec::new();
// Simple state machine: skip content inside backtick spans.
let mut in_code = false;
let bytes = body.as_bytes();
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'`' {
in_code = !in_code;
i += 1;
continue;
}
if !in_code && bytes[i] == b'@' {
// Skip escaped @@
if i + 1 < bytes.len() && bytes[i + 1] == b'@' {
i += 2;
continue;
}
let start = i + 1;
let mut end = start;
while end < bytes.len()
&& (bytes[end].is_ascii_alphanumeric() || bytes[end] == b'_' || bytes[end] == b'-')
{
end += 1;
}
let len = end - start;
if len >= 2 {
let name = body[start..end].to_lowercase();
if seen.insert(name.clone()) {
mentions.push(name);
}
}
i = end;
} else {
i += 1;
}
}
mentions
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_mentions_basic() {
let m = parse_mentions("hey @alice, cc @bob");
assert_eq!(m, vec!["alice".to_string(), "bob".to_string()]);
}
#[test]
fn test_parse_mentions_dedup() {
let m = parse_mentions("@alice said hi to @alice");
assert_eq!(m, vec!["alice".to_string()]);
}
#[test]
fn test_parse_mentions_case_insensitive() {
let m = parse_mentions("@Alice and @ALICE");
assert_eq!(m, vec!["alice".to_string()]);
}
#[test]
fn test_parse_mentions_skip_code_span() {
let m = parse_mentions("use `@here` to notify @alice");
assert_eq!(m, vec!["alice".to_string()]);
}
#[test]
fn test_parse_mentions_skip_escaped() {
let m = parse_mentions("@@alice is not a mention, but @bob is");
assert_eq!(m, vec!["bob".to_string()]);
}
#[test]
fn test_parse_mentions_short_name_ignored() {
let m = parse_mentions("@a is too short, @ab is ok");
assert_eq!(m, vec!["ab".to_string()]);
}
#[test]
fn test_parse_mentions_empty() {
assert!(parse_mentions("no mentions here").is_empty());
assert!(parse_mentions("").is_empty());
}
}