use textsender_api::db; mod db_mgr { use std::str::FromStr; pub const LIMIT: usize = 6; pub async fn get_pool() -> Result { let tm_db_url = textsender_models::envy::environment::get_db_url() .await .value; let tm_options = sqlx::postgres::PgConnectOptions::from_str(&tm_db_url).unwrap(); sqlx::PgPool::connect_with(tm_options).await } pub async fn generate_db_name() -> String { let db_name = get_database_name().await.unwrap() + &"_" + &uuid::Uuid::new_v4().to_string()[..LIMIT]; db_name } pub async fn connect_to_db(db_name: &str) -> Result { let db_url = textsender_models::envy::environment::get_db_url() .await .value; let options = sqlx::postgres::PgConnectOptions::from_str(&db_url)?.database(db_name); sqlx::PgPool::connect_with(options).await } pub async fn create_database( template_pool: &sqlx::PgPool, db_name: &str, ) -> Result<(), sqlx::Error> { let create_query = format!("CREATE DATABASE {}", db_name); match sqlx::query(&create_query).execute(template_pool).await { Ok(_) => Ok(()), Err(e) => Err(e), } } pub async fn get_database_ready() -> ( sqlx::Pool, String, sqlx::Pool, ) { let tm_pool = get_pool().await.unwrap(); let db_name = generate_db_name().await; match create_database(&tm_pool, &db_name).await { Ok(_) => { println!("Success"); } Err(err) => { assert!(false, "Error: {:?}", err); } } let pool = connect_to_db(&db_name).await.unwrap(); migrations(&pool).await; (tm_pool, db_name, pool) } // Function to drop a database pub async fn drop_database( template_pool: &sqlx::PgPool, db_name: &str, ) -> Result<(), sqlx::Error> { let drop_query = format!("DROP DATABASE IF EXISTS {} WITH (FORCE)", db_name); sqlx::query(&drop_query).execute(template_pool).await?; Ok(()) } pub async fn get_database_name() -> Result> { let database_url = textsender_models::envy::environment::get_db_url() .await .value; let parsed_url = url::Url::parse(&database_url)?; if parsed_url.scheme() == "postgres" || parsed_url.scheme() == "postgresql" { match parsed_url .path_segments() .and_then(|segments| segments.last().map(|s| s.to_string())) { Some(sss) => Ok(sss), None => Err("Error parsing".into()), } } else { // Handle other database types if needed Err("Error parsing".into()) } } pub async fn migrations(pool: &sqlx::PgPool) { super::db::migrations(pool).await; } } mod init { pub async fn app(pool: sqlx::PgPool) -> axum::Router { textsender_api::config::init::routes() .await .layer(axum::Extension(pool)) .layer(axum::extract::DefaultBodyLimit::max(1024 * 1024 * 1024)) .layer(tower_http::timeout::TimeoutLayer::with_status_code( axum::http::StatusCode::OK, std::time::Duration::from_secs(300), )) } } mod util { pub async fn resp_to_bytes( response: axum::response::Response, ) -> Result { axum::body::to_bytes(response.into_body(), std::usize::MAX).await } pub async fn get_resp_data(response: axum::response::Response) -> Data where Data: for<'a> serde::Deserialize<'a>, { let body = resp_to_bytes(response).await.unwrap(); serde_json::from_slice(&body).unwrap() } /* pub async fn format_url_with_value(endpoint: &str, value: &uuid::Uuid) -> String { let last = endpoint.len() - 5; format!("{}/{value}", &endpoint[0..last]) } */ } pub fn token_fields() -> (String, String, String) { ( String::from("What a twist!"), String::from("textsender_test"), String::from("textsender"), ) } /// Test User Id pub const TEST_USER_ID: uuid::Uuid = uuid::uuid!("cc938368-615a-4694-b2ca-6e122fa31c52"); /// Test contact phone number pub const TEST_CONTACT_PHONE_NUMBER: &str = "+10123456789"; /// Test contact updated firstname pub const TEST_CONTACT_UPDATED_FIRSTNAME: &str = "Johnny"; /// Test contact updated lastname pub const TEST_CONTACT_UPDATED_LASTNAME: &str = "CASH"; /// Test contact updated nickname pub const TEST_CONTACT_UPDATED_NICKNAME: &str = "The Man in Black"; /// Test message content pub const TEST_MESSAGE_CONTENT: &str = "The wind cries mary"; pub async fn test_token() -> Result { let key: String = textsender_models::envy::environment::get_secret_main_key() .await .value; let (message, issuer, audience) = token_fields(); let token_resource = textsender_models::token::TokenResource { message: message, issuer: issuer, audiences: vec![audience], user_id: TEST_USER_ID, }; match textsender_models::token::create_token(&key, &token_resource, time::Duration::hours(1)) { Ok((access_token, _some_time)) => Ok(access_token), Err(err) => Err(err), } } pub fn test_valid_schedulable_time() -> time::OffsetDateTime { let now = time::OffsetDateTime::now_utc(); now + std::time::Duration::from_secs(60 * 60) } pub fn convert_time_to_iso(time: time::OffsetDateTime) -> Result { use time::format_description::well_known::Iso8601; match time.format(&Iso8601::DEFAULT) { Ok(converted) => Ok(converted), Err(err) => Err(err), } } pub async fn bearer_auth() -> String { let token = match test_token().await { Ok(access_token) => access_token, Err(err) => { assert!(false, "Error: {err:?}"); String::new() } }; format!("Bearer {token}") } mod request { use tower::ServiceExt; pub async fn create_contact( app: &axum::Router, ) -> Result { let payload = serde_json::json!({ "phone_number": super::TEST_CONTACT_PHONE_NUMBER, "user_id": super::TEST_USER_ID, }); match run_post( Some(&payload), textsender_api::caller::endpoints::ADD_CONTACT, axum::http::Method::POST, true, ) .await { Ok(req) => match app.clone().oneshot(req).await { Ok(response) => Ok(response), Err(err) => Err(axum::http::Error::from(err)), }, Err(err) => Err(err), } } pub async fn get_contact( app: &axum::Router, id: &uuid::Uuid, ) -> Result { let uri = format!( "{}?id={}", textsender_api::caller::endpoints::GET_CONTACT, id ); match run_post(None, &uri, axum::http::Method::GET, false).await { Ok(req) => match app.clone().oneshot(req).await { Ok(response) => Ok(response), Err(err) => Err(axum::http::Error::from(err)), }, Err(err) => Err(err), } } pub async fn update_contact_names( app: &axum::Router, id: &uuid::Uuid, firstname: Option<&str>, lastname: Option<&str>, nickname: Option<&str>, ) -> Result { let payload = serde_json::json!({ "firstname": firstname, "lastname": lastname, "nickname": nickname, "phone_number": super::TEST_CONTACT_PHONE_NUMBER, "id": id }); match run_post( Some(&payload), textsender_api::caller::endpoints::UPDATE_CONTACT_NAME, axum::http::Method::PATCH, true, ) .await { Ok(req) => match app.clone().oneshot(req).await { Ok(response) => Ok(response), Err(err) => Err(axum::http::Error::from(err)), }, Err(err) => Err(err), } } pub mod message { use tower::ServiceExt; pub async fn create_message( app: &axum::Router, ) -> Result { let payload = serde_json::json!({ "content": super::super::TEST_MESSAGE_CONTENT, "user_id": super::super::TEST_USER_ID, }); match super::run_post( Some(&payload), textsender_api::caller::endpoints::ADD_MESSAGE, axum::http::Method::POST, true, ) .await { Ok(req) => match app.clone().oneshot(req).await { Ok(response) => Ok(response), Err(err) => Err(axum::http::Error::from(err)), }, Err(err) => Err(err), } } pub async fn get_message( app: &axum::Router, id: &uuid::Uuid, ) -> Result { let uri = format!( "{}?id={}", textsender_api::caller::endpoints::GET_MESSAGE, id ); match super::run_post(None, &uri, axum::http::Method::GET, false).await { Ok(req) => match app.clone().oneshot(req).await { Ok(response) => Ok(response), Err(err) => Err(axum::http::Error::from(err)), }, Err(err) => Err(err), } } pub mod scheduling { use tower::ServiceExt; pub async fn create_scheduled_message( app: &axum::Router, ) -> Result { let scheduled = super::super::super::test_valid_schedulable_time(); let scheduled = match super::super::super::convert_time_to_iso(scheduled) { Ok(t) => t, Err(err) => { assert!(false, "Error: {err:?}"); String::new() } }; let payload = serde_json::json!({ "scheduled": scheduled, "status": textsender_models::message::scheduling::PENDING, "user_id": super::super::super::TEST_USER_ID, }); match super::super::run_post( Some(&payload), textsender_api::caller::endpoints::SCHEDULE_MESSAGE, axum::http::Method::POST, true, ) .await { Ok(req) => match app.clone().oneshot(req).await { Ok(response) => Ok(response), Err(err) => Err(axum::http::Error::from(err)), }, Err(err) => Err(err), } } pub async fn get_scheduled_messages( app: &axum::Router, id: &uuid::Uuid, ) -> Result { let uri = format!( "{}?id={}", textsender_api::caller::endpoints::GET_SCHEDULE_MESSAGE, id ); match super::super::run_post(None, &uri, axum::http::Method::GET, false).await { Ok(req) => match app.clone().oneshot(req).await { Ok(response) => Ok(response), Err(err) => Err(axum::http::Error::from(err)), }, Err(err) => Err(err), } } /* pub async fn create_scheduled_message_event( app: &axum::Router, contact_id: &uuid::Uuid, message_id: &uuid::Uuid, scheduled_message_id: &uuid::Uuid, ) -> Result { let payload = serde_json::json!({ "contact_id": contact_id, "message_id": message_id, "scheduled_message_id": scheduled_message_id }); match super::super::run_post( Some(&payload), textsender_api::caller::endpoints::CREATE_SCHEDULED_MESSAGE_EVENT, axum::http::Method::POST, true, ) .await { Ok(req) => match app.clone().oneshot(req).await { Ok(response) => Ok(response), Err(err) => Err(axum::http::Error::from(err)), }, Err(err) => Err(err), } } */ } } pub async fn run_post( payload: Option<&serde_json::Value>, uri: &str, method: axum::http::Method, has_body: bool, ) -> Result, axum::http::Error> { let body = if has_body { assert_eq!( true, payload.is_some(), "Has request body and payload has data" ); axum::body::Body::from(payload.unwrap().to_string()) } else { axum::body::Body::empty() }; match axum::http::Request::builder() .method(method) .uri(uri) .header(axum::http::header::CONTENT_TYPE, "application/json") .header( axum::http::header::AUTHORIZATION, super::bearer_auth().await, ) .body(body) { Ok(t) => Ok(t), Err(err) => Err(err), } } } #[tokio::test] async fn test_create_contact() { let (tm_pool, db_name, pool) = db_mgr::get_database_ready().await; let app = init::app(pool).await; // Send request match request::create_contact(&app).await { Ok(response) => { let resp = util::get_resp_data::< textsender_api::caller::contact::response::AddContactResponse, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); } Err(err) => { assert!(false, "Error: {:?}", err); } }; let _ = db_mgr::drop_database(&tm_pool, &db_name).await; } #[tokio::test] async fn test_get_contact() { let (tm_pool, db_name, pool) = db_mgr::get_database_ready().await; let app = init::app(pool).await; // Send request match request::create_contact(&app).await { Ok(response) => { let resp = util::get_resp_data::< textsender_api::caller::contact::response::AddContactResponse, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); let contact = &resp.data[0]; assert_eq!(true, contact.id.is_some(), "Missing Contact Id"); let id = contact.id.unwrap(); match request::get_contact(&app, &id).await { Ok(response) => { let resp = util::get_resp_data::< textsender_api::caller::contact::response::GetContactResponse, >(response) .await; assert_eq!( false, resp.data.is_empty(), "At least Contact record should have been returned" ); } Err(err) => { assert!(false, "Error: {err:?}"); } } } Err(err) => { assert!(false, "Error: {:?}", err); } }; let _ = db_mgr::drop_database(&tm_pool, &db_name).await; } #[tokio::test] async fn test_update_contact_names() { let (tm_pool, db_name, pool) = db_mgr::get_database_ready().await; let app = init::app(pool).await; let mut opt_contact: Option = None; // Send request match request::create_contact(&app).await { Ok(response) => { let resp = util::get_resp_data::< textsender_api::caller::contact::response::AddContactResponse, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); let contact = &resp.data[0]; assert_eq!(true, contact.id.is_some(), "Missing Contact Id"); let id = contact.id.unwrap(); match request::get_contact(&app, &id).await { Ok(response) => { let resp = util::get_resp_data::< textsender_api::caller::contact::response::GetContactResponse, >(response) .await; assert_eq!( false, resp.data.is_empty(), "At least Contact record should have been returned" ); opt_contact = Some(resp.data[0].clone()) } Err(err) => { assert!(false, "Error: {err:?}"); } } } Err(err) => { assert!(false, "Error: {:?}", err); } }; match opt_contact { Some(contact) => { let new_firstname = Some(TEST_CONTACT_UPDATED_FIRSTNAME); let new_lastname = Some(TEST_CONTACT_UPDATED_LASTNAME); let new_nickname = Some(TEST_CONTACT_UPDATED_NICKNAME); let contact_id = contact.id.unwrap(); match request::update_contact_names( &app, &contact_id, new_firstname, new_lastname, new_nickname, ) .await { Ok(response) => { let resp = util::get_resp_data::< textsender_api::caller::contact::response::UpdateContactNamesResponse, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Error updating Contact names"); } Err(err) => { assert!(false, "Error: {err:?}"); } } } None => { assert!(false, "Contact not found"); } } let _ = db_mgr::drop_database(&tm_pool, &db_name).await; } #[tokio::test] async fn test_create_message() { let (tm_pool, db_name, pool) = db_mgr::get_database_ready().await; let app = init::app(pool).await; // Send request match request::message::create_message(&app).await { Ok(response) => { let resp = util::get_resp_data::< textsender_api::caller::message::response::AddMessageResponse, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); let message = &resp.data[0]; assert_eq!( TEST_MESSAGE_CONTENT, message.content, "The content of the created message should match" ); } Err(err) => { assert!(false, "Error: {:?}", err); } }; let _ = db_mgr::drop_database(&tm_pool, &db_name).await; } #[tokio::test] async fn test_get_message() { let (tm_pool, db_name, pool) = db_mgr::get_database_ready().await; let app = init::app(pool).await; let mut message_result: Option = None; // Send request match request::message::create_message(&app).await { Ok(response) => { let resp = util::get_resp_data::< textsender_api::caller::message::response::AddMessageResponse, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); let message = &resp.data[0]; assert_eq!( TEST_MESSAGE_CONTENT, message.content, "The content of the created message should match" ); message_result = Some(message.clone()); } Err(err) => { assert!(false, "Error: {:?}", err); } }; assert_eq!(true, message_result.is_some()); let message = message_result.unwrap(); let message_id = message.id.unwrap(); match request::message::get_message(&app, &message_id).await { Ok(response) => { let resp = util::get_resp_data::< textsender_api::caller::message::response::GetMessageResponse, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); } Err(err) => { assert!(false, "Error: {err:?}"); } } let _ = db_mgr::drop_database(&tm_pool, &db_name).await; } #[tokio::test] async fn test_schedule_message() { let (tm_pool, db_name, pool) = db_mgr::get_database_ready().await; let app = init::app(pool).await; match request::message::scheduling::create_scheduled_message(&app).await { Ok(response) => { let resp = util::get_resp_data::< textsender_api::caller::message::scheduling::response::ScheduleMessageResponse, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); } Err(err) => { assert!(false, "Error: {err:?}"); } } let _ = db_mgr::drop_database(&tm_pool, &db_name).await; } #[tokio::test] async fn test_get_scheduled_message() { let (tm_pool, db_name, pool) = db_mgr::get_database_ready().await; let app = init::app(pool).await; let mut id: Option = None; match request::message::scheduling::create_scheduled_message(&app).await { Ok(response) => { let resp = util::get_resp_data::< textsender_api::caller::message::scheduling::response::ScheduleMessageResponse, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); let scheduled_message = &resp.data[0]; id = Some(scheduled_message.id); } Err(err) => { assert!(false, "Error: {err:?}"); } } if let Some(parsed_id) = id { match request::message::scheduling::get_scheduled_messages(&app, &parsed_id).await { Ok(response) => { let resp = util::get_resp_data::< textsender_api::caller::message::scheduling::response::GetScheduledMessageResponse, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); } Err(err) => { assert!(false, "Error: {err:?}"); } } } else { assert!(false, "Scheduled Message Id could not be retrieved"); } let _ = db_mgr::drop_database(&tm_pool, &db_name).await; } #[tokio::test] async fn test_create_scheduled_message() { let (tm_pool, db_name, pool) = db_mgr::get_database_ready().await; let app = init::app(pool).await; // let mut contact_id: uuid::Uuid = uuid::Uuid::nil(); // let mut message_id: uuid::Uuid = uuid::Uuid::nil(); // let mut scheduled_message_id: uuid::Uuid = uuid::Uuid::nil(); match request::create_contact(&app).await { Ok(response) => { let resp = util::get_resp_data::< textsender_api::caller::contact::response::AddContactResponse, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); // contact_id = resp.data[0].id.unwrap(); } Err(err) => { assert!(false, "Error: {:?}", err); } }; /* match request::message::create_message(&app).await { Ok(response) => { let resp = util::get_resp_data::< textsender_api::caller::message::response::AddMessageResponse, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); let message = &resp.data[0]; message_id = message.id.unwrap(); } Err(err) => { assert!(false, "Error: {:?}", err); } }; match request::message::scheduling::create_scheduled_message(&app).await { Ok(response) => { let resp = util::get_resp_data::< textsender_api::caller::message::scheduling::response::ScheduleMessageResponse, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); scheduled_message_id = resp.data[0].id; } Err(err) => { assert!(false, "Error: {err:?}"); } } match request::message::scheduling::create_scheduled_message_event( &app, &contact_id, &message_id, &scheduled_message_id, ) .await { Ok(response) => { let resp = util::get_resp_data::< textsender_api::caller::message::scheduling::response::GetScheduledMessageResponse, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); } Err(err) => { assert!(false, "Error: {err:?}"); } } */ let _ = db_mgr::drop_database(&tm_pool, &db_name).await; }