use crate::client::HttpClient;
use crate::error::Result;
use crate::types::{
CreateListParams, List, ListListsParams, ListStats, ListsResponse, Pagination, UpdateListParams,
};
#[derive(Debug, Clone)]
pub struct Lists {
client: HttpClient,
}
impl Lists {
pub fn new(client: HttpClient) -> Self {
Self { client }
}
pub async fn create(&self, params: &CreateListParams) -> Result<List> {
self.client.post("/contact-lists", params).await
}
pub async fn get(&self, id: &str) -> Result<List> {
self.client.get(&format!("/contact-lists/{}", id)).await
}
pub async fn update(&self, id: &str, params: &UpdateListParams) -> Result<List> {
self.client
.put(&format!("/contact-lists/{}", id), params)
.await
}
pub async fn delete(&self, id: &str) -> Result<()> {
self.client.delete(&format!("/contact-lists/{}", id)).await
}
pub async fn list(&self, params: &ListListsParams) -> Result<ListsResponse> {
let lists: Vec<List> = self
.client
.get_with_params("/contact-lists", params)
.await?;
Ok(ListsResponse {
lists,
pagination: Pagination {
page: params.page.unwrap_or(1),
limit: params.limit.unwrap_or(50),
total: 0, total_pages: 0,
has_next: false,
has_prev: false,
},
})
}
pub async fn stats(&self, id: &str) -> Result<ListStats> {
self.client
.get(&format!("/contact-lists/{}/stats", id))
.await
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::client::ClientConfig;
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
async fn setup() -> (MockServer, Lists) {
let mock_server = MockServer::start().await;
let config = ClientConfig::new("test_key").base_url(mock_server.uri());
let client = HttpClient::new(config).unwrap();
let lists = Lists::new(client);
(mock_server, lists)
}
#[tokio::test]
async fn test_create_list() {
let (mock_server, lists) = setup().await;
Mock::given(method("POST"))
.and(path("/api/v1/contact-lists"))
.respond_with(ResponseTemplate::new(201).set_body_json(serde_json::json!({
"success": true,
"data": {
"id": "list_123",
"name": "Newsletter",
"description": "Weekly newsletter",
"totalContacts": 0,
"activeContacts": 0,
"suppressedContacts": 0,
"tags": [],
"createdAt": "2024-01-01T00:00:00Z"
}
})))
.mount(&mock_server)
.await;
let params = CreateListParams {
name: "Newsletter".to_string(),
description: Some("Weekly newsletter".to_string()),
};
let list = lists.create(¶ms).await.unwrap();
assert_eq!(list.id, "list_123");
assert_eq!(list.name, "Newsletter");
}
#[tokio::test]
async fn test_get_list() {
let (mock_server, lists) = setup().await;
Mock::given(method("GET"))
.and(path("/api/v1/contact-lists/list_123"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"success": true,
"data": {
"id": "list_123",
"name": "Newsletter",
"totalContacts": 100,
"activeContacts": 95,
"suppressedContacts": 5,
"tags": [],
"createdAt": "2024-01-01T00:00:00Z"
}
})))
.mount(&mock_server)
.await;
let list = lists.get("list_123").await.unwrap();
assert_eq!(list.total_contacts, 100);
}
#[tokio::test]
async fn test_update_list() {
let (mock_server, lists) = setup().await;
Mock::given(method("PUT"))
.and(path("/api/v1/contact-lists/list_123"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"success": true,
"data": {
"id": "list_123",
"name": "Updated Newsletter",
"totalContacts": 100,
"activeContacts": 95,
"suppressedContacts": 5,
"tags": [],
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-02T00:00:00Z"
}
})))
.mount(&mock_server)
.await;
let params = UpdateListParams {
name: Some("Updated Newsletter".to_string()),
..Default::default()
};
let list = lists.update("list_123", ¶ms).await.unwrap();
assert_eq!(list.name, "Updated Newsletter");
}
#[tokio::test]
async fn test_delete_list() {
let (mock_server, lists) = setup().await;
Mock::given(method("DELETE"))
.and(path("/api/v1/contact-lists/list_123"))
.respond_with(ResponseTemplate::new(204))
.mount(&mock_server)
.await;
lists.delete("list_123").await.unwrap();
}
#[tokio::test]
async fn test_list_lists() {
let (mock_server, lists) = setup().await;
Mock::given(method("GET"))
.and(path("/api/v1/contact-lists"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"success": true,
"data": [
{"id": "list_1", "name": "List A", "totalContacts": 50, "activeContacts": 48, "suppressedContacts": 2, "tags": [], "createdAt": "2024-01-01T00:00:00Z"},
{"id": "list_2", "name": "List B", "totalContacts": 100, "activeContacts": 95, "suppressedContacts": 5, "tags": [], "createdAt": "2024-01-01T00:00:00Z"}
]
})))
.mount(&mock_server)
.await;
let result = lists.list(&ListListsParams::default()).await.unwrap();
assert_eq!(result.lists.len(), 2);
}
#[tokio::test]
async fn test_get_list_stats() {
let (mock_server, lists) = setup().await;
Mock::given(method("GET"))
.and(path("/api/v1/contact-lists/list_123/stats"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"success": true,
"data": {
"totalContacts": 1000,
"activeContacts": 900,
"suppressedContacts": 100
}
})))
.mount(&mock_server)
.await;
let stats = lists.stats("list_123").await.unwrap();
assert_eq!(stats.total_contacts, 1000);
assert_eq!(stats.active_contacts, 900);
}
}