const PNG_BYTES: &[u8] = b"\x89PNG\r\n\x1a\n\x00\x01\x02\x03\x04\x05\x06\x07";
mod scan {
use super::PNG_BYTES;
use crate::utils::image::{
LocalFileScan, LocalImageFailure, LocalImageFailureReason, TELEGRAM_DOCUMENT_MAX_BYTES,
append_file_failure_notice, extract_local_files, file_failure_notice, file_marker_text,
validate_local_file,
};
use std::path::{Path, PathBuf};
const PDF_BYTES: &[u8] = b"%PDF-1.7\n1 0 obj\n<<>>\nendobj\n";
fn write_file(dir: &Path, name: &str, bytes: &[u8]) -> PathBuf {
let path = dir.join(name);
std::fs::write(&path, bytes).expect("write fixture");
path
}
fn paths(scan: &LocalFileScan) -> Vec<PathBuf> {
scan.attachments.iter().map(|f| f.path.clone()).collect()
}
fn failure(raw: &str) -> LocalImageFailure {
LocalImageFailure {
raw: raw.to_string(),
resolved: None,
reason: LocalImageFailureReason::NotFound,
}
}
#[test]
fn a_resolved_link_becomes_a_visible_marker() {
let dir = tempfile::tempdir().expect("tempdir");
let pdf = write_file(dir.path(), "q3.pdf", PDF_BYTES);
let scan = extract_local_files(
&format!("before [Q3 report]({}) after", pdf.display()),
None,
);
assert_eq!(scan.text, "before 📎 Q3 report after");
assert_eq!(paths(&scan), vec![pdf]);
assert_eq!(scan.attachments[0].caption.as_deref(), Some("Q3 report"));
let span = scan.attachments[0]
.marker_span
.clone()
.expect("the scan records the marker's span");
assert_eq!(&scan.text[span], "📎 Q3 report");
assert!(scan.failures.is_empty());
}
#[test]
fn an_empty_label_yields_no_caption() {
let dir = tempfile::tempdir().expect("tempdir");
let pdf = write_file(dir.path(), "q3.pdf", PDF_BYTES);
let scan = extract_local_files(&format!("[]({})", pdf.display()), None);
assert_eq!(paths(&scan), vec![pdf]);
assert_eq!(
scan.attachments[0].caption, None,
"an empty label is not a caption"
);
}
#[test]
fn an_empty_label_falls_back_to_the_basename() {
let dir = tempfile::tempdir().expect("tempdir");
let pdf = write_file(dir.path(), "q3.pdf", PDF_BYTES);
let scan = extract_local_files(&format!("[]({})", pdf.display()), None);
assert_eq!(scan.text, "📎 q3.\u{200b}pdf", "the marker is never blank");
let span = scan.attachments[0]
.marker_span
.clone()
.expect("the scan records the marker's span");
assert_eq!(&scan.text[span], "📎 q3.\u{200b}pdf");
}
#[test]
fn a_dotted_label_does_not_autolink_as_a_domain() {
let out = file_marker_text("1918-fix-state.md", "/tmp/1918-fix-state.md");
assert_eq!(
out, "1918-fix-state.\u{200b}md",
"the dot the client keys on is followed by an invisible breaker"
);
assert!(
!out.contains(".md"),
"no bare `.md` survives for the client's TLD detector to match"
);
assert_eq!(
out.replace('\u{200b}', ""),
"1918-fix-state.md",
"the breaker is pure ink: removing it restores the file's own name"
);
}
#[test]
fn a_dot_with_no_alphanumeric_after_gets_no_breaker() {
assert_eq!(file_marker_text("done.", "/tmp/done."), "done.");
assert_eq!(
file_marker_text("v1.2 final", "/tmp/v1"),
"v1.\u{200b}2 final",
"the pattern is the dot plus what follows, not the token's shape"
);
}
#[test]
fn the_marker_span_indexes_the_final_text() {
let dir = tempfile::tempdir().expect("tempdir");
let pdf = write_file(dir.path(), "q3.pdf", PDF_BYTES);
let scan = extract_local_files(&format!(" \n [Q3 report]({}) ", pdf.display()), None);
assert_eq!(scan.text, "📎 Q3 report");
let span = scan.attachments[0]
.marker_span
.clone()
.expect("the scan records the marker's span");
assert_eq!(&scan.text[span], "📎 Q3 report");
}
#[test]
fn two_files_attach_in_order_of_appearance() {
let dir = tempfile::tempdir().expect("tempdir");
let a = write_file(dir.path(), "a.pdf", PDF_BYTES);
let b = write_file(dir.path(), "b.pdf", PDF_BYTES);
let scan = extract_local_files(
&format!("[A]({}) then [B]({})", a.display(), b.display()),
None,
);
assert_eq!(paths(&scan), vec![a, b]);
assert_eq!(scan.text, "📎 A then 📎 B");
let span_a = scan.attachments[0]
.marker_span
.clone()
.expect("the scan records the marker's span");
let span_b = scan.attachments[1]
.marker_span
.clone()
.expect("the scan records the marker's span");
assert_eq!(&scan.text[span_a], "📎 A");
assert_eq!(&scan.text[span_b], "📎 B");
}
#[test]
fn an_angle_bracket_target_holds_spaces() {
let dir = tempfile::tempdir().expect("tempdir");
let pdf = write_file(dir.path(), "Q3 final.pdf", PDF_BYTES);
let scan = extract_local_files(&format!("x [Q3](<{}>) y", pdf.display()), None);
assert_eq!(scan.text, "x 📎 Q3 y");
assert_eq!(paths(&scan), vec![pdf]);
}
#[test]
fn a_title_after_the_target_is_ignored_and_the_label_captions() {
let dir = tempfile::tempdir().expect("tempdir");
let pdf = write_file(dir.path(), "q3.pdf", PDF_BYTES);
let scan = extract_local_files(
&format!("x [Q3 report]({} \"quarterly\") y", pdf.display()),
None,
);
assert_eq!(scan.text, "x 📎 Q3 report y");
assert_eq!(scan.attachments[0].caption.as_deref(), Some("Q3 report"));
}
#[test]
fn a_relative_target_resolves_against_the_base_dir() {
let dir = tempfile::tempdir().expect("tempdir");
std::fs::create_dir(dir.path().join("reports")).expect("mkdir");
let pdf = write_file(&dir.path().join("reports"), "q3.pdf", PDF_BYTES);
let scan = extract_local_files("see [report](reports/q3.pdf) here", Some(dir.path()));
assert_eq!(scan.text, "see 📎 report here");
assert_eq!(paths(&scan), vec![pdf]);
assert_eq!(scan.attachments[0].caption.as_deref(), Some("report"));
}
#[test]
fn a_relative_target_without_a_base_dir_stays_literal() {
let text = "see [report](reports/q3.pdf) here";
let scan = extract_local_files(text, None);
assert_eq!(scan.text, text, "no base directory to resolve against");
assert!(scan.attachments.is_empty());
assert!(scan.failures.is_empty());
}
#[test]
fn a_remote_link_is_left_alone() {
let text = "see [the site](https://example.com/a) for details";
let scan = extract_local_files(text, None);
assert_eq!(scan.text, text, "Telegram resolves a real URL itself");
assert!(scan.attachments.is_empty());
assert!(scan.failures.is_empty(), "a URL is not a missing file");
}
#[test]
fn an_image_reference_is_not_claimed_by_the_file_family() {
let dir = tempfile::tempdir().expect("tempdir");
let png = write_file(dir.path(), "chart.png", PNG_BYTES);
let text = format!("x  y", png.display());
let scan = extract_local_files(&text, None);
assert_eq!(scan.text, text, "the image family owns ``");
assert!(scan.attachments.is_empty());
assert!(scan.failures.is_empty());
}
#[test]
fn a_link_inside_a_code_span_is_left_alone() {
let dir = tempfile::tempdir().expect("tempdir");
let pdf = write_file(dir.path(), "q3.pdf", PDF_BYTES);
let text = format!("`[Q3 report]({})`", pdf.display());
let scan = extract_local_files(&text, None);
assert_eq!(
scan.text, text,
"a code span is documentation, not a deliverable"
);
assert!(scan.attachments.is_empty());
assert!(scan.failures.is_empty());
}
#[test]
fn a_link_inside_a_code_fence_is_left_alone() {
let dir = tempfile::tempdir().expect("tempdir");
let pdf = write_file(dir.path(), "q3.pdf", PDF_BYTES);
let text = format!("```\n[Q3 report]({})\n```", pdf.display());
let scan = extract_local_files(&text, None);
assert_eq!(scan.text, text);
assert!(scan.attachments.is_empty());
}
#[test]
fn an_escaped_link_stays_literal() {
let dir = tempfile::tempdir().expect("tempdir");
let pdf = write_file(dir.path(), "q3.pdf", PDF_BYTES);
let text = format!("\\[Q3 report]({})", pdf.display());
let scan = extract_local_files(&text, None);
assert_eq!(scan.text, text);
assert!(scan.attachments.is_empty());
}
#[test]
fn a_non_file_scheme_or_anchor_is_left_alone() {
for text in [
"mail [me](mailto:a@b.c)",
"call [me](tel:+1234)",
"see [anchor](#section)",
"see [nothing]()",
] {
let scan = extract_local_files(text, None);
assert_eq!(scan.text, text, "{text} must survive byte-identical");
assert!(scan.attachments.is_empty(), "{text}");
assert!(scan.failures.is_empty(), "{text}");
}
}
#[test]
fn prose_that_merely_looks_like_a_link_is_untouched() {
for text in ["see [1] and [2] for details", "a [b] c", "[standalone]"] {
let scan = extract_local_files(text, None);
assert_eq!(scan.text, text);
assert!(scan.attachments.is_empty());
assert!(scan.failures.is_empty());
}
}
#[test]
fn a_missing_file_is_reported_and_the_link_survives() {
let dir = tempfile::tempdir().expect("tempdir");
let missing = dir.path().join("gone.pdf");
let raw = format!("[Q3 report]({})", missing.display());
let text = format!("x {raw} y");
let scan = extract_local_files(&text, Some(dir.path()));
assert_eq!(
scan.text, text,
"a rejected link keeps its label: it is the reader's only clue"
);
assert!(scan.attachments.is_empty());
assert_eq!(scan.failures.len(), 1);
assert_eq!(scan.failures[0].reason, LocalImageFailureReason::NotFound);
assert_eq!(scan.failures[0].raw, raw);
assert_eq!(
scan.failures[0].resolved.as_deref(),
Some(missing.as_path())
);
}
#[test]
fn a_directory_is_not_a_regular_file() {
let dir = tempfile::tempdir().expect("tempdir");
let sub = dir.path().join("reports");
std::fs::create_dir(&sub).expect("mkdir");
let scan = extract_local_files(&format!("[x]({})", sub.display()), None);
assert!(scan.attachments.is_empty());
assert_eq!(scan.failures[0].reason, LocalImageFailureReason::NotAFile);
}
#[test]
fn an_empty_file_is_reported_as_empty() {
let dir = tempfile::tempdir().expect("tempdir");
let empty = write_file(dir.path(), "empty.pdf", b"");
let scan = extract_local_files(&format!("[x]({})", empty.display()), None);
assert!(scan.attachments.is_empty());
assert_eq!(scan.failures[0].reason, LocalImageFailureReason::Empty);
}
#[test]
fn a_file_past_the_document_ceiling_is_reported_as_too_large() {
let dir = tempfile::tempdir().expect("tempdir");
let big = dir.path().join("big.pdf");
std::fs::File::create(&big)
.expect("create")
.set_len(TELEGRAM_DOCUMENT_MAX_BYTES + 1)
.expect("set_len");
let scan = extract_local_files(&format!("[x]({})", big.display()), None);
assert!(scan.attachments.is_empty());
assert_eq!(scan.failures[0].reason, LocalImageFailureReason::TooLarge);
}
#[test]
fn validate_accepts_a_readable_non_empty_file() {
let dir = tempfile::tempdir().expect("tempdir");
let pdf = write_file(dir.path(), "q3.pdf", PDF_BYTES);
assert!(validate_local_file(&pdf).is_ok());
}
#[test]
fn no_failures_produces_no_file_notice() {
assert!(file_failure_notice(&[]).is_none());
}
#[test]
fn a_file_notice_names_the_reference_and_the_reason() {
let notice =
file_failure_notice(&[failure("[Q3 report](/root/reports/q3.pdf)")]).expect("notice");
assert!(notice.contains("File not attached"), "{notice}");
assert!(notice.contains("a file"), "{notice}");
assert!(
notice.contains("[Q3 report](/root/reports/q3.pdf)"),
"{notice}"
);
assert!(notice.contains("file not found"), "{notice}");
}
#[test]
fn appending_a_file_notice_keeps_the_body_and_adds_a_blank_line() {
let body = append_file_failure_notice("hello", &[failure("[x](/nope)")]);
assert!(body.starts_with("hello\n\n⚠️ File not attached"), "{body}");
}
#[test]
fn an_empty_body_becomes_the_file_notice_alone() {
let body = append_file_failure_notice(" ", &[failure("[x](/nope)")]);
assert!(body.starts_with("⚠️ File not attached"), "{body}");
}
#[test]
fn appending_without_failures_returns_the_body_untouched() {
assert_eq!(append_file_failure_notice("hello", &[]), "hello");
}
}
mod floor {
use crate::channels::telegram::delivery::{DeliveredFile, send_local_files};
use crate::utils::image::{LocalFile, LocalImageFailureReason};
use std::path::{Path, PathBuf};
fn delivered_paths(delivered: &[DeliveredFile]) -> Vec<PathBuf> {
delivered.iter().map(|f| f.path.clone()).collect()
}
const CHAT: i64 = 133_526_395;
const PDF_BYTES: &[u8] = b"%PDF-1.7\n1 0 obj\n<<>>\nendobj\n";
const SEND_MESSAGE_OK: &str = r#"{"ok":true,"result":{"message_id":701,"date":1757166400,"chat":{"id":133526395,"type":"private"},"text":"ok"}}"#;
const REFUSED: &str =
r#"{"ok":false,"error_code":413,"description":"Request Entity Too Large"}"#;
fn test_bot(server: &mockito::ServerGuard) -> teloxide::Bot {
teloxide::Bot::with_client(
"TESTTOKEN",
reqwest_teloxide::Client::builder().build().unwrap(),
)
.set_api_url(server.url().parse().unwrap())
}
fn file_at(path: PathBuf, caption: Option<&str>) -> LocalFile {
LocalFile {
path,
caption: caption.map(str::to_string),
marker_span: None,
}
}
fn write_fixture(dir: &Path, name: &str, bytes: &[u8]) -> PathBuf {
let path = dir.join(name);
std::fs::write(&path, bytes).expect("write fixture");
path
}
#[tokio::test]
async fn an_unreadable_file_returns_a_failure_entry() {
let dir = tempfile::tempdir().expect("tempdir");
let missing = dir.path().join("gone.pdf");
let server = mockito::Server::new_async().await;
let bot = test_bot(&server);
let (delivered, failures) = send_local_files(
uuid::Uuid::new_v4(),
&bot,
teloxide::types::ChatId(CHAT),
None,
&[file_at(missing.clone(), None)],
)
.await;
assert!(delivered.is_empty(), "nothing was delivered");
assert_eq!(failures.len(), 1, "the unreadable file must be reported");
assert_eq!(failures[0].raw, missing.display().to_string());
assert_eq!(
failures[0].reason,
LocalImageFailureReason::Unreadable,
"a path that cannot be read is a REFERENCE problem, not a delivery one"
);
}
#[tokio::test]
async fn a_file_the_channel_refuses_returns_a_delivery_failure() {
let dir = tempfile::tempdir().expect("tempdir");
let report = write_fixture(dir.path(), "q3.pdf", PDF_BYTES);
let mut server = mockito::Server::new_async().await;
let document_mock = server
.mock("POST", "/botTESTTOKEN/SendDocument")
.with_status(413)
.with_header("content-type", "application/json")
.with_body(REFUSED)
.expect(1)
.create_async()
.await;
let bot = test_bot(&server);
let (delivered, failures) = send_local_files(
uuid::Uuid::new_v4(),
&bot,
teloxide::types::ChatId(CHAT),
None,
&[file_at(report.clone(), Some("Q3 report"))],
)
.await;
document_mock.assert_async().await;
assert!(
delivered.is_empty(),
"the channel refused it, so nothing was delivered"
);
assert_eq!(failures.len(), 1, "the refusal must be reported");
assert_eq!(
failures[0].reason,
LocalImageFailureReason::DeliveryFailed,
"a file the channel refused is a DELIVERY problem: the reference itself was fine"
);
}
#[tokio::test]
async fn a_resolved_link_ships_exactly_one_document_captioned_by_its_label() {
let dir = tempfile::tempdir().expect("tempdir");
let report = write_fixture(dir.path(), "q3.pdf", PDF_BYTES);
let mut server = mockito::Server::new_async().await;
let document_mock = server
.mock("POST", "/botTESTTOKEN/SendDocument")
.match_body(mockito::Matcher::Regex("Q3 report".to_string()))
.with_status(200)
.with_header("content-type", "application/json")
.with_body(SEND_MESSAGE_OK)
.expect(1)
.create_async()
.await;
let bot = test_bot(&server);
let (delivered, failures) = send_local_files(
uuid::Uuid::new_v4(),
&bot,
teloxide::types::ChatId(CHAT),
None,
&[file_at(report.clone(), Some("Q3 report"))],
)
.await;
document_mock.assert_async().await;
assert_eq!(
delivered_paths(&delivered),
vec![report],
"the delivered path comes back"
);
assert_eq!(delivered[0].message_id, 701, "the bubble id comes back");
assert!(failures.is_empty(), "a delivered file reports no failure");
}
#[tokio::test]
async fn a_delivered_document_arrives_named_after_its_file() {
let dir = tempfile::tempdir().expect("tempdir");
let report = write_fixture(dir.path(), "q3-report.pdf", PDF_BYTES);
let mut server = mockito::Server::new_async().await;
let document_mock = server
.mock("POST", "/botTESTTOKEN/SendDocument")
.match_body(mockito::Matcher::Regex(
r#"filename="q3-report\.pdf""#.to_string(),
))
.with_status(200)
.with_header("content-type", "application/json")
.with_body(SEND_MESSAGE_OK)
.expect(1)
.create_async()
.await;
let bot = test_bot(&server);
let (_, failures) = send_local_files(
uuid::Uuid::new_v4(),
&bot,
teloxide::types::ChatId(CHAT),
None,
&[file_at(report, Some("Q3 report"))],
)
.await;
document_mock.assert_async().await;
assert!(failures.is_empty(), "a named document still delivers");
}
#[tokio::test]
async fn a_link_without_a_label_ships_the_document_with_no_caption() {
let dir = tempfile::tempdir().expect("tempdir");
let report = write_fixture(dir.path(), "q3.pdf", PDF_BYTES);
let mut server = mockito::Server::new_async().await;
let document_mock = server
.mock("POST", "/botTESTTOKEN/SendDocument")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(SEND_MESSAGE_OK)
.expect(1)
.create_async()
.await;
let bot = test_bot(&server);
let (delivered, failures) = send_local_files(
uuid::Uuid::new_v4(),
&bot,
teloxide::types::ChatId(CHAT),
None,
&[file_at(report.clone(), None)],
)
.await;
document_mock.assert_async().await;
assert_eq!(delivered_paths(&delivered), vec![report]);
assert!(failures.is_empty());
}
#[tokio::test]
async fn each_link_ships_its_own_bubble_in_order() {
let dir = tempfile::tempdir().expect("tempdir");
let first = write_fixture(dir.path(), "q3.pdf", PDF_BYTES);
let second = write_fixture(dir.path(), "q4.pdf", PDF_BYTES);
let mut server = mockito::Server::new_async().await;
let document_mock = server
.mock("POST", "/botTESTTOKEN/SendDocument")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(SEND_MESSAGE_OK)
.expect(2)
.create_async()
.await;
let bot = test_bot(&server);
let (delivered, failures) = send_local_files(
uuid::Uuid::new_v4(),
&bot,
teloxide::types::ChatId(CHAT),
None,
&[
file_at(first.clone(), Some("Q3")),
file_at(second.clone(), Some("Q4")),
],
)
.await;
document_mock.assert_async().await;
assert_eq!(
delivered_paths(&delivered),
vec![first, second],
"delivered in the reply's order"
);
assert!(failures.is_empty());
}
}
mod links {
use crate::channels::telegram::delivery::{file_message_link, link_file_markers};
use crate::utils::image::extract_local_files;
use std::path::Path;
const PDF_BYTES: &[u8] = b"%PDF-1.7\n1 0 obj\n<<>>\nendobj\n";
const SUPERGROUP_ID: i64 = -100_123_456_789_012;
fn write_fixture(dir: &Path, name: &str) -> std::path::PathBuf {
let path = dir.join(name);
std::fs::write(&path, PDF_BYTES).expect("write fixture");
path
}
fn chat_of_kind(v: serde_json::Value) -> teloxide::types::Chat {
serde_json::from_value(v).expect("chat json")
}
#[test]
fn a_supergroup_bubble_gets_a_message_link() {
let chat = chat_of_kind(serde_json::json!({
"id": SUPERGROUP_ID, "type": "supergroup", "title": "Ops",
}));
let link =
file_message_link(&chat.kind, SUPERGROUP_ID, None, 701).expect("supergroup links");
assert_eq!(link, "https://t.me/c/123456789012/701");
}
#[test]
fn a_topic_message_links_into_its_topic() {
let chat = chat_of_kind(serde_json::json!({
"id": SUPERGROUP_ID, "type": "supergroup", "title": "Ops", "is_forum": true,
}));
let thread = teloxide::types::ThreadId(teloxide::types::MessageId(42));
let link = file_message_link(&chat.kind, SUPERGROUP_ID, Some(thread), 701)
.expect("topic messages link");
assert_eq!(link, "https://t.me/c/123456789012/42/701");
}
#[test]
fn a_channel_bubble_gets_a_message_link() {
const CHANNEL_ID: i64 = -100_999_888_777_666;
let chat = chat_of_kind(serde_json::json!({
"id": CHANNEL_ID, "type": "channel", "title": "Announcements",
}));
let link =
file_message_link(&chat.kind, CHANNEL_ID, None, 5).expect("channel bubbles link");
assert_eq!(link, "https://t.me/c/999888777666/5");
}
#[test]
fn a_private_chat_has_no_message_link() {
let chat = chat_of_kind(serde_json::json!({
"id": 133_526_395, "type": "private", "first_name": "A",
}));
assert!(file_message_link(&chat.kind, 133_526_395, None, 701).is_none());
}
#[test]
fn a_basic_group_has_no_message_link() {
let chat = chat_of_kind(serde_json::json!({
"id": -456_789, "type": "group", "title": "Team",
}));
assert!(file_message_link(&chat.kind, -456_789, None, 701).is_none());
}
#[test]
fn a_delivered_files_link_is_spliced_onto_its_marker() {
let dir = tempfile::tempdir().expect("tempdir");
let pdf = write_fixture(dir.path(), "q3.pdf");
let scan = extract_local_files("Here is [Q3 report](q3.pdf).", Some(dir.path()));
assert_eq!(scan.text, "Here is 📎 Q3 report.");
let linked = link_file_markers(
&scan.text,
&scan,
&[(pdf, "https://t.me/c/123456789012/701".to_string())],
);
assert_eq!(
linked, "Here is [📎 Q3 report](https://t.me/c/123456789012/701).",
"the marker becomes a link to the bubble, everything else is untouched"
);
}
#[test]
fn prose_around_a_spliced_marker_is_untouched() {
let dir = tempfile::tempdir().expect("tempdir");
let pdf = write_fixture(dir.path(), "q3.pdf");
let scan = extract_local_files("before [x](q3.pdf) after", Some(dir.path()));
let linked = link_file_markers(
&scan.text,
&scan,
&[(pdf, "https://t.me/c/1/1".to_string())],
);
assert!(linked.starts_with("before "));
assert!(linked.ends_with(" after"));
assert!(linked.contains("[📎 x](https://t.me/c/1/1)"));
}
#[test]
fn a_moved_span_degrades_to_the_plain_marker() {
let dir = tempfile::tempdir().expect("tempdir");
let pdf = write_fixture(dir.path(), "q3.pdf");
let scan = extract_local_files("Here is [Q3 report](q3.pdf).", Some(dir.path()));
let shifted = format!(">> {}", scan.text);
let linked = link_file_markers(
&shifted,
&scan,
&[(pdf, "https://t.me/c/1/701".to_string())],
);
assert_eq!(linked, shifted, "a moved span is never spliced");
}
#[test]
fn an_absent_link_keeps_the_plain_marker() {
let dir = tempfile::tempdir().expect("tempdir");
write_fixture(dir.path(), "q3.pdf");
let scan = extract_local_files("Here is [Q3 report](q3.pdf).", Some(dir.path()));
assert_eq!(link_file_markers(&scan.text, &scan, &[]), scan.text);
let other = write_fixture(dir.path(), "other.pdf");
let linked = link_file_markers(
&scan.text,
&scan,
&[(other, "https://t.me/c/1/2".to_string())],
);
assert_eq!(linked, scan.text);
}
}
mod rich {
use crate::utils::image::{DOC_ID_PREFIX, extract_local_files, rewrite_local_files};
use std::path::{Path, PathBuf};
const PDF_BYTES: &[u8] = b"%PDF-1.7\n1 0 obj\n<<>>\nendobj\n";
fn write_fixture(dir: &Path, name: &str) -> PathBuf {
let path = dir.join(name);
std::fs::write(&path, PDF_BYTES).expect("write fixture");
path
}
#[test]
fn a_resolved_link_is_rewritten_in_place_as_a_document_reference() {
let dir = tempfile::tempdir().expect("tempdir");
let pdf = write_fixture(dir.path(), "q3.pdf");
let rw = rewrite_local_files(
"Read [report](q3.pdf) first.",
Some(dir.path()),
DOC_ID_PREFIX,
&[],
true,
);
assert_eq!(
rw.rich, "Read  first.",
"the reference takes the link's exact position"
);
assert_eq!(rw.entries.len(), 1, "one entry per rewritten reference");
assert_eq!(rw.entries[0].id, "doc0");
assert_eq!(rw.entries[0].file.path, pdf);
assert_eq!(
rw.entries[0].file.caption.as_deref(),
Some("report"),
"the label captions the document, as the floor does"
);
}
#[test]
fn two_files_get_doc0_and_doc1_in_order() {
let dir = tempfile::tempdir().expect("tempdir");
let first = write_fixture(dir.path(), "a.pdf");
let second = write_fixture(dir.path(), "b.pdf");
let rw = rewrite_local_files(
"[A](a.pdf) then [B](b.pdf)",
Some(dir.path()),
DOC_ID_PREFIX,
&[],
true,
);
assert_eq!(
rw.rich,
" then "
);
assert_eq!(rw.entries[0].file.path, first);
assert_eq!(rw.entries[1].file.path, second);
}
#[test]
fn a_remote_link_is_left_byte_identical() {
let rw = rewrite_local_files(
"See [docs](https://example.com/x.pdf) online.",
None,
DOC_ID_PREFIX,
&[],
true,
);
assert_eq!(rw.rich, "See [docs](https://example.com/x.pdf) online.");
assert!(rw.entries.is_empty());
}
#[test]
fn a_link_inside_a_code_span_is_left_byte_identical() {
let dir = tempfile::tempdir().expect("tempdir");
write_fixture(dir.path(), "q3.pdf");
let rw = rewrite_local_files(
"run `cat [x](q3.pdf)` now",
Some(dir.path()),
DOC_ID_PREFIX,
&[],
true,
);
assert_eq!(rw.rich, "run `cat [x](q3.pdf)` now");
assert!(rw.entries.is_empty());
}
#[test]
fn a_missing_file_stays_a_verbatim_link() {
let dir = tempfile::tempdir().expect("tempdir");
let rw = rewrite_local_files(
"See [ghost](nope.pdf).",
Some(dir.path()),
DOC_ID_PREFIX,
&[],
true,
);
assert_eq!(rw.rich, "See [ghost](nope.pdf).");
assert!(rw.entries.is_empty());
}
#[test]
fn an_already_delivered_file_is_consumed_not_referenced() {
let dir = tempfile::tempdir().expect("tempdir");
let pdf = write_fixture(dir.path(), "q3.pdf");
let rw = rewrite_local_files(
"Read [report](q3.pdf) first.",
Some(dir.path()),
DOC_ID_PREFIX,
&[pdf],
true,
);
assert_eq!(rw.rich, "Read first.", "the consumed link leaves quietly");
assert!(rw.entries.is_empty());
}
#[test]
fn a_custom_prefix_namespaces_the_ids() {
let dir = tempfile::tempdir().expect("tempdir");
write_fixture(dir.path(), "q3.pdf");
let rw = rewrite_local_files("[r](q3.pdf)", Some(dir.path()), "attachment", &[], true);
assert_eq!(rw.rich, "");
}
#[test]
fn an_empty_label_falls_back_to_the_basename_in_the_alt() {
let dir = tempfile::tempdir().expect("tempdir");
write_fixture(dir.path(), "q3.pdf");
let rw = rewrite_local_files("[](q3.pdf)", Some(dir.path()), DOC_ID_PREFIX, &[], true);
assert_eq!(
rw.rich, "",
"the alt is disarmed exactly as the text marker is (#1938)"
);
assert_eq!(rw.entries[0].file.caption, None, "no label, no caption");
}
#[test]
fn the_scan_and_the_rewrite_agree_on_what_is_a_file() {
let dir = tempfile::tempdir().expect("tempdir");
write_fixture(dir.path(), "q3.pdf");
let input = "[ok](q3.pdf) and [remote](https://x/y) and `[/code/q.pdf](q.pdf)`";
let scan = extract_local_files(input, Some(dir.path()));
let rw = rewrite_local_files(input, Some(dir.path()), DOC_ID_PREFIX, &[], true);
assert_eq!(scan.attachments.len(), rw.entries.len(), "same claims");
assert_eq!(scan.attachments[0].path, rw.entries[0].file.path);
assert!(scan.text.contains("📎"));
assert!(rw.rich.contains("[remote](https://x/y)"));
assert!(rw.rich.contains("`[/code/q.pdf](q.pdf)`"));
}
#[test]
fn a_dotted_label_is_disarmed_in_the_alt_but_not_the_caption() {
let dir = tempfile::tempdir().expect("tempdir");
write_fixture(dir.path(), "1918-fix-state.md");
let rw = rewrite_local_files(
"See [1918-fix-state.md](1918-fix-state.md) now.",
Some(dir.path()),
DOC_ID_PREFIX,
&[],
true,
);
assert_eq!(
rw.rich, "See  now.",
"the alt is disarmed exactly as the text marker is"
);
assert_eq!(
rw.entries[0].file.caption.as_deref(),
Some("1918-fix-state.md"),
"the caption stays verbatim: only visible marker text is disarmed"
);
}
#[test]
fn a_markdown_reference_inlines_when_inlining_is_opted_into() {
let dir = tempfile::tempdir().expect("tempdir");
write_fixture(dir.path(), "notes.md");
write_fixture(dir.path(), "notes.markdown");
for name in ["notes.md", "notes.markdown"] {
let rw = rewrite_local_files(
&format!("See [{name}]({name})."),
Some(dir.path()),
DOC_ID_PREFIX,
&[],
true,
);
assert!(
rw.rich.contains("tg://document?id=doc0"),
"{name} must inline by default"
);
assert_eq!(rw.entries.len(), 1, "{name} must produce its entry");
}
}
#[test]
fn a_markdown_reference_stays_a_marker_when_inlining_is_off() {
let dir = tempfile::tempdir().expect("tempdir");
write_fixture(dir.path(), "notes.md");
write_fixture(dir.path(), "q3.pdf");
let rw = rewrite_local_files(
"Notes [here](notes.md) and [report](q3.pdf) attached.",
Some(dir.path()),
DOC_ID_PREFIX,
&[],
false,
);
assert!(
rw.rich.contains("📎 here"),
"the markdown reference becomes the marker, not a media reference"
);
assert!(
rw.rich.contains(""),
"the pdf takes the first media id and inlines as before"
);
assert_eq!(rw.entries.len(), 1, "only the pdf becomes media");
assert_eq!(
rw.entries[0].file.path.file_name(),
Some(std::ffi::OsStr::new("q3.pdf"))
);
}
#[test]
fn a_hand_written_document_reference_passes_through_byte_identical() {
let dir = tempfile::tempdir().expect("tempdir");
write_fixture(dir.path(), "q3.pdf");
let rw = rewrite_local_files(
"See  and [report](q3.pdf).",
Some(dir.path()),
DOC_ID_PREFIX,
&[],
true,
);
assert!(
rw.rich.contains(""),
"a hand-written tg:// reference survives untouched: {:?}",
rw.rich
);
assert_eq!(rw.entries.len(), 1, "only the local link produces an entry");
assert_eq!(rw.entries[0].id, "doc0");
}
}
mod intermediate {
use crate::channels::telegram::TelegramState;
use crate::channels::telegram::flow::StreamingState;
use crate::channels::telegram::intermediates::deliver_intermediate_message;
use std::path::{Path, PathBuf};
use std::sync::Arc;
const CHAT: i64 = 133_526_395;
const PDF_BYTES: &[u8] = b"%PDF-1.7\n1 0 obj\n<<>>\nendobj\n";
const RICH_OK: &str = r#"{"ok":true,"result":{"message_id":812}}"#;
const SEND_MESSAGE_OK: &str = r#"{"ok":true,"result":{"message_id":701,"date":1757166400,"chat":{"id":133526395,"type":"private"},"text":"ok"}}"#;
fn write_fixture(dir: &Path, name: &str) -> PathBuf {
let path = dir.join(name);
std::fs::write(&path, PDF_BYTES).expect("write fixture");
path
}
fn test_bot(server: &mockito::ServerGuard) -> teloxide::Bot {
teloxide::Bot::with_client(
"TESTTOKEN",
reqwest_teloxide::Client::builder().build().unwrap(),
)
.set_api_url(server.url().parse().unwrap())
}
fn streaming() -> Arc<std::sync::Mutex<StreamingState>> {
Arc::new(std::sync::Mutex::new(StreamingState {
is_dm: true,
pending_suggestions: None,
pending_trailer: None,
delivered_files: Vec::new(),
media_intermediates: Vec::new(),
msg_id: None,
thinking: String::new(),
tool_msgs: Vec::new(),
display_queue: Vec::new(),
open_group_msg_id: None,
rich_transport_failures: 0,
flow_entries: Vec::new(),
flow_status: None,
flow_rich: false,
response: String::new(),
final_bubble: None,
dirty: false,
recreate: false,
header_preview: None,
compacting: false,
sections: Default::default(),
retained_goal: None,
applied_plan_kb: Default::default(),
tool_round_count: 0,
tools_started_at: None,
turn_started_at: std::time::Instant::now(),
flow_outcome: None,
bg_indicator: None,
bg_count: None,
subagent_counts: Default::default(),
sent_intermediates: Vec::new(),
intermediate_msg_ids: Vec::new(),
voice_msg_ids: Vec::new(),
processing: true,
is_cli: false,
}))
}
#[tokio::test]
async fn a_report_with_a_file_inlines_it_in_the_rich_media_array() {
let dir = tempfile::tempdir().expect("tempdir");
let pdf = write_fixture(dir.path(), "q3.pdf");
let mut server = mockito::Server::new_async().await;
let rich_mock = server
.mock("POST", "/botTESTTOKEN/sendRichMessage")
.match_body(mockito::Matcher::Regex(
"tg://document\\?id=doc0".to_string(),
))
.with_status(200)
.with_header("content-type", "application/json")
.with_body(RICH_OK)
.expect(1)
.create_async()
.await;
let document_mock = server
.mock("POST", "/botTESTTOKEN/SendDocument")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(SEND_MESSAGE_OK)
.expect(0)
.create_async()
.await;
let bot = test_bot(&server);
let st = streaming();
let tg = TelegramState::new();
let ok = deliver_intermediate_message(
&bot,
teloxide::types::ChatId(CHAT),
None,
&st,
&tg,
uuid::Uuid::new_v4(),
dir.path(),
"Report ready: [q3.pdf](q3.pdf)\n\nSome prose so the report has a body.",
)
.await;
assert!(ok, "the rich bubble is the delivery");
rich_mock.assert_async().await;
document_mock.assert_async().await;
let s = st.lock().unwrap_or_else(|e| e.into_inner());
assert_eq!(
s.delivered_files
.iter()
.map(|d| d.path.clone())
.collect::<Vec<_>>(),
vec![pdf],
"an inlined file counts as delivered for the final leg"
);
assert_eq!(
s.delivered_files[0].message_id, 812,
"the recorded address is the rich bubble that carries the file"
);
assert_eq!(s.sent_intermediates.len(), 1);
assert_eq!(
s.media_intermediates.len(),
1,
"the bubble is recorded as media-bearing"
);
assert!(
s.intermediate_msg_ids.is_empty(),
"a media-bearing bubble is never deletable"
);
}
#[tokio::test]
async fn a_declined_rich_send_ships_the_file_as_its_own_bubble() {
let dir = tempfile::tempdir().expect("tempdir");
let pdf = write_fixture(dir.path(), "q3.pdf");
let mut server = mockito::Server::new_async().await;
let rich_mock = server
.mock("POST", "/botTESTTOKEN/sendRichMessage")
.with_status(500)
.with_body("boom")
.expect(2)
.create_async()
.await;
let document_mock = server
.mock("POST", "/botTESTTOKEN/SendDocument")
.match_body(mockito::Matcher::Regex("q3".to_string()))
.with_status(200)
.with_header("content-type", "application/json")
.with_body(SEND_MESSAGE_OK)
.expect(1)
.create_async()
.await;
let text_mock = server
.mock("POST", "/botTESTTOKEN/SendMessage")
.match_body(mockito::Matcher::Regex("q3\\.\\u{200b}pdf".to_string()))
.with_status(200)
.with_header("content-type", "application/json")
.with_body(SEND_MESSAGE_OK)
.expect(1)
.create_async()
.await;
let bot = test_bot(&server);
let st = streaming();
let tg = TelegramState::new();
let ok = deliver_intermediate_message(
&bot,
teloxide::types::ChatId(CHAT),
None,
&st,
&tg,
uuid::Uuid::new_v4(),
dir.path(),
"Report ready: [q3.pdf](q3.pdf)\n\nProse body for the bubble.",
)
.await;
rich_mock.assert_async().await;
document_mock.assert_async().await;
text_mock.assert_async().await;
assert!(ok);
let s = st.lock().unwrap_or_else(|e| e.into_inner());
assert_eq!(
s.delivered_files,
vec![crate::channels::telegram::delivery::DeliveredFile {
path: pdf,
message_id: 701,
}],
"the pair points at the document's own bubble"
);
}
#[tokio::test]
async fn a_repeated_intermediate_does_not_ship_its_file_twice() {
let dir = tempfile::tempdir().expect("tempdir");
write_fixture(dir.path(), "q3.pdf");
let mut server = mockito::Server::new_async().await;
let rich_mock = server
.mock("POST", "/botTESTTOKEN/sendRichMessage")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(RICH_OK)
.expect(1)
.create_async()
.await;
let bot = test_bot(&server);
let st = streaming();
let tg = TelegramState::new();
let text = "Report ready: [q3.pdf](q3.pdf)\n\nProse body for the bubble.";
let first = deliver_intermediate_message(
&bot,
teloxide::types::ChatId(CHAT),
None,
&st,
&tg,
uuid::Uuid::new_v4(),
dir.path(),
text,
)
.await;
let second = deliver_intermediate_message(
&bot,
teloxide::types::ChatId(CHAT),
None,
&st,
&tg,
uuid::Uuid::new_v4(),
dir.path(),
text,
)
.await;
assert!(first);
assert!(second, "the repeat reports success without re-delivering");
rich_mock.assert_async().await; }
#[cfg(unix)]
#[test]
fn the_probe_reports_an_unreadable_file_instead_of_its_bytes() {
use crate::utils::image::{LocalFile, probe_document_bytes};
use std::os::unix::fs::PermissionsExt;
let dir = tempfile::tempdir().expect("tempdir");
let good = write_fixture(dir.path(), "good.pdf");
let sealed = dir.path().join("sealed.pdf");
std::fs::write(&sealed, b"%PDF-1.7\n").expect("write sealed");
std::fs::set_permissions(&sealed, std::fs::Permissions::from_mode(0o000))
.expect("seal the file");
let files = vec![
LocalFile {
path: good.clone(),
caption: Some("good".to_string()),
marker_span: None,
},
LocalFile {
path: sealed.clone(),
caption: Some("sealed".to_string()),
marker_span: None,
},
];
let (readable, failures) = probe_document_bytes(&files);
assert_eq!(
readable.keys().collect::<Vec<_>>(),
vec![&good],
"exactly the readable file comes back with bytes"
);
assert_eq!(failures.len(), 1, "one failure, naming the sealed file");
assert_eq!(failures[0].resolved, Some(sealed.clone()));
assert_eq!(
failures[0].reason,
crate::utils::image::LocalImageFailureReason::Unreadable
);
}
}
mod fallback {
use crate::channels::telegram::delivery::{
DeliveredFile, file_message_link, link_file_markers,
};
use crate::channels::telegram::intermediates::fallback_would_duplicate;
use crate::utils::image::extract_local_files;
use std::path::Path;
const SUPERGROUP_ID: i64 = -100_123_456_789_012;
fn write_fixture(dir: &Path, name: &str) -> std::path::PathBuf {
let p = dir.join(name);
std::fs::write(&p, b"%PDF-1.7 fixture").expect("write fixture");
p
}
fn chat_of_kind(v: serde_json::Value) -> teloxide::types::Chat {
serde_json::from_value(v).expect("chat json")
}
#[test]
fn a_media_intermediate_that_carries_the_body_suppresses_the_resend() {
let body = "Here is the report, inline.";
let media = vec![(teloxide::types::MessageId(90_465), body.to_string())];
assert!(
fallback_would_duplicate(&media, body),
"the bubble already delivered this body and cannot be replaced"
);
assert!(fallback_would_duplicate(
&media,
"Here is the\nreport, inline."
));
assert!(!fallback_would_duplicate(
&media,
"An entirely different answer."
));
assert!(!fallback_would_duplicate(&[], body));
}
#[test]
fn the_fallback_links_a_marker_to_the_intermediates_bubble() {
let dir = tempfile::tempdir().expect("tempdir");
let pdf = write_fixture(dir.path(), "q3.pdf");
let body = "Report: [Q3 report](q3.pdf).";
let scan = extract_local_files(body, Some(dir.path()));
assert_eq!(scan.text, "Report: 📎 Q3 report.");
let ledger = vec![DeliveredFile {
path: pdf.clone(),
message_id: 91_047,
}];
let chat = chat_of_kind(serde_json::json!({
"id": SUPERGROUP_ID, "type": "supergroup", "title": "g"
}));
let mut links: Vec<(std::path::PathBuf, String)> = Vec::new();
for file in &ledger {
if let Some(link) = file_message_link(&chat.kind, SUPERGROUP_ID, None, file.message_id)
{
links.push((file.path.clone(), link));
}
}
assert_eq!(
link_file_markers(&scan.text, &scan, &links),
"Report: [📎 Q3 report](https://t.me/c/123456789012/91047).",
"the marker points at the bubble that actually holds the document"
);
let dm = chat_of_kind(serde_json::json!({
"id": 777_001, "type": "private", "first_name": "a"
}));
assert!(file_message_link(&dm.kind, -456_789, None, 91_047).is_none());
}
}
mod marker_consume {
use crate::utils::image::{
DOC_ID_PREFIX, LocalFileScan, extract_local_files, rewrite_local_files,
};
use std::path::{Path, PathBuf};
const PDF_BYTES: &[u8] = b"%PDF-1.7\n1 0 obj\n<<>>\nendobj\n";
fn write_fixture(dir: &Path, name: &str) -> PathBuf {
let path = dir.join(name);
std::fs::write(&path, PDF_BYTES).expect("write fixture");
path
}
fn paths(scan: &LocalFileScan) -> Vec<PathBuf> {
scan.attachments.iter().map(|f| f.path.clone()).collect()
}
#[test]
fn a_model_written_marker_prefix_is_not_doubled_on_the_text_plane() {
let dir = tempfile::tempdir().expect("tempdir");
let pdf = write_fixture(dir.path(), "q3.pdf");
let scan = extract_local_files(
&format!("before 📎 [Q3 report]({}) after", pdf.display()),
Some(dir.path()),
);
assert_eq!(
scan.text, "before 📎 Q3 report after",
"the model's own 📎 is consumed, not doubled"
);
assert_eq!(scan.text.matches('📎').count(), 1, "exactly one marker");
assert_eq!(paths(&scan), vec![pdf]);
}
#[test]
fn a_run_of_model_prefixes_collapses_to_one_marker() {
let dir = tempfile::tempdir().expect("tempdir");
let pdf = write_fixture(dir.path(), "q3.pdf");
let scan = extract_local_files(
&format!("📎 📎 [Q3 report]({})", pdf.display()),
Some(dir.path()),
);
assert_eq!(scan.text, "📎 Q3 report", "one marker from a whole run");
assert_eq!(scan.text.matches('📎').count(), 1);
assert_eq!(paths(&scan), vec![pdf]);
}
#[test]
fn a_model_written_marker_prefix_is_not_doubled_in_the_rich_plane() {
let dir = tempfile::tempdir().expect("tempdir");
let pdf = write_fixture(dir.path(), "q3.pdf");
let rw = rewrite_local_files(
&format!("before 📎 [Q3 report]({}) after", pdf.display()),
Some(dir.path()),
DOC_ID_PREFIX,
&[],
true,
);
assert_eq!(
rw.rich.matches('📎').count(),
1,
"exactly one marker: {:?}",
rw.rich
);
assert_eq!(
rw.rich, "before  after",
"the model's own 📎 is consumed, not doubled"
);
assert_eq!(rw.entries.len(), 1);
}
#[test]
fn the_detached_marker_arm_consumes_the_prefix_too() {
let dir = tempfile::tempdir().expect("tempdir");
write_fixture(dir.path(), "notes.md");
let rw = rewrite_local_files(
"before 📎 [notes](notes.md)",
Some(dir.path()),
DOC_ID_PREFIX,
&[],
false,
);
assert_eq!(
rw.rich.matches('📎').count(),
1,
"exactly one marker: {:?}",
rw.rich
);
assert_eq!(
rw.rich, "before 📎 notes",
"the marker replaces the model's prefix"
);
assert!(
rw.entries.is_empty(),
"an opt-out hit records no media entry"
);
}
}