mod common;
use emailit::types::{
Attachment, CreateEmailBaseOptions, ListEmailsParams, Tracking, UpdateEmailParams,
};
use serde_json::json;
use wiremock::matchers::{bearer_token, body_json, header, method, path, query_param};
use wiremock::{Mock, ResponseTemplate};
#[tokio::test]
async fn test_send_email() {
let (client, server) = common::setup().await;
Mock::given(method("POST"))
.and(path("/v2/emails"))
.and(bearer_token("test_api_key"))
.and(header("Content-Type", "application/json"))
.and(body_json(json!({
"from": "sender@example.com",
"to": ["user@example.com"],
"subject": "Hello",
"html": "<h1>Hi</h1>"
})))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"id": "em_123",
"object": "email",
"status": "pending",
"from": "sender@example.com",
"subject": "Hello"
})))
.mount(&server)
.await;
let email = CreateEmailBaseOptions::new("sender@example.com", ["user@example.com"], "Hello")
.with_html("<h1>Hi</h1>");
let result = client.emails.send(email).await.unwrap();
assert_eq!(result.id.unwrap(), "em_123");
assert_eq!(result.status.unwrap(), "pending");
}
#[tokio::test]
async fn test_send_email_with_template() {
let (client, server) = common::setup().await;
Mock::given(method("POST"))
.and(path("/v2/emails"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"id": "em_456",
"object": "email",
"status": "pending"
})))
.mount(&server)
.await;
let email = CreateEmailBaseOptions::new("sender@example.com", ["user@example.com"], "")
.with_template("welcome_email");
let result = client.emails.send(email).await.unwrap();
assert_eq!(result.id.unwrap(), "em_456");
}
#[tokio::test]
async fn test_send_email_with_attachments() {
let (client, server) = common::setup().await;
Mock::given(method("POST"))
.and(path("/v2/emails"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"id": "em_789",
"object": "email",
"status": "pending"
})))
.mount(&server)
.await;
let email = CreateEmailBaseOptions::new("sender@example.com", ["user@example.com"], "Invoice")
.with_html("<p>Attached</p>")
.with_attachments(vec![
Attachment::new("invoice.pdf", "base64content").with_content_type("application/pdf"),
]);
let result = client.emails.send(email).await.unwrap();
assert_eq!(result.id.unwrap(), "em_789");
}
#[tokio::test]
async fn test_send_email_with_tracking() {
let (client, server) = common::setup().await;
Mock::given(method("POST"))
.and(path("/v2/emails"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"id": "em_track",
"object": "email",
"status": "pending"
})))
.mount(&server)
.await;
let email = CreateEmailBaseOptions::new("sender@example.com", ["user@example.com"], "Test")
.with_html("<p>Tracked</p>")
.with_tracking(Tracking::new().with_loads(true).with_clicks(false));
let result = client.emails.send(email).await.unwrap();
assert_eq!(result.id.unwrap(), "em_track");
}
#[tokio::test]
async fn test_send_email_scheduled() {
let (client, server) = common::setup().await;
Mock::given(method("POST"))
.and(path("/v2/emails"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"id": "em_sched",
"object": "email",
"status": "scheduled",
"scheduled_at": "2026-01-10T09:00:00Z"
})))
.mount(&server)
.await;
let email = CreateEmailBaseOptions::new("sender@example.com", ["user@example.com"], "Reminder")
.with_html("<p>Tomorrow</p>")
.with_scheduled_at("2026-01-10T09:00:00Z");
let result = client.emails.send(email).await.unwrap();
assert_eq!(result.status.unwrap(), "scheduled");
}
#[tokio::test]
async fn test_list_emails() {
let (client, server) = common::setup().await;
Mock::given(method("GET"))
.and(path("/v2/emails"))
.and(query_param("page", "1"))
.and(query_param("limit", "10"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"data": [
{"id": "em_1", "object": "email", "status": "delivered"},
{"id": "em_2", "object": "email", "status": "pending"}
],
"page": 1,
"per_page": 10,
"total": 2,
"total_pages": 1
})))
.mount(&server)
.await;
let result = client
.emails
.list(Some(ListEmailsParams {
page: Some(1),
limit: Some(10),
}))
.await
.unwrap();
assert_eq!(result.data.len(), 2);
assert_eq!(result.data[0].id.as_deref(), Some("em_1"));
assert_eq!(result.total, Some(2));
}
#[tokio::test]
async fn test_get_email() {
let (client, server) = common::setup().await;
Mock::given(method("GET"))
.and(path("/v2/emails/em_123"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"id": "em_123",
"object": "email",
"status": "delivered"
})))
.mount(&server)
.await;
let result = client.emails.get("em_123").await.unwrap();
assert_eq!(result.id.unwrap(), "em_123");
}
#[tokio::test]
async fn test_get_email_raw() {
let (client, server) = common::setup().await;
Mock::given(method("GET"))
.and(path("/v2/emails/em_123/raw"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"id": "em_123",
"raw": "MIME-Version: 1.0\r\n..."
})))
.mount(&server)
.await;
let result = client.emails.get_raw("em_123").await.unwrap();
assert_eq!(result.id.unwrap(), "em_123");
}
#[tokio::test]
async fn test_get_email_attachments() {
let (client, server) = common::setup().await;
Mock::given(method("GET"))
.and(path("/v2/emails/em_123/attachments"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"data": [
{"filename": "file.pdf", "content_type": "application/pdf"}
]
})))
.mount(&server)
.await;
let result = client.emails.get_attachments("em_123").await.unwrap();
assert_eq!(result.data.len(), 1);
}
#[tokio::test]
async fn test_get_email_body() {
let (client, server) = common::setup().await;
Mock::given(method("GET"))
.and(path("/v2/emails/em_123/body"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"html": "<h1>Hello</h1>",
"text": "Hello"
})))
.mount(&server)
.await;
let result = client.emails.get_body("em_123").await.unwrap();
assert_eq!(result.html.unwrap(), "<h1>Hello</h1>");
assert_eq!(result.text.unwrap(), "Hello");
}
#[tokio::test]
async fn test_get_email_meta() {
let (client, server) = common::setup().await;
Mock::given(method("GET"))
.and(path("/v2/emails/em_123/meta"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"id": "em_123",
"object": "email",
"subject": "Test"
})))
.mount(&server)
.await;
let result = client.emails.get_meta("em_123").await.unwrap();
assert_eq!(result.id.unwrap(), "em_123");
}
#[tokio::test]
async fn test_update_email() {
let (client, server) = common::setup().await;
Mock::given(method("POST"))
.and(path("/v2/emails/em_123"))
.and(body_json(json!({"scheduled_at": "2026-02-01T10:00:00Z"})))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"id": "em_123",
"scheduled_at": "2026-02-01T10:00:00Z"
})))
.mount(&server)
.await;
let result = client
.emails
.update(
"em_123",
UpdateEmailParams {
scheduled_at: "2026-02-01T10:00:00Z".into(),
},
)
.await
.unwrap();
assert_eq!(result.id.unwrap(), "em_123");
}
#[tokio::test]
async fn test_cancel_email() {
let (client, server) = common::setup().await;
Mock::given(method("POST"))
.and(path("/v2/emails/em_123/cancel"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"id": "em_123",
"status": "cancelled"
})))
.mount(&server)
.await;
let result = client.emails.cancel("em_123").await.unwrap();
assert_eq!(result.status.unwrap(), "cancelled");
}
#[tokio::test]
async fn test_retry_email() {
let (client, server) = common::setup().await;
Mock::given(method("POST"))
.and(path("/v2/emails/em_123/retry"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"id": "em_123",
"status": "pending"
})))
.mount(&server)
.await;
let result = client.emails.retry("em_123").await.unwrap();
assert_eq!(result.status.unwrap(), "pending");
}
#[tokio::test]
async fn test_send_email_401() {
let (client, server) = common::setup().await;
Mock::given(method("POST"))
.and(path("/v2/emails"))
.respond_with(ResponseTemplate::new(401).set_body_json(json!({
"error": "Unauthorized",
"message": "Invalid API key"
})))
.mount(&server)
.await;
let email = CreateEmailBaseOptions::new("from@test.com", ["to@test.com"], "Test")
.with_html("<p>test</p>");
let err = client.emails.send(email).await.unwrap_err();
assert!(err.is_authentication());
assert_eq!(err.status(), Some(401));
assert!(err.to_string().contains("Unauthorized"));
}
#[tokio::test]
async fn test_send_email_422() {
let (client, server) = common::setup().await;
Mock::given(method("POST"))
.and(path("/v2/emails"))
.respond_with(ResponseTemplate::new(422).set_body_json(json!({
"error": {"message": "Validation failed"}
})))
.mount(&server)
.await;
let email = CreateEmailBaseOptions::new("from@test.com", ["to@test.com"], "Test")
.with_html("<p>test</p>");
let err = client.emails.send(email).await.unwrap_err();
assert!(err.is_unprocessable_entity());
}
#[tokio::test]
async fn test_send_email_429() {
let (client, server) = common::setup().await;
Mock::given(method("POST"))
.and(path("/v2/emails"))
.respond_with(ResponseTemplate::new(429).set_body_json(json!({
"error": "Rate limit exceeded"
})))
.mount(&server)
.await;
let email = CreateEmailBaseOptions::new("from@test.com", ["to@test.com"], "Test")
.with_html("<p>test</p>");
let err = client.emails.send(email).await.unwrap_err();
assert!(err.is_rate_limit());
}
#[tokio::test]
async fn test_url_encoding() {
let (client, server) = common::setup().await;
Mock::given(method("GET"))
.and(path("/v2/emails/email%2F123"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"id": "email/123"
})))
.mount(&server)
.await;
let result = client.emails.get("email/123").await.unwrap();
assert_eq!(result.id.unwrap(), "email/123");
}
#[tokio::test]
async fn test_collection_iteration() {
let (client, server) = common::setup().await;
Mock::given(method("GET"))
.and(path("/v2/emails"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"data": [
{"id": "em_1", "object": "email"},
{"id": "em_2", "object": "email"},
{"id": "em_3", "object": "email"}
],
"page": 1,
"per_page": 10,
"total": 3,
"total_pages": 1,
"next_page_url": null
})))
.mount(&server)
.await;
let result = client.emails.list(None).await.unwrap();
assert_eq!(result.len(), 3);
assert!(!result.is_empty());
assert!(!result.has_more());
let ids: Vec<String> = result.iter().filter_map(|e| e.id.clone()).collect();
assert_eq!(ids, vec!["em_1", "em_2", "em_3"]);
}
#[tokio::test]
async fn test_collection_has_more() {
let (client, server) = common::setup().await;
Mock::given(method("GET"))
.and(path("/v2/emails"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"data": [{"id": "em_1"}],
"page": 1,
"per_page": 1,
"total": 5,
"total_pages": 5,
"next_page_url": "https://api.emailit.com/v2/emails?page=2"
})))
.mount(&server)
.await;
let result = client.emails.list(None).await.unwrap();
assert!(result.has_more());
}