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:
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user