fix(channels): enforce reply_target naming consistency
This commit is contained in:
parent
a7a580a479
commit
cad7fb8f22
3 changed files with 74 additions and 4 deletions
|
|
@ -13,7 +13,7 @@ pub struct ChannelMessage {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub sender: String,
|
pub sender: String,
|
||||||
/// Channel-specific reply address (e.g. Telegram chat_id, Discord channel_id).
|
/// Channel-specific reply address (e.g. Telegram chat_id, Discord channel_id).
|
||||||
pub reply_to: String,
|
pub reply_target: String,
|
||||||
pub content: String,
|
pub content: String,
|
||||||
pub channel: String,
|
pub channel: String,
|
||||||
pub timestamp: u64,
|
pub timestamp: u64,
|
||||||
|
|
@ -97,7 +97,7 @@ impl Channel for TelegramChannel {
|
||||||
let channel_msg = ChannelMessage {
|
let channel_msg = ChannelMessage {
|
||||||
id: msg["message_id"].to_string(),
|
id: msg["message_id"].to_string(),
|
||||||
sender,
|
sender,
|
||||||
reply_to: chat_id,
|
reply_target: chat_id,
|
||||||
content: msg["text"].as_str().unwrap_or("").to_string(),
|
content: msg["text"].as_str().unwrap_or("").to_string(),
|
||||||
channel: "telegram".into(),
|
channel: "telegram".into(),
|
||||||
timestamp: msg["date"].as_u64().unwrap_or(0),
|
timestamp: msg["date"].as_u64().unwrap_or(0),
|
||||||
|
|
|
||||||
|
|
@ -551,7 +551,7 @@ impl Channel for IrcChannel {
|
||||||
// Determine reply target: if sent to a channel, reply to channel;
|
// Determine reply target: if sent to a channel, reply to channel;
|
||||||
// if DM (target == our nick), reply to sender
|
// if DM (target == our nick), reply to sender
|
||||||
let is_channel = target.starts_with('#') || target.starts_with('&');
|
let is_channel = target.starts_with('#') || target.starts_with('&');
|
||||||
let reply_to = if is_channel {
|
let reply_target = if is_channel {
|
||||||
target.to_string()
|
target.to_string()
|
||||||
} else {
|
} else {
|
||||||
sender_nick.to_string()
|
sender_nick.to_string()
|
||||||
|
|
@ -566,7 +566,7 @@ impl Channel for IrcChannel {
|
||||||
let channel_msg = ChannelMessage {
|
let channel_msg = ChannelMessage {
|
||||||
id: format!("irc_{}_{seq}", chrono::Utc::now().timestamp_millis()),
|
id: format!("irc_{}_{seq}", chrono::Utc::now().timestamp_millis()),
|
||||||
sender: sender_nick.to_string(),
|
sender: sender_nick.to_string(),
|
||||||
reply_target: reply_to,
|
reply_target,
|
||||||
content,
|
content,
|
||||||
channel: "irc".to_string(),
|
channel: "irc".to_string(),
|
||||||
timestamp: std::time::SystemTime::now()
|
timestamp: std::time::SystemTime::now()
|
||||||
|
|
|
||||||
70
tests/reply_target_field_regression.rs
Normal file
70
tests/reply_target_field_regression.rs
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
//! Regression guard for ChannelMessage field naming consistency.
|
||||||
|
//!
|
||||||
|
//! This test prevents accidental reintroduction of the removed `reply_to` field
|
||||||
|
//! in Rust source code where `reply_target` must be used.
|
||||||
|
|
||||||
|
use std::fs;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
const SCAN_PATHS: &[&str] = &["src", "examples"];
|
||||||
|
const FORBIDDEN_PATTERNS: &[&str] = &[".reply_to", "reply_to:"];
|
||||||
|
|
||||||
|
fn collect_rs_files(dir: &Path, out: &mut Vec<PathBuf>) {
|
||||||
|
let entries = fs::read_dir(dir)
|
||||||
|
.unwrap_or_else(|err| panic!("Failed to read directory {}: {err}", dir.display()));
|
||||||
|
|
||||||
|
for entry in entries {
|
||||||
|
let entry =
|
||||||
|
entry.unwrap_or_else(|err| panic!("Failed to read entry in {}: {err}", dir.display()));
|
||||||
|
let path = entry.path();
|
||||||
|
|
||||||
|
if path.is_dir() {
|
||||||
|
collect_rs_files(&path, out);
|
||||||
|
} else if path.extension().is_some_and(|ext| ext == "rs") {
|
||||||
|
out.push(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn source_does_not_use_legacy_reply_to_field() {
|
||||||
|
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||||
|
let mut rust_files = Vec::new();
|
||||||
|
|
||||||
|
for relative in SCAN_PATHS {
|
||||||
|
collect_rs_files(&root.join(relative), &mut rust_files);
|
||||||
|
}
|
||||||
|
|
||||||
|
rust_files.sort();
|
||||||
|
|
||||||
|
let mut violations = Vec::new();
|
||||||
|
|
||||||
|
for file_path in rust_files {
|
||||||
|
let content = fs::read_to_string(&file_path).unwrap_or_else(|err| {
|
||||||
|
panic!("Failed to read source file {}: {err}", file_path.display())
|
||||||
|
});
|
||||||
|
|
||||||
|
for (line_idx, line) in content.lines().enumerate() {
|
||||||
|
for pattern in FORBIDDEN_PATTERNS {
|
||||||
|
if line.contains(pattern) {
|
||||||
|
let rel = file_path
|
||||||
|
.strip_prefix(root)
|
||||||
|
.unwrap_or(&file_path)
|
||||||
|
.display()
|
||||||
|
.to_string();
|
||||||
|
violations.push(format!(
|
||||||
|
"{rel}:{} contains forbidden pattern `{pattern}`: {}",
|
||||||
|
line_idx + 1,
|
||||||
|
line.trim()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
violations.is_empty(),
|
||||||
|
"Found legacy `reply_to` field usage:\n{}",
|
||||||
|
violations.join("\n")
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue