use serde_json::json;
use wi_api::{
error::WiError,
types::{
MarkReadParams, PresenceParams, SendAudioParams, SendContactParams, SendDocumentParams,
SendImageParams, SendLocationParams, SendPollParams, SendReactionParams, SendStickerParams,
SendTextParams, SendVideoParams,
},
Wi,
};
use wiremock::{
matchers::{body_json, header, method, path},
Mock, MockServer, ResponseTemplate,
};
async fn test_client(server: &MockServer) -> Wi {
Wi::builder("test-api-key").base_url(server.uri()).build()
}
fn msg_response() -> serde_json::Value {
json!({"messageId": "msg_1", "id": "msg_1"})
}
#[tokio::test]
async fn send_text_correct_request() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/sessions/sess-1/messages/send-text"))
.and(header("x-api-key", "test-api-key"))
.and(body_json(json!({"to": "5511999999999", "text": "Hello!"})))
.respond_with(ResponseTemplate::new(200).set_body_json(msg_response()))
.expect(1)
.mount(&server)
.await;
let wi = test_client(&server).await;
let msg = wi
.session("sess-1")
.send_text(SendTextParams {
to: "5511999999999".into(),
text: "Hello!".into(),
..Default::default()
})
.await
.unwrap();
assert_eq!(msg.message_id, "msg_1");
assert_eq!(msg.id, "msg_1");
server.verify().await;
}
#[tokio::test]
async fn send_text_with_quoted() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/sessions/s/messages/send-text"))
.and(body_json(json!({
"to": "55",
"text": "reply",
"quoted": "quoted-msg-id"
})))
.respond_with(ResponseTemplate::new(200).set_body_json(msg_response()))
.expect(1)
.mount(&server)
.await;
let wi = test_client(&server).await;
wi.session("s")
.send_text(SendTextParams {
to: "55".into(),
text: "reply".into(),
quoted: Some("quoted-msg-id".into()),
..Default::default()
})
.await
.unwrap();
server.verify().await;
}
#[tokio::test]
async fn send_text_401_returns_wi_error() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/sessions/s/messages/send-text"))
.respond_with(ResponseTemplate::new(401).set_body_json(json!({
"message": "Unauthorized"
})))
.mount(&server)
.await;
let wi = test_client(&server).await;
let err = wi
.session("s")
.send_text(SendTextParams {
to: "55".into(),
text: "hi".into(),
..Default::default()
})
.await
.unwrap_err();
match err {
WiError::Api {
status, message, ..
} => {
assert_eq!(status, 401);
assert_eq!(message, "Unauthorized");
}
other => panic!("unexpected error: {:?}", other),
}
}
#[tokio::test]
async fn send_text_404_returns_wi_error_with_code() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/sessions/s/messages/send-text"))
.respond_with(ResponseTemplate::new(404).set_body_json(json!({
"message": "Session not found",
"code": "SESSION_NOT_FOUND"
})))
.mount(&server)
.await;
let wi = test_client(&server).await;
let err = wi
.session("s")
.send_text(SendTextParams {
to: "55".into(),
text: "hi".into(),
..Default::default()
})
.await
.unwrap_err();
match err {
WiError::Api {
status,
message,
code,
} => {
assert_eq!(status, 404);
assert_eq!(message, "Session not found");
assert_eq!(code.as_deref(), Some("SESSION_NOT_FOUND"));
}
other => panic!("unexpected error: {:?}", other),
}
}
#[tokio::test]
async fn send_image_includes_caption() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/sessions/s/messages/send-image"))
.and(body_json(json!({
"to": "55",
"url": "https://img.com/a.jpg",
"caption": "look"
})))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"messageId": "m2", "id": "m2"
})))
.expect(1)
.mount(&server)
.await;
let wi = test_client(&server).await;
let msg = wi
.session("s")
.send_image(SendImageParams {
to: "55".into(),
url: "https://img.com/a.jpg".into(),
caption: Some("look".into()),
..Default::default()
})
.await
.unwrap();
assert_eq!(msg.message_id, "m2");
server.verify().await;
}
#[tokio::test]
async fn send_audio_ptt_flag() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/sessions/s/messages/send-audio"))
.and(body_json(json!({
"to": "55",
"url": "https://cdn/a.ogg",
"ptt": true
})))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"messageId": "m3", "id": "m3"
})))
.expect(1)
.mount(&server)
.await;
let wi = test_client(&server).await;
wi.session("s")
.send_audio(SendAudioParams {
to: "55".into(),
url: "https://cdn/a.ogg".into(),
ptt: Some(true),
})
.await
.unwrap();
server.verify().await;
}
#[tokio::test]
async fn send_audio_without_ptt_omits_field() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/sessions/s/messages/send-audio"))
.and(body_json(json!({"to": "55", "url": "https://cdn/b.ogg"})))
.respond_with(ResponseTemplate::new(200).set_body_json(msg_response()))
.expect(1)
.mount(&server)
.await;
let wi = test_client(&server).await;
wi.session("s")
.send_audio(SendAudioParams {
to: "55".into(),
url: "https://cdn/b.ogg".into(),
ptt: None,
})
.await
.unwrap();
server.verify().await;
}
#[tokio::test]
async fn send_video_correct_path() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/sessions/s/messages/send-video"))
.and(body_json(json!({"to": "55", "url": "https://cdn/v.mp4"})))
.respond_with(ResponseTemplate::new(200).set_body_json(msg_response()))
.expect(1)
.mount(&server)
.await;
let wi = test_client(&server).await;
wi.session("s")
.send_video(SendVideoParams {
to: "55".into(),
url: "https://cdn/v.mp4".into(),
..Default::default()
})
.await
.unwrap();
server.verify().await;
}
#[tokio::test]
async fn send_document_with_filename() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/sessions/s/messages/send-document"))
.and(body_json(json!({
"to": "55",
"url": "https://cdn/doc.pdf",
"filename": "report.pdf"
})))
.respond_with(ResponseTemplate::new(200).set_body_json(msg_response()))
.expect(1)
.mount(&server)
.await;
let wi = test_client(&server).await;
wi.session("s")
.send_document(SendDocumentParams {
to: "55".into(),
url: "https://cdn/doc.pdf".into(),
filename: Some("report.pdf".into()),
..Default::default()
})
.await
.unwrap();
server.verify().await;
}
#[tokio::test]
async fn send_location_lat_lng() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/sessions/s/messages/send-location"))
.and(body_json(json!({
"to": "55",
"latitude": -23.5505,
"longitude": -46.6333
})))
.respond_with(ResponseTemplate::new(200).set_body_json(msg_response()))
.expect(1)
.mount(&server)
.await;
let wi = test_client(&server).await;
wi.session("s")
.send_location(SendLocationParams {
to: "55".into(),
latitude: -23.5505,
longitude: -46.6333,
..Default::default()
})
.await
.unwrap();
server.verify().await;
}
#[tokio::test]
async fn send_contact_fields() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/sessions/s/messages/send-contact"))
.and(body_json(json!({
"to": "55",
"contact_name": "Alice",
"contact_number": "5511888888888"
})))
.respond_with(ResponseTemplate::new(200).set_body_json(msg_response()))
.expect(1)
.mount(&server)
.await;
let wi = test_client(&server).await;
wi.session("s")
.send_contact(SendContactParams {
to: "55".into(),
contact_name: "Alice".into(),
contact_number: "5511888888888".into(),
})
.await
.unwrap();
server.verify().await;
}
#[tokio::test]
async fn send_sticker_correct_path() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/sessions/s/messages/send-sticker"))
.and(body_json(json!({"to": "55", "url": "https://cdn/s.webp"})))
.respond_with(ResponseTemplate::new(200).set_body_json(msg_response()))
.expect(1)
.mount(&server)
.await;
let wi = test_client(&server).await;
wi.session("s")
.send_sticker(SendStickerParams {
to: "55".into(),
url: "https://cdn/s.webp".into(),
})
.await
.unwrap();
server.verify().await;
}
#[tokio::test]
async fn send_poll_correct_shape() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/sessions/s/messages/send-poll"))
.and(body_json(json!({
"to": "55",
"question": "Best?",
"options": ["A", "B", "C"]
})))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"messageId": "m6", "id": "m6"
})))
.expect(1)
.mount(&server)
.await;
let wi = test_client(&server).await;
let msg = wi
.session("s")
.send_poll(SendPollParams {
to: "55".into(),
question: "Best?".into(),
options: vec!["A".into(), "B".into(), "C".into()],
..Default::default()
})
.await
.unwrap();
assert_eq!(msg.message_id, "m6");
server.verify().await;
}
#[tokio::test]
async fn react_correct_path_and_body() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/sessions/s/chat/react"))
.and(header("x-api-key", "test-api-key"))
.and(body_json(json!({
"to": "55",
"message_id": "msg-xyz",
"emoji": "👍"
})))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
.expect(1)
.mount(&server)
.await;
let wi = test_client(&server).await;
wi.session("s")
.react(SendReactionParams {
to: "55".into(),
message_id: "msg-xyz".into(),
emoji: "👍".into(),
})
.await
.unwrap();
server.verify().await;
}
#[tokio::test]
async fn mark_read_correct_path_and_body() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/sessions/s/chat/markread"))
.and(header("x-api-key", "test-api-key"))
.and(body_json(json!({"chat_id": "chat-abc"})))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
.expect(1)
.mount(&server)
.await;
let wi = test_client(&server).await;
wi.session("s")
.mark_read(MarkReadParams {
chat_id: "chat-abc".into(),
last_message_id: None,
})
.await
.unwrap();
server.verify().await;
}
#[tokio::test]
async fn presence_correct_path_and_body() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/sessions/s/chat/presence"))
.and(header("x-api-key", "test-api-key"))
.and(body_json(json!({
"chat_id": "chat-abc",
"presence": "composing"
})))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
.expect(1)
.mount(&server)
.await;
let wi = test_client(&server).await;
wi.session("s")
.presence(PresenceParams {
chat_id: "chat-abc".into(),
presence: "composing".into(),
})
.await
.unwrap();
server.verify().await;
}
#[tokio::test]
async fn status_get_request() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/sessions/abc/status"))
.and(header("x-api-key", "test-api-key"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"connected": true,
"phone": "5511999"
})))
.mount(&server)
.await;
let wi = test_client(&server).await;
let status = wi.session("abc").status().await.unwrap();
assert!(status.connected);
assert_eq!(status.phone.as_deref(), Some("5511999"));
}
#[tokio::test]
async fn status_disconnected() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/sessions/s/status"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"connected": false,
"phone": null
})))
.mount(&server)
.await;
let wi = test_client(&server).await;
let status = wi.session("s").status().await.unwrap();
assert!(!status.connected);
assert!(status.phone.is_none());
}
#[tokio::test]
async fn qr_get_request() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/sessions/s/qr"))
.and(header("x-api-key", "test-api-key"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"qr": "data:image/png;base64,abc123"
})))
.mount(&server)
.await;
let wi = test_client(&server).await;
let qr = wi.session("s").qr().await.unwrap();
assert_eq!(qr.qr.as_deref(), Some("data:image/png;base64,abc123"));
}
#[tokio::test]
async fn connect_post_empty_body() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/sessions/s/connect"))
.and(header("x-api-key", "test-api-key"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
.expect(1)
.mount(&server)
.await;
let wi = test_client(&server).await;
wi.session("s").connect().await.unwrap();
server.verify().await;
}
#[tokio::test]
async fn disconnect_post_empty_body() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/sessions/s/disconnect"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
.expect(1)
.mount(&server)
.await;
let wi = test_client(&server).await;
wi.session("s").disconnect().await.unwrap();
server.verify().await;
}
#[tokio::test]
async fn logout_post_empty_body() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/sessions/s/logout"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
.expect(1)
.mount(&server)
.await;
let wi = test_client(&server).await;
wi.session("s").logout().await.unwrap();
server.verify().await;
}
#[tokio::test]
async fn pair_phone_sends_phone_in_body() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/sessions/s/pairphone"))
.and(body_json(json!({"phone": "5511999999999"})))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"pairCode": "ABCD1234"
})))
.expect(1)
.mount(&server)
.await;
let wi = test_client(&server).await;
let result = wi.session("s").pair_phone("5511999999999").await.unwrap();
server.verify().await;
assert!(result.pair_code.is_none());
}
#[tokio::test]
async fn session_url_pattern() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/sessions/my-session-id/status"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"connected": false, "phone": null
})))
.expect(1)
.mount(&server)
.await;
let wi = Wi::builder("k").base_url(server.uri()).build();
wi.session("my-session-id").status().await.unwrap();
server.verify().await;
}