Compare commits
No commits in common. "4f7b87ea151ec2647fd15089baf6b77650bfa7f5" and "8881e4d7eb0f082cd71ab41b4496e6f999cfb3ef" have entirely different histories.
4f7b87ea15
...
8881e4d7eb
4 changed files with 8 additions and 314 deletions
|
@ -13,15 +13,14 @@ pub fn setup(cfg: &mut actix_web::web::ServiceConfig) {
|
||||||
|
|
||||||
#[derive(Deserialize, Validate, ToSchema)]
|
#[derive(Deserialize, Validate, ToSchema)]
|
||||||
pub struct CreateUser {
|
pub struct CreateUser {
|
||||||
#[validate(length(min = 4, max = 255))]
|
#[validate(length(min = 4))]
|
||||||
/// Username (minimum 4 characters, maximum 255 characters)
|
/// Username (minimum 4 characters)
|
||||||
/// TODO: Don't allow spaces, only alphanumeric characters and underscores
|
/// TODO: Don't allow spaces, only alphanumeric characters and underscores
|
||||||
username: String,
|
username: String,
|
||||||
#[validate(length(min = 3))]
|
/// Full name of the user
|
||||||
/// Full name of the user (minimum 3 characters)
|
|
||||||
name: String,
|
name: String,
|
||||||
#[validate(length(min = 8, max = 255))]
|
#[validate(length(min = 8))]
|
||||||
/// Password (minimum 8 characters, maximum 255 characters)
|
/// Password (minimum 8 characters)
|
||||||
password: String,
|
password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,74 +135,3 @@ async fn delete_user(
|
||||||
db.delete_user(id).await?;
|
db.delete_user(id).await?;
|
||||||
Ok(web::Json(format!("User {} deleted", id)))
|
Ok(web::Json(format!("User {} deleted", id)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[actix_web::test]
|
|
||||||
async fn test_validation_create_user_struct_valid() {
|
|
||||||
let user = CreateUser {
|
|
||||||
username: "testuser".to_string(),
|
|
||||||
name: "Test User".to_string(),
|
|
||||||
password: "password123".to_string(),
|
|
||||||
};
|
|
||||||
let validation_result = user.validate();
|
|
||||||
assert!(validation_result.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_web::test]
|
|
||||||
async fn test_validation_create_user_struct_username_invalid() {
|
|
||||||
let user = CreateUser {
|
|
||||||
username: "usr".to_string(), // too short
|
|
||||||
name: "Test User".to_string(),
|
|
||||||
password: "password".to_string(),
|
|
||||||
};
|
|
||||||
let validation_result = user.validate();
|
|
||||||
assert!(validation_result.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_web::test]
|
|
||||||
async fn test_validation_create_user_struct_username_too_long() {
|
|
||||||
let user = CreateUser {
|
|
||||||
username: "a".repeat(256), // too long
|
|
||||||
name: "Test User".to_string(),
|
|
||||||
password: "password123".to_string(),
|
|
||||||
};
|
|
||||||
let validation_result = user.validate();
|
|
||||||
assert!(validation_result.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_web::test]
|
|
||||||
async fn test_validation_create_user_struct_name_invalid() {
|
|
||||||
let user = CreateUser {
|
|
||||||
username: "testuser".to_string(),
|
|
||||||
name: "".to_string(), // empty name
|
|
||||||
password: "password123".to_string(),
|
|
||||||
};
|
|
||||||
let validation_result = user.validate();
|
|
||||||
assert!(validation_result.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_web::test]
|
|
||||||
async fn test_validation_create_user_struct_password_invalid() {
|
|
||||||
let user = CreateUser {
|
|
||||||
username: "testuser".to_string(),
|
|
||||||
name: "Test User".to_string(),
|
|
||||||
password: "pass".to_string(), // too short
|
|
||||||
};
|
|
||||||
let validation_result = user.validate();
|
|
||||||
assert!(validation_result.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_web::test]
|
|
||||||
async fn test_validation_create_user_struct_password_too_long() {
|
|
||||||
let user = CreateUser {
|
|
||||||
username: "testuser".to_string(),
|
|
||||||
name: "Test User".to_string(),
|
|
||||||
password: "a".repeat(256), // too long
|
|
||||||
};
|
|
||||||
let validation_result = user.validate();
|
|
||||||
assert!(validation_result.is_err());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -12,8 +12,8 @@ use validator::Validate;
|
||||||
|
|
||||||
#[derive(Deserialize, Validate, ToSchema)]
|
#[derive(Deserialize, Validate, ToSchema)]
|
||||||
pub struct CreateProject {
|
pub struct CreateProject {
|
||||||
#[validate(length(min = 3, max = 255))]
|
#[validate(length(min = 3))]
|
||||||
/// Project name (minimum 3 characters and maximum 255 characters)
|
/// Project name (minimum 3 characters)
|
||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,53 +84,3 @@ impl Database {
|
||||||
Ok(project)
|
Ok(project)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[actix_web::test]
|
|
||||||
async fn test_validation_create_project_struct_valid() {
|
|
||||||
let project = CreateProject {
|
|
||||||
name: "Test Project".to_string(),
|
|
||||||
};
|
|
||||||
let validation_result = project.validate();
|
|
||||||
assert!(validation_result.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_web::test]
|
|
||||||
async fn test_validation_create_project_struct_invalid_too_short() {
|
|
||||||
let project = CreateProject {
|
|
||||||
name: "TP".to_string(), // too short
|
|
||||||
};
|
|
||||||
let validation_result = project.validate();
|
|
||||||
assert!(validation_result.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_web::test]
|
|
||||||
async fn test_validation_create_project_struct_empty() {
|
|
||||||
let project = CreateProject {
|
|
||||||
name: "".to_string(), // empty string
|
|
||||||
};
|
|
||||||
let validation_result = project.validate();
|
|
||||||
assert!(validation_result.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_web::test]
|
|
||||||
async fn test_validation_create_project_struct_min_length() {
|
|
||||||
let project = CreateProject {
|
|
||||||
name: "abc".to_string(), // exactly at min length
|
|
||||||
};
|
|
||||||
let validation_result = project.validate();
|
|
||||||
assert!(validation_result.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_web::test]
|
|
||||||
async fn test_validation_create_project_struct_long_name() {
|
|
||||||
// 256 characters long should be invalid because of max length
|
|
||||||
let long_name = "a".repeat(256);
|
|
||||||
let project = CreateProject { name: long_name };
|
|
||||||
let validation_result = project.validate();
|
|
||||||
assert!(validation_result.is_err());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ use lazy_static::lazy_static;
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
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};
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use super::setup;
|
use super::setup;
|
||||||
|
|
||||||
|
@ -69,11 +68,6 @@ impl TestContext {
|
||||||
self.cleanup_projects(db).await;
|
self.cleanup_projects(db).await;
|
||||||
self.cleanup_users(db).await;
|
self.cleanup_users(db).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn cleanup_all(&self, db: &Database) {
|
|
||||||
self.cleanup_projects(db).await;
|
|
||||||
self.cleanup_users(db).await;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
@ -92,17 +86,3 @@ macro_rules! create_test_app {
|
||||||
.await
|
.await
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! with_test_context {
|
|
||||||
($test_fn:expr) => {{
|
|
||||||
let ctx = $crate::common::test_helpers::TestContext::new();
|
|
||||||
let db = $crate::common::test_helpers::get_database().await;
|
|
||||||
|
|
||||||
let result = $test_fn(ctx.clone(), db).await;
|
|
||||||
|
|
||||||
ctx.cleanup_all(db).await;
|
|
||||||
|
|
||||||
result
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,164 +0,0 @@
|
||||||
use crate::{common::test_helpers::TestContext, create_test_app, with_test_context};
|
|
||||||
use actix_web::test;
|
|
||||||
use backend::Database;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[actix_web::test]
|
|
||||||
async fn test_user_crud_operations() {
|
|
||||||
with_test_context!(|ctx: TestContext, db: &Database| async move {
|
|
||||||
// Create single user
|
|
||||||
let user = ctx
|
|
||||||
.create_user(
|
|
||||||
db,
|
|
||||||
Some("testuser".to_string()),
|
|
||||||
Some("Test User".to_string()),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(user.username, "testuser");
|
|
||||||
assert_eq!(user.name, "Test User");
|
|
||||||
|
|
||||||
// Assert user exists
|
|
||||||
assert!(ctx.assert_user_exists(db, user.id).await);
|
|
||||||
|
|
||||||
// Get user by ID
|
|
||||||
let retrieved_user = ctx.get_user_by_id(db, user.id).await.unwrap().unwrap();
|
|
||||||
assert_eq!(retrieved_user.id, user.id);
|
|
||||||
|
|
||||||
// Create multiple users
|
|
||||||
let users = ctx.create_multiple_users(db, 3).await.unwrap();
|
|
||||||
assert_eq!(users.len(), 3);
|
|
||||||
|
|
||||||
// Assert user count (initial user + 3 new users = 4)
|
|
||||||
assert!(ctx.assert_user_count(db, 4).await);
|
|
||||||
|
|
||||||
// Delete a user
|
|
||||||
ctx.delete_user(db, user.id).await.unwrap();
|
|
||||||
assert!(ctx.assert_user_not_exists(db, user.id).await);
|
|
||||||
|
|
||||||
// All cleanup handled automatically
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_web::test]
|
|
||||||
async fn test_project_crud_operations() {
|
|
||||||
with_test_context!(|ctx: TestContext, db: &Database| async move {
|
|
||||||
// Create project with custom name
|
|
||||||
let project = ctx
|
|
||||||
.create_project_with_name(db, "My Test Project".to_string())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(project.name, "My Test Project");
|
|
||||||
|
|
||||||
// Assert project exists
|
|
||||||
assert!(ctx.assert_project_exists(db, &project.id).await);
|
|
||||||
|
|
||||||
// Update project
|
|
||||||
let updated = ctx
|
|
||||||
.update_project(db, &project.id, "Updated Project Name".to_string())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(updated.name, "Updated Project Name");
|
|
||||||
|
|
||||||
// Assert project name changed
|
|
||||||
assert!(
|
|
||||||
ctx.assert_project_name(db, &project.id, "Updated Project Name")
|
|
||||||
.await
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create multiple projects
|
|
||||||
let projects = ctx.create_multiple_projects(db, 2).await.unwrap();
|
|
||||||
assert_eq!(projects.len(), 2);
|
|
||||||
|
|
||||||
// Assert project count (1 original + 2 new = 3)
|
|
||||||
assert!(ctx.assert_project_count(db, 3).await);
|
|
||||||
|
|
||||||
// All cleanup automatic
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_web::test]
|
|
||||||
async fn test_authentication_operations() {
|
|
||||||
with_test_context!(|ctx: TestContext, db: &Database| async move {
|
|
||||||
// Create authenticated user
|
|
||||||
let (user, password) = ctx
|
|
||||||
.create_authenticated_user(
|
|
||||||
db,
|
|
||||||
Some("authuser".to_string()),
|
|
||||||
Some("securepass123".to_string()),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Test successful login
|
|
||||||
assert!(
|
|
||||||
ctx.assert_user_can_login(db, "authuser", "securepass123")
|
|
||||||
.await
|
|
||||||
);
|
|
||||||
|
|
||||||
// Test login returns correct user ID
|
|
||||||
assert!(
|
|
||||||
ctx.assert_user_login_returns_correct_id(db, "authuser", "securepass123", user.id)
|
|
||||||
.await
|
|
||||||
);
|
|
||||||
|
|
||||||
// Test failed login with wrong password
|
|
||||||
assert!(
|
|
||||||
ctx.assert_user_cannot_login(db, "authuser", "wrongpassword")
|
|
||||||
.await
|
|
||||||
);
|
|
||||||
|
|
||||||
// Test multiple invalid login attempts
|
|
||||||
let invalid_results = ctx.test_invalid_login_attempts(db, "authuser").await;
|
|
||||||
assert!(invalid_results.iter().all(|&result| result)); // All should fail (return true)
|
|
||||||
|
|
||||||
// Create and verify multiple auth users
|
|
||||||
let auth_users = ctx
|
|
||||||
.create_multiple_authenticated_users(db, 2)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(auth_users.len(), 2);
|
|
||||||
|
|
||||||
for (user, pwd) in &auth_users {
|
|
||||||
assert!(ctx.assert_user_can_login(db, &user.username, pwd).await);
|
|
||||||
}
|
|
||||||
|
|
||||||
// All cleanup automatic
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_web::test]
|
|
||||||
async fn test_mixed_operations_with_api() {
|
|
||||||
with_test_context!(|ctx: TestContext, db: &Database| async move {
|
|
||||||
let app = create_test_app!();
|
|
||||||
|
|
||||||
// Create user via helper
|
|
||||||
let user = ctx.create_user(db, None, None).await.unwrap();
|
|
||||||
|
|
||||||
// Create project via helper
|
|
||||||
let project = ctx.create_project(db, None).await.unwrap();
|
|
||||||
|
|
||||||
// Test API endpoints
|
|
||||||
let user_resp = test::TestRequest::get()
|
|
||||||
.uri(&format!("/api/v1/user/{}", user.id))
|
|
||||||
.send_request(&app)
|
|
||||||
.await;
|
|
||||||
assert!(user_resp.status().is_success());
|
|
||||||
|
|
||||||
let projects_resp = test::TestRequest::get()
|
|
||||||
.uri("/api/v1/project")
|
|
||||||
.send_request(&app)
|
|
||||||
.await;
|
|
||||||
assert!(projects_resp.status().is_success());
|
|
||||||
|
|
||||||
// Assert both exist in database
|
|
||||||
assert!(ctx.assert_user_exists(db, user.id).await);
|
|
||||||
assert!(ctx.assert_project_exists(db, &project.id).await);
|
|
||||||
|
|
||||||
// All cleanup automatic
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue