User-Integration Tests and better infrastructure #28
2 changed files with 151 additions and 25 deletions
|
@ -1,5 +1,6 @@
|
||||||
use backend::Database;
|
use backend::Database;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
use testcontainers::ContainerAsync;
|
use testcontainers::ContainerAsync;
|
||||||
use testcontainers_modules::{postgres::Postgres, redis::Redis};
|
use testcontainers_modules::{postgres::Postgres, redis::Redis};
|
||||||
|
|
||||||
|
@ -30,6 +31,54 @@ pub async fn get_database() -> &'static Database {
|
||||||
&state.database
|
&state.database
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static TEST_COUNTER: AtomicU64 = AtomicU64::new(1);
|
||||||
|
|
||||||
|
pub fn get_unique_test_id() -> String {
|
||||||
|
let counter = TEST_COUNTER.fetch_add(1, Ordering::SeqCst);
|
||||||
|
let timestamp = std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_millis();
|
||||||
|
format!("test_{}_{}", timestamp, counter)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UserFactory;
|
||||||
|
|
||||||
|
impl UserFactory {
|
||||||
|
pub fn create_request(username: Option<String>, name: Option<String>) -> serde_json::Value {
|
||||||
|
let test_id = get_unique_test_id();
|
||||||
|
serde_json::json!({
|
||||||
|
"username": username.unwrap_or_else(|| format!("user_{}", test_id)),
|
||||||
|
"name": name.unwrap_or_else(|| format!("Test User {}", test_id)),
|
||||||
|
"password": "password123"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_unique_request() -> serde_json::Value {
|
||||||
|
Self::create_request(None, None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TestContext {
|
||||||
|
pub test_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestContext {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
test_id: get_unique_test_id(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_user_data(&self, username_prefix: Option<&str>, name: Option<&str>) -> serde_json::Value {
|
||||||
|
let username = username_prefix
|
||||||
|
.map(|prefix| format!("{}_{}", prefix, self.test_id))
|
||||||
|
.unwrap_or_else(|| format!("user_{}", self.test_id));
|
||||||
|
|
||||||
|
UserFactory::create_request(Some(username), name.map(String::from))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! create_test_app {
|
macro_rules! create_test_app {
|
||||||
() => {{
|
() => {{
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
use actix_web::{http::header, test};
|
use actix_web::{http::header, test};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::create_test_app;
|
use crate::{create_test_app, common::test_helpers::UserFactory};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use serde_json::json;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||||
|
@ -19,46 +17,125 @@ mod tests {
|
||||||
#[actix_web::test]
|
#[actix_web::test]
|
||||||
async fn test_create_user() {
|
async fn test_create_user() {
|
||||||
let app = create_test_app!();
|
let app = create_test_app!();
|
||||||
|
let user_data = UserFactory::create_unique_request();
|
||||||
|
|
||||||
let resp = test::TestRequest::post()
|
let resp = test::TestRequest::post()
|
||||||
.uri("/api/v1/user")
|
.uri("/api/v1/user")
|
||||||
.insert_header(header::ContentType::json())
|
.insert_header(header::ContentType::json())
|
||||||
.set_payload(
|
.set_payload(user_data.to_string())
|
||||||
json!({
|
|
||||||
"username": "testuser",
|
|
||||||
"name": "Test User",
|
|
||||||
"password": "password"
|
|
||||||
})
|
|
||||||
.to_string(),
|
|
||||||
)
|
|
||||||
.send_request(&app)
|
.send_request(&app)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let status = resp.status();
|
let status = resp.status();
|
||||||
let user: RespCreateUser = test::read_body_json(resp).await;
|
let user: RespCreateUser = test::read_body_json(resp).await;
|
||||||
|
|
||||||
assert!(user.name == "Test User");
|
// Verify that the user was created with the expected structure
|
||||||
assert!(user.username == "testuser");
|
assert!(!user.name.is_empty());
|
||||||
|
assert!(!user.username.is_empty());
|
||||||
|
assert!(user.username.starts_with("user_test_"));
|
||||||
|
assert!(user.name.starts_with("Test User"));
|
||||||
assert!(status.is_success());
|
assert!(status.is_success());
|
||||||
|
|
||||||
let resp_del = test::TestRequest::delete()
|
// Cleanup - delete the created user
|
||||||
|
let _delete_resp = test::TestRequest::delete()
|
||||||
.uri(&format!("/api/v1/user/{}", user.id))
|
.uri(&format!("/api/v1/user/{}", user.id))
|
||||||
.send_request(&app)
|
.send_request(&app)
|
||||||
.await;
|
.await;
|
||||||
let status_del = resp_del.status();
|
// Don't assert on cleanup status in case of race conditions
|
||||||
|
}
|
||||||
|
|
||||||
let delete_message: String = test::read_body_json(resp_del).await;
|
#[actix_web::test]
|
||||||
|
async fn test_delete_user() {
|
||||||
|
let app = create_test_app!();
|
||||||
|
let user_data = UserFactory::create_unique_request();
|
||||||
|
|
||||||
|
// Create user to delete
|
||||||
|
let create_resp = test::TestRequest::post()
|
||||||
|
.uri("/api/v1/user")
|
||||||
|
.insert_header(header::ContentType::json())
|
||||||
|
.set_payload(user_data.to_string())
|
||||||
|
.send_request(&app)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let create_status = create_resp.status();
|
||||||
|
assert!(create_status.is_success(), "Failed to create user: {}", create_status);
|
||||||
|
let user: RespCreateUser = test::read_body_json(create_resp).await;
|
||||||
|
|
||||||
|
// Delete the user
|
||||||
|
let delete_resp = test::TestRequest::delete()
|
||||||
|
.uri(&format!("/api/v1/user/{}", user.id))
|
||||||
|
.send_request(&app)
|
||||||
|
.await;
|
||||||
|
let delete_status = delete_resp.status();
|
||||||
|
|
||||||
|
let delete_message: String = test::read_body_json(delete_resp).await;
|
||||||
assert_eq!(delete_message, format!("User {} deleted", user.id));
|
assert_eq!(delete_message, format!("User {} deleted", user.id));
|
||||||
|
assert!(delete_status.is_success(), "Failed to delete user with status: {:?}", delete_status);
|
||||||
|
}
|
||||||
|
|
||||||
assert!(
|
#[actix_web::test]
|
||||||
status_del.is_success(),
|
async fn test_get_users() {
|
||||||
"Failed to delete user with status: {:?}",
|
let app = create_test_app!();
|
||||||
status_del
|
|
||||||
);
|
|
||||||
|
|
||||||
// Debugging output
|
let resp = test::TestRequest::get()
|
||||||
dbg!(user);
|
.uri("/api/v1/user")
|
||||||
dbg!(delete_message);
|
.send_request(&app)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let status = resp.status();
|
||||||
|
let users: Vec<RespCreateUser> = test::read_body_json(resp).await;
|
||||||
|
|
||||||
|
assert!(status.is_success());
|
||||||
|
assert!(users.is_empty() || !users.is_empty()); // Just verify it returns a valid array
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn test_create_user_duplicate_username() {
|
||||||
|
let app = create_test_app!();
|
||||||
|
let user_data = UserFactory::create_unique_request();
|
||||||
|
|
||||||
|
// Create first user
|
||||||
|
let resp1 = test::TestRequest::post()
|
||||||
|
.uri("/api/v1/user")
|
||||||
|
.insert_header(header::ContentType::json())
|
||||||
|
.set_payload(user_data.to_string())
|
||||||
|
.send_request(&app)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let status1 = resp1.status();
|
||||||
|
let user1: RespCreateUser = test::read_body_json(resp1).await;
|
||||||
|
assert!(status1.is_success());
|
||||||
|
|
||||||
|
// Try to create user with same username
|
||||||
|
let resp2 = test::TestRequest::post()
|
||||||
|
.uri("/api/v1/user")
|
||||||
|
.insert_header(header::ContentType::json())
|
||||||
|
.set_payload(user_data.to_string())
|
||||||
|
.send_request(&app)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let status2 = resp2.status();
|
||||||
|
assert!(status2.is_client_error() || status2.is_server_error());
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
let _delete_resp = test::TestRequest::delete()
|
||||||
|
.uri(&format!("/api/v1/user/{}", user1.id))
|
||||||
|
.send_request(&app)
|
||||||
|
.await;
|
||||||
|
// Don't assert on cleanup status in case of race conditions
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn test_delete_nonexistent_user() {
|
||||||
|
let app = create_test_app!();
|
||||||
|
let fake_id = "00000000-0000-0000-0000-000000000000";
|
||||||
|
|
||||||
|
let resp = test::TestRequest::delete()
|
||||||
|
.uri(&format!("/api/v1/user/{}", fake_id))
|
||||||
|
.send_request(&app)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let status = resp.status();
|
||||||
|
assert!(status.is_client_error() || status.is_server_error());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue