pub mod client;
pub mod errors;
pub mod types;
#[cfg(test)]
mod tests {
use std::env;
use dotenv::dotenv;
use crate::{client::*, errors::*, types::*};
fn get_client_from_env() -> SignalWireClient {
dotenv().ok();
let space_name = env::var("SIGNALWIRE_SPACE_NAME").expect("Missing space name");
let project_id = env::var("SIGNALWIRE_PROJECT_ID").expect("Missing project ID");
let api_key = env::var("SIGNALWIRE_API_KEY").expect("Missing API key");
SignalWireClient::new(&space_name, &project_id, &api_key)
}
#[tokio::test]
async fn test_get_jwt() {
let client = get_client_from_env();
let result = client.get_jwt().await;
match result {
Ok(jwt_response) => {
assert!(!jwt_response.jwt_token.is_empty(), "JWT token should not be empty");
assert!(!jwt_response.refresh_token.is_empty(), "Refresh token should not be empty");
}
Err(e) => {
eprintln!("Observed error with test credentials: {:?}", e);
assert!(false, "Test should fail with invalid credentials");
}
}
}
#[tokio::test]
async fn test_get_phone_numbers_available() {
let client = get_client_from_env();
let query_params = PhoneNumberAvailableQueryParams::new().build();
match client.get_phone_numbers_available("US", &query_params).await {
Ok(response) => {
assert!(!response.phone_numbers_available.is_empty(), "Expected non-empty phone numbers list");
}
Err(e) => {
eprintln!("Error: {:?}", e);
assert_eq!(e.to_string(), "Expected error message");
}
}
}
#[tokio::test]
async fn test_get_phone_numbers_owned() {
let client = get_client_from_env();
let query_params = PhoneNumberOwnedFilterParams::new().build();
let result = client.get_phone_numbers_owned(&query_params).await;
match result {
Ok(phone_numbers) => {
assert!(!phone_numbers.data.is_empty(), "Expected non-empty phone numbers list");
}
Err(SignalWireError::Unauthorized) => {
println!("Error: Unauthorized - Test passed as expected.");
}
Err(e) => {
panic!("Unexpected error: {:?}", e);
}
}
}
#[tokio::test]
async fn test_send_sms() {
dotenv().ok();
if env::var("SIGNALWIRE_RUN_SMS_TEST").unwrap_or_else(|_| "false".to_string()) != "true" {
println!("Skipping SMS test. To enable, set SIGNALWIRE_RUN_SMS_TEST=true in your .env file.");
println!("Make sure to also set SIGNALWIRE_FROM_NUMBER and SIGNALWIRE_TO_NUMBER.");
println!("Set SIGNALWIRE_STORE_SMS_SID=true (optional) to save SID for follow-up tests.");
return;
}
let client = get_client_from_env();
let from_number = env::var("SIGNALWIRE_FROM_NUMBER").expect("Missing SIGNALWIRE_FROM_NUMBER env var");
let to_number = env::var("SIGNALWIRE_TO_NUMBER").expect("Missing SIGNALWIRE_TO_NUMBER env var");
println!("Sending SMS from {} to {}", from_number, to_number);
let message = SmsMessage {
from: from_number,
to: to_number,
body: "This is a test message from the SignalWire Rust SDK.".to_string(),
};
let sid = match client.send_sms(&message).await {
Ok(response) => {
assert_eq!(response.from, message.from);
assert_eq!(response.to, message.to);
assert_eq!(response.body, message.body);
assert!(!response.sid.is_empty(), "Expected non-empty SID");
println!("✓ SMS sent successfully with SID: {}", response.sid);
let status = response.get_status();
println!("Initial message status: {}", status);
response.sid
}
Err(SignalWireError::Unauthorized) => {
println!("Error: Unauthorized - Check your credentials");
return;
}
Err(e) => {
panic!("Unexpected error: {:?}", e);
}
};
use std::{fs::File, io::Write};
match File::create(".signalwire_test_sms_sid") {
Ok(mut file) => {
if let Err(e) = writeln!(file, "{}", sid) {
println!("Failed to write SMS SID to file: {}", e);
} else {
println!("Stored SMS SID for follow-up test: {}", sid);
}
}
Err(e) => println!("Failed to create SMS SID file: {}", e),
}
println!("Waiting 5 seconds before checking message status...");
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
match client.get_message_status(&sid).await {
Ok(status_response) => {
let current_status = status_response.get_status();
println!("✓ Initial status check: {}", current_status);
println!(
"Message details: SID={}, From={}, To={}, Body=\"{}\"",
status_response.sid, status_response.from, status_response.to, status_response.body
);
println!("Run the follow-up delayed status check with: cargo test test_delayed_status_check -- --nocapture");
}
Err(e) => {
println!("✗ Failed to check message status: {:?}", e);
}
}
}
#[tokio::test]
async fn test_delayed_status_check() {
dotenv().ok();
let message_sid = match env::var("SIGNALWIRE_MESSAGE_SID") {
Ok(sid) => sid,
Err(_) => {
match std::fs::read_to_string(".signalwire_test_sms_sid") {
Ok(sid) => sid.trim().to_string(),
Err(_) => {
println!("No message SID found. Either:");
println!("1. Set SIGNALWIRE_MESSAGE_SID in your .env file");
println!("2. Run test_send_sms with SIGNALWIRE_STORE_SMS_SID=true first");
return;
}
}
}
};
println!("Checking status of message with SID: {}", message_sid);
println!("Waiting 10 seconds for potential status changes...");
tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
let client = get_client_from_env();
match client.get_message_status(&message_sid).await {
Ok(response) => {
let status = response.get_status();
println!("✓ Message status after delay: {}", status);
println!("Details:");
println!(" SID: {}", response.sid);
println!(" From: {}", response.from);
println!(" To: {}", response.to);
println!(" Body: {}", response.body);
println!(" Date sent: {:?}", response.date_sent);
println!(" Price: {:?}", response.price);
println!(" Error code: {:?}", response.error_code);
assert_eq!(response.sid, message_sid);
assert!(!response.status.is_empty(), "Status should not be empty");
}
Err(SignalWireError::NotFound(_)) => {
println!("Message not found: {}", message_sid);
}
Err(e) => {
panic!("Unexpected error checking message status: {:?}", e);
}
}
}
#[tokio::test]
async fn test_list_subprojects() {
let client = get_client_from_env();
let query_params = SubprojectQueryParams::new().build();
match client.list_subprojects(&query_params).await {
Ok(response) => {
assert!(!response.accounts.is_empty(), "Expected non-empty accounts list");
println!("Found {} subproject(s)", response.accounts.len());
for (i, account) in response.accounts.iter().enumerate() {
println!("Subproject #{}: SID={}, Name={}", i + 1, account.sid, account.friendly_name);
}
}
Err(SignalWireError::Unauthorized) => {
println!("Error: Unauthorized - Check your credentials");
}
Err(e) => {
eprintln!("Error: {:?}", e);
panic!("Unexpected error listing subprojects");
}
}
}
#[tokio::test]
async fn test_create_and_delete_subproject() {
dotenv().ok();
if env::var("SIGNALWIRE_RUN_SUBPROJECT_TEST").unwrap_or_else(|_| "false".to_string()) != "true" {
println!("Skipping subproject creation/deletion test. To enable, set SIGNALWIRE_RUN_SUBPROJECT_TEST=true in your .env file.");
return;
}
let client = get_client_from_env();
let friendly_name = format!("Test Subproject {}", chrono::Utc::now().timestamp());
println!("Creating subproject with name: {}", friendly_name);
let subproject = match client.create_subproject(&friendly_name).await {
Ok(response) => {
println!("✓ Subproject created: SID={}, Name={}", response.sid, response.friendly_name);
assert_eq!(response.friendly_name, friendly_name, "Friendly name mismatch");
response
}
Err(SignalWireError::Unauthorized) => {
println!("Error: Unauthorized - Check your credentials");
return;
}
Err(e) => {
panic!("Error creating subproject: {:?}", e);
}
};
match client.get_subproject(&subproject.sid).await {
Ok(response) => {
println!("✓ Subproject retrieved: SID={}, Name={}", response.sid, response.friendly_name);
assert_eq!(response.sid, subproject.sid, "SID mismatch");
assert_eq!(response.friendly_name, friendly_name, "Friendly name mismatch");
}
Err(e) => {
panic!("Error retrieving subproject: {:?}", e);
}
};
let updated_name = format!("{} - Updated", friendly_name);
match client.update_subproject(&subproject.sid, &updated_name, None).await {
Ok(response) => {
println!("✓ Subproject updated: SID={}, Name={}", response.sid, response.friendly_name);
assert_eq!(response.sid, subproject.sid, "SID mismatch");
assert_eq!(response.friendly_name, updated_name, "Updated friendly name mismatch");
}
Err(e) => {
panic!("Error updating subproject: {:?}", e);
}
};
println!("Deleting subproject: SID={}", subproject.sid);
match client.delete_subproject(&subproject.sid).await {
Ok(()) => {
println!("✓ Subproject deleted successfully");
}
Err(e) => {
panic!("Error deleting subproject: {:?}", e);
}
};
match client.get_subproject(&subproject.sid).await {
Err(SignalWireError::NotFound(_)) => {
println!("✓ Subproject no longer exists (as expected)");
}
Ok(_) => {
panic!("Subproject still exists after deletion");
}
Err(e) => {
println!("Unexpected error checking deleted subproject: {:?}", e);
}
};
}
#[tokio::test]
async fn test_get_subproject_phone_numbers() {
dotenv().ok();
let subproject_sid = match env::var("SIGNALWIRE_TEST_SUBPROJECT_SID") {
Ok(sid) => sid,
Err(_) => {
println!("Skipping subproject phone numbers test. To enable, set SIGNALWIRE_TEST_SUBPROJECT_SID in your .env file.");
return;
}
};
let client = get_client_from_env();
let query_params = PhoneNumberOwnedFilterParams::new().build();
match client.get_subproject(&subproject_sid).await {
Ok(subproject) => {
println!("Testing phone numbers for subproject: {} ({})", subproject.friendly_name, subproject.sid);
}
Err(e) => {
println!("Error retrieving subproject details: {:?}", e);
println!("Make sure the SIGNALWIRE_TEST_SUBPROJECT_SID is correct and the subproject exists.");
return;
}
}
match client.get_subproject_phone_numbers(&subproject_sid, &query_params).await {
Ok(phone_numbers) => {
println!("Found {} phone number(s) in the subproject", phone_numbers.incoming_phone_numbers.len());
if phone_numbers.incoming_phone_numbers.is_empty() {
println!("No phone numbers found in this subproject.");
} else {
for (i, number) in phone_numbers.incoming_phone_numbers.iter().enumerate() {
println!("Phone #{}: Number={}", i + 1, number.phone_number);
}
}
}
Err(SignalWireError::NotFound(_)) => {
println!("Subproject not found or has no phone numbers.");
}
Err(e) => {
println!("Error retrieving subproject phone numbers: {:?}", e);
}
}
}
}