use wiremock::matchers::{method, path_regex};
use wiremock::{Mock, MockServer, ResponseTemplate};
use zeroclaw::channels::telegram::TelegramChannel;
use zeroclaw::channels::traits::{Channel, SendMessage};
fn test_channel(mock_url: &str) -> TelegramChannel {
TelegramChannel::new("TEST_TOKEN".into(), vec!["*".into()], false)
.with_api_base(mock_url.to_string())
}
async fn mock_send_message_ok(server: &MockServer) {
Mock::given(method("POST"))
.and(path_regex(r"/botTEST_TOKEN/sendMessage$"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"ok": true,
"result": {
"message_id": 1,
"chat": {"id": 123},
"text": "ok"
}
})))
.expect(1..)
.mount(server)
.await;
}
#[tokio::test]
async fn document_url_failure_falls_back_to_text_link() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path_regex(r"/botTEST_TOKEN/sendDocument$"))
.respond_with(ResponseTemplate::new(400).set_body_json(serde_json::json!({
"ok": false,
"error_code": 400,
"description": "Bad Request: wrong type of the web page content"
})))
.expect(1)
.mount(&server)
.await;
mock_send_message_ok(&server).await;
let channel = test_channel(&server.uri());
let msg = SendMessage::new(
"Here is the report [DOCUMENT:https://example.com/page.html]",
"123",
);
let result = channel.send(&msg).await;
assert!(
result.is_ok(),
"send should succeed via text fallback, got: {result:?}"
);
}
#[tokio::test]
async fn photo_url_failure_falls_back_to_text_link() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path_regex(r"/botTEST_TOKEN/sendPhoto$"))
.respond_with(ResponseTemplate::new(400).set_body_json(serde_json::json!({
"ok": false,
"error_code": 400,
"description": "Bad Request: failed to get HTTP URL content"
})))
.expect(1)
.mount(&server)
.await;
mock_send_message_ok(&server).await;
let channel = test_channel(&server.uri());
let msg = SendMessage::new(
"Check this [IMAGE:https://internal-server.local/screenshot.png]",
"456",
);
let result = channel.send(&msg).await;
assert!(
result.is_ok(),
"send should succeed via text fallback, got: {result:?}"
);
}
#[tokio::test]
async fn text_portion_delivered_before_attachment_failure() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path_regex(r"/botTEST_TOKEN/sendDocument$"))
.respond_with(ResponseTemplate::new(400).set_body_json(serde_json::json!({
"ok": false,
"error_code": 400,
"description": "Bad Request: wrong type of the web page content"
})))
.expect(1)
.mount(&server)
.await;
Mock::given(method("POST"))
.and(path_regex(r"/botTEST_TOKEN/sendMessage$"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"ok": true,
"result": {
"message_id": 1,
"chat": {"id": 789},
"text": "ok"
}
})))
.expect(2)
.mount(&server)
.await;
let channel = test_channel(&server.uri());
let msg = SendMessage::new(
"Here is the file [DOCUMENT:https://example.com/report.html]",
"789",
);
let result = channel.send(&msg).await;
assert!(result.is_ok(), "send should succeed, got: {result:?}");
}
#[tokio::test]
async fn multiple_attachments_independent_fallback() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path_regex(r"/botTEST_TOKEN/sendDocument$"))
.respond_with(ResponseTemplate::new(400).set_body_json(serde_json::json!({
"ok": false,
"error_code": 400,
"description": "Bad Request: wrong type of the web page content"
})))
.expect(1)
.mount(&server)
.await;
Mock::given(method("POST"))
.and(path_regex(r"/botTEST_TOKEN/sendPhoto$"))
.respond_with(ResponseTemplate::new(400).set_body_json(serde_json::json!({
"ok": false,
"error_code": 400,
"description": "Bad Request: failed to get HTTP URL content"
})))
.expect(1)
.mount(&server)
.await;
Mock::given(method("POST"))
.and(path_regex(r"/botTEST_TOKEN/sendMessage$"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"ok": true,
"result": {
"message_id": 1,
"chat": {"id": 100},
"text": "ok"
}
})))
.expect(3) .mount(&server)
.await;
let channel = test_channel(&server.uri());
let msg = SendMessage::new(
"Files: [DOCUMENT:https://example.com/page.html] and [IMAGE:https://internal.local/pic.png]",
"100",
);
let result = channel.send(&msg).await;
assert!(
result.is_ok(),
"send should succeed with fallbacks for all attachments, got: {result:?}"
);
}
#[tokio::test]
async fn successful_attachment_no_fallback() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path_regex(r"/botTEST_TOKEN/sendDocument$"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"ok": true,
"result": {
"message_id": 2,
"chat": {"id": 200},
"document": {"file_id": "abc"}
}
})))
.expect(1)
.mount(&server)
.await;
Mock::given(method("POST"))
.and(path_regex(r"/botTEST_TOKEN/sendMessage$"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"ok": true,
"result": {
"message_id": 1,
"chat": {"id": 200},
"text": "ok"
}
})))
.expect(1) .mount(&server)
.await;
let channel = test_channel(&server.uri());
let msg = SendMessage::new(
"Report attached [DOCUMENT:https://example.com/report.pdf]",
"200",
);
let result = channel.send(&msg).await;
assert!(
result.is_ok(),
"send should succeed normally, got: {result:?}"
);
}
#[tokio::test]
async fn document_only_message_falls_back_to_text() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path_regex(r"/botTEST_TOKEN/sendDocument$"))
.respond_with(ResponseTemplate::new(400).set_body_json(serde_json::json!({
"ok": false,
"error_code": 400,
"description": "Bad Request: failed to get HTTP URL content"
})))
.expect(1)
.mount(&server)
.await;
Mock::given(method("POST"))
.and(path_regex(r"/botTEST_TOKEN/sendMessage$"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"ok": true,
"result": {
"message_id": 1,
"chat": {"id": 300},
"text": "ok"
}
})))
.expect(1)
.mount(&server)
.await;
let channel = test_channel(&server.uri());
let msg = SendMessage::new("[DOCUMENT:https://example.com/file.html]", "300");
let result = channel.send(&msg).await;
assert!(
result.is_ok(),
"document-only message should fall back to text, got: {result:?}"
);
}