refactor(tests): Add chaos and performance tests, remove obsolete tests, and update dependencies

This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-03-11 09:17:04 -03:00
parent 490ba66c51
commit d08ebfc094
14 changed files with 253 additions and 89 deletions

10
.vscode/launch.json vendored
View file

@ -18,7 +18,7 @@
"args": [],
"cwd": "${workspaceFolder}",
"env": {
"RUST_LOG": "debug",
"RUST_LOG": "info",
"DATABASE_URL": "postgres://gbuser:gbpassword@localhost:5432/generalbots",
"REDIS_URL": "redis://localhost:6379"
}
@ -42,7 +42,9 @@
"args": [
"--test-threads=1"
],
"cwd": "${workspaceFolder}"
"cwd": "${workspaceFolder}", "env": {
"RUST_LOG": "info"
}
},
{
"type": "lldb",
@ -61,7 +63,9 @@
}
},
"args": [],
"cwd": "${workspaceFolder}"
"cwd": "${workspaceFolder}", "env": {
"RUST_LOG": "info"
}
},
],
"compounds": [

View file

@ -2,5 +2,6 @@
"lldb.executable": "/usr/bin/lldb",
"lldb.showDisassembly": "never",
"lldb.dereferencePointers": true,
"lldb.consoleMode": "commands"
"lldb.consoleMode": "commands",
"rust-test Explorer.cargoTestExtraArgs": ["--", "--nocapture"]
}

28
Cargo.lock generated
View file

@ -2334,6 +2334,19 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe"
[[package]]
name = "env_logger"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
dependencies = [
"humantime",
"is-terminal",
"log",
"regex",
"termcolor",
]
[[package]]
name = "env_logger"
version = "0.11.6"
@ -2761,6 +2774,7 @@ dependencies = [
"thiserror 1.0.69",
"tokio",
"tokio-openssl",
"tokio-stream",
"tokio-test",
"tower 0.4.13",
"tower-http",
@ -2872,10 +2886,12 @@ dependencies = [
"actix-multipart",
"actix-web",
"async-trait",
"env_logger 0.10.2",
"futures 0.3.31",
"gb-core",
"jsonwebtoken",
"lettre",
"log",
"minio",
"rstest",
"sanitize-filename",
@ -2884,6 +2900,7 @@ dependencies = [
"tempfile",
"thiserror 1.0.69",
"tokio",
"tokio-stream",
"tokio-test",
"tracing",
"uuid",
@ -3080,6 +3097,7 @@ dependencies = [
"sqlx",
"tempfile",
"tokio",
"tokio-stream",
"tokio-tungstenite 0.24.0",
"tracing",
"tungstenite 0.20.1",
@ -4614,7 +4632,7 @@ dependencies = [
"crc",
"dashmap 6.1.0",
"derivative",
"env_logger",
"env_logger 0.11.6",
"futures-util",
"hex",
"hmac",
@ -4724,12 +4742,6 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
[[package]]
name = "multimap"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1a5d38b9b352dbd913288736af36af41c48d61b1a8cd34bcecd727561b7d511"
[[package]]
name = "multimap"
version = "0.10.0"
@ -6064,7 +6076,7 @@ dependencies = [
"heck 0.5.0",
"itertools 0.13.0",
"log",
"multimap 0.9.1",
"multimap 0.10.0",
"once_cell",
"petgraph 0.6.5",
"prettyplease 0.2.30",

View file

@ -39,6 +39,8 @@ futures = "0.3"
futures-util = "0.3" # Add futures-util here
parking_lot = "0.12"
bytes = "1.0"
log = "0.4"
env_logger = "0.10"
# Web framework and servers
axum = { version = "0.7.9", features = ["ws", "multipart"] }

View file

@ -47,6 +47,7 @@ axum-extra = { version = "0.7" } # Add headers feature
tower = "0.4"
tower-http = { version = "0.5", features = ["auth", "cors", "trace"] }
headers = "0.3"
tokio-stream = { workspace = true }
[dev-dependencies]
rstest = "0.18"

View file

@ -19,18 +19,16 @@ pub struct CoreError(pub String);
pub enum CustomerStatus {
Active,
Inactive,
Suspended
Suspended,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SubscriptionTier {
Free,
Pro,
Enterprise
Enterprise,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Instance {
pub id: Uuid,
@ -118,7 +116,7 @@ impl FromStr for UserStatus {
"active" => Ok(UserStatus::Active),
"inactive" => Ok(UserStatus::Inactive),
"suspended" => Ok(UserStatus::Suspended),
_ => Ok(UserStatus::Inactive)
_ => Ok(UserStatus::Inactive),
}
}
}
@ -146,8 +144,6 @@ pub struct User {
pub created_at: DateTime<Utc>,
}
// Update the Customer struct to include these fields
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Customer {
@ -155,16 +151,16 @@ pub struct Customer {
pub name: String,
pub max_instances: u32,
pub email: String,
pub status: CustomerStatus, // Add this field
pub subscription_tier: SubscriptionTier, // Add this field
pub status: CustomerStatus, // Add this field
pub subscription_tier: SubscriptionTier, // Add this field
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
impl Customer {
pub fn new(
name: String,
email: String,
name: String,
email: String,
subscription_tier: SubscriptionTier,
max_instances: u32,
) -> Self {
@ -174,7 +170,7 @@ impl Customer {
email,
max_instances,
subscription_tier,
status: CustomerStatus::Active, // Default to Active
status: CustomerStatus::Active, // Default to Active
created_at: Utc::now(),
updated_at: Utc::now(),
}
@ -238,15 +234,17 @@ pub struct FileInfo {
pub created_at: DateTime<Utc>,
}
// App state shared across all handlers
// App state shared across all handlers
pub struct AppState {
pub config: AppConfig,
pub db_pool: PgPool,
pub redis_pool: RedisConnectionManager,
pub kafka_producer: FutureProducer,
// pub zitadel_client: AuthServiceClient<tonic::transport::Channel>,
pub minio_client: MinioClient,
pub minio_client: Option<MinioClient>,
pub config: Option<AppConfig>,
pub db_pool: Option<PgPool>,
pub redis_pool: Option<RedisConnectionManager>,
pub kafka_producer: Option<FutureProducer>,
//pub zitadel_client: Option<AuthServiceClient><tonic::transport::Channel>,
}
// File models
@ -288,7 +286,7 @@ pub struct Conversation {
pub struct ConversationMember {
pub conversation_id: Uuid,
pub user_id: Uuid,
pub joined_at: DateTime<Utc>
pub joined_at: DateTime<Utc>,
}
// Calendar models
@ -348,36 +346,33 @@ pub struct ApiResponse<T> {
pub enum AppError {
#[error("Database error: {0}")]
Database(#[from] sqlx::Error),
#[error("Redis error: {0}")]
Redis(#[from] redis::RedisError),
#[error("Kafka error: {0}")]
Kafka(String),
#[error("Zitadel error: {0}")]
Zitadel(#[from] tonic::Status),
#[error("Minio error: {0}")]
Minio(String),
#[error("Validation error: {0}")]
Validation(String),
#[error("Not found: {0}")]
NotFound(String),
#[error("Unauthorized: {0}")]
Unauthorized(String),
#[error("Forbidden: {0}")]
Forbidden(String),
#[error("Internal server error: {0}")]
Internal(String),
}
impl actix_web::ResponseError for AppError {
@ -385,7 +380,9 @@ impl actix_web::ResponseError for AppError {
let (status, error_message) = match self {
AppError::Validation(_) => (actix_web::http::StatusCode::BAD_REQUEST, self.to_string()),
AppError::NotFound(_) => (actix_web::http::StatusCode::NOT_FOUND, self.to_string()),
AppError::Unauthorized(_) => (actix_web::http::StatusCode::UNAUTHORIZED, self.to_string()),
AppError::Unauthorized(_) => {
(actix_web::http::StatusCode::UNAUTHORIZED, self.to_string())
}
AppError::Forbidden(_) => (actix_web::http::StatusCode::FORBIDDEN, self.to_string()),
_ => (
actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,

View file

@ -22,6 +22,9 @@ actix-web ={ workspace = true }
actix-multipart ={ workspace = true }
sanitize-filename = { workspace = true }
tempfile = { workspace = true }
log = { workspace = true }
env_logger = { workspace = true }
tokio-stream = { workspace = true }
[dev-dependencies]
rstest= { workspace = true }

View file

@ -1,13 +1,15 @@
use actix_multipart::Multipart;
use futures::TryStreamExt;
use gb_core::models::AppState;
use std::io::Write;
use gb_core::models::AppError;
use gb_core::utils::{create_response, extract_user_id};
use actix_web::{post, web, HttpRequest, HttpResponse};
use tempfile::NamedTempFile;
use gb_core::models::AppError;
use gb_core::models::AppState;
use gb_core::utils::{create_response, extract_user_id};
use minio::s3::builders::ObjectContent;
use minio::s3::Client;
use std::io::Write;
use tempfile::NamedTempFile;
use minio::s3::types::ToStream;
use tokio_stream::StreamExt;
#[post("/files/upload/{folder_path}")]
pub async fn upload_file(
@ -17,24 +19,29 @@ pub async fn upload_file(
) -> Result<HttpResponse, actix_web::Error> {
let folder_path = folder_path.into_inner();
// Create a temporary file to store the uploaded file
// Create a temporary file to store the uploaded file.
let mut temp_file = NamedTempFile::new().map_err(|e| {
actix_web::error::ErrorInternalServerError(format!("Failed to create temp file: {}", e))
})?;
let mut file_name = None;
// Iterate over the multipart stream
// Iterate over the multipart stream.
while let Some(mut field) = payload.try_next().await? {
let content_disposition = field.content_disposition();
file_name = content_disposition
.get_filename()
.map(|name| name.to_string());
// Write the file content to the temporary file
// Write the file content to the temporary file.
while let Some(chunk) = field.try_next().await? {
temp_file.write_all(&chunk).map_err(|e| {
actix_web::error::ErrorInternalServerError(format!("Failed to write to temp file: {}", e))
actix_web::error::ErrorInternalServerError(format!(
"Failed to write to temp file: {}",
e
))
})?;
}
}
@ -46,7 +53,7 @@ pub async fn upload_file(
let object_name = format!("{}/{}", folder_path, file_name);
// Upload the file to the MinIO bucket
let client: Client = state.minio_client.clone();
let client: Client = state.minio_client.clone().unwrap();
let bucket_name = "file-upload-rust-bucket";
let content = ObjectContent::from(temp_file.path());
@ -79,10 +86,46 @@ pub async fn delete_file(
_file_path: web::Json<String>,
) -> Result<HttpResponse, AppError> {
let _user_id = extract_user_id(&req)?;
Ok(create_response(
true,
Some("File deleted successfully".to_string()),
))
}
#[post("/files/list/{folder_path}")]
pub async fn list_file(
folder_path: web::Path<String>,
state: web::Data<AppState>,
) -> Result<HttpResponse, actix_web::Error> {
let folder_path = folder_path.into_inner();
let client: Client = state.minio_client.clone().unwrap();
let bucket_name = "file-upload-rust-bucket";
// Create the stream using the to_stream() method
let mut objects_stream = client
.list_objects(bucket_name)
.prefix(Some(folder_path))
.to_stream()
.await;
let mut file_list = Vec::new();
// Use StreamExt::next() to iterate through the stream
while let Some(items) = objects_stream.next().await {
match items {
Ok(result) => {
for item in result.contents {
file_list.push(item.name);
}
},
Err(e) => {
return Err(actix_web::error::ErrorInternalServerError(
format!("Failed to list files in MinIO: {}", e)
));
}
}
}
Ok(HttpResponse::Ok().json(file_list))
}

View file

@ -26,11 +26,11 @@ async fn main() -> std::io::Result<()> {
let minio_client = init_minio(&config).await.expect("Failed to initialize Minio");
let app_state = web::Data::new(models::AppState {
config: config.clone(),
db_pool,
redis_pool,
kafka_producer,
minio_client,
config: Some(config.clone()),
db_pool: Some(db_pool),
redis_pool: Some(redis_pool),
kafka_producer: Some(kafka_producer),
minio_client: Some(minio_client),
});
// Start HTTP server

View file

@ -27,6 +27,7 @@ criterion = { workspace = true, features = ["async_futures"] }
# Async Runtime
tokio = { workspace = true }
tokio-stream= { workspace = true }
async-trait = { workspace = true }
# HTTP Client

View file

@ -1,12 +1,11 @@
pub struct ChaosTest {
namespace: String,
}
impl ChaosTest {
pub async fn new(namespace: String) -> anyhow::Result<Self> {
// Initialize the ChaosTest struct
Ok(ChaosTest { namespace })
Ok(ChaosTest { })
}
pub async fn network_partition(&self) -> anyhow::Result<()> {

View file

@ -1,14 +1,10 @@
use actix_web::{test, web, App};
use anyhow::Result;
use async_trait::async_trait;
use bytes::Bytes;
use gb_core::models::AppState;
use gb_file::handlers::upload_file;
use gb_testing::integration::{IntegrationTest, IntegrationTestCase};
use minio::s3::args::{
BucketExistsArgs, GetObjectArgs, MakeBucketArgs, RemoveObjectArgs, StatObjectArgs,
};
use minio::s3::client::{Client as MinioClient, ClientBuilder as MinioClientBuilder};
use minio::s3::args::{BucketExistsArgs, GetObjectArgs, MakeBucketArgs, StatObjectArgs};
use minio::s3::client::ClientBuilder as MinioClientBuilder;
use minio::s3::creds::StaticProvider;
use minio::s3::http::BaseUrl;
use std::fs::File;
@ -17,13 +13,10 @@ use std::io::Write;
use std::str::FromStr;
use tempfile::NamedTempFile;
#[tokio::test]
async fn test_successful_file_upload() -> Result<()> {
// Setup test environment and MinIO client
let base_url = format!("https://{}", "localhost:9000");
let base_url = format!("http://{}", "localhost:9000");
let base_url = BaseUrl::from_str(&base_url)?;
let credentials = StaticProvider::new(&"minioadmin", &"minioadmin", None);
@ -45,14 +38,16 @@ async fn test_successful_file_upload() -> Result<()> {
}
let app_state = web::Data::new(AppState {
minio_client,
config: todo!(),
db_pool: todo!(),
redis_pool: todo!(),
kafka_producer: todo!(),
minio_client: Some(minio_client.clone()),
config: None,
db_pool: None,
kafka_producer: None,
redis_pool: None,
});
let app = test::init_service(App::new().app_data(app_state.clone()).service(upload_file)).await;
let app =
test::init_service(App::new().app_data(app_state.clone())
.service(upload_file)).await;
// Create a test file with content
let mut temp_file = NamedTempFile::new()?;
@ -91,17 +86,13 @@ async fn test_successful_file_upload() -> Result<()> {
// Using object-based API for stat_object
let stat_object_args = StatObjectArgs::new(bucket_name, object_name)?;
let object_exists =
minio_client
.stat_object(&stat_object_args)
.await
.is_ok();
let object_exists = minio_client.clone().stat_object(&stat_object_args).await.is_ok();
assert!(object_exists, "Uploaded file should exist in MinIO");
// Verify file content using object-based API
let get_object_args = GetObjectArgs::new(bucket_name, object_name)?;
let get_object_result = minio_client.get_object(bucket_name, object_name);
// let get_object_args = GetObjectArgs::new(bucket_name, object_name)?;
// let get_object_result = minio_client.get_object(bucket_name, object_name);
// let mut object_content = Vec::new();
// get_object_result.read_to_end(&mut object_content)?;

View file

@ -0,0 +1,110 @@
use actix_web::{test, web, App};
use anyhow::Result;
use bytes::Bytes;
use gb_core::models::AppState;
use gb_file::handlers::list_file;
use minio::s3::args::{BucketExistsArgs, MakeBucketArgs};
use minio::s3::builders::SegmentedBytes;
use minio::s3::client::ClientBuilder as MinioClientBuilder;
use minio::s3::creds::StaticProvider;
use minio::s3::http::BaseUrl;
use minio::s3::types::ToStream;
use std::fs::File;
use std::io::Read;
use std::io::Write;
use std::str::FromStr;
use tempfile::NamedTempFile;
use tokio_stream::StreamExt;
#[tokio::test]
async fn test_successful_file_listing() -> Result<(), Box<dyn std::error::Error>> {
// Setup test environment and MinIO client
let base_url = format!("http://{}", "localhost:9000");
let base_url = BaseUrl::from_str(&base_url)?;
let credentials = StaticProvider::new("minioadmin", "minioadmin", None);
let minio_client = MinioClientBuilder::new(base_url.clone())
.provider(Some(Box::new(credentials)))
.build()?;
// Create test bucket if it doesn't exist
let bucket_name = "file-upload-rust-bucket";
// Using object-based API for bucket_exists
let bucket_exists_args = BucketExistsArgs::new(bucket_name)?;
let bucket_exists = minio_client.bucket_exists(&bucket_exists_args).await?;
if !bucket_exists {
// Using object-based API for make_bucket
let make_bucket_args = MakeBucketArgs::new(bucket_name)?;
minio_client.make_bucket(&make_bucket_args).await?;
}
// Put a single file in the bucket
let folder_path = "test-folder";
let file_name = "test.txt";
let object_name = format!("{}/{}", folder_path, file_name);
// Create a temporary file with some content
let mut temp_file = NamedTempFile::new()?;
writeln!(temp_file, "This is a test file.")?;
// Upload the file to the bucket
let mut file = File::open(temp_file.path())?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
let content = SegmentedBytes::from(Bytes::from(buffer));
minio_client.put_object(bucket_name, &object_name, content);
let app_state = web::Data::new(AppState {
minio_client: Some(minio_client.clone()),
config: None,
db_pool: None,
kafka_producer: None,
redis_pool: None,
});
let app = test::init_service(App::new().app_data(app_state.clone()).service(list_file)).await;
// Execute request to list files in the folder
let req = test::TestRequest::post()
.uri(&format!("/files/list/{}", folder_path))
.to_request();
let resp = test::call_service(&app, req).await;
// Verify response
assert_eq!(resp.status(), 200);
// Parse the response body as JSON
let body = test::read_body(resp).await;
let file_list: Vec<String> = serde_json::from_slice(&body)?;
// Verify the uploaded file is in the list
assert!(
file_list.contains(&object_name),
"Uploaded file should be listed"
);
// List all objects in a directory.
let mut list_objects = minio_client
.list_objects("my-bucket")
.use_api_v1(true)
.recursive(true)
.to_stream()
.await;
while let Some(result) = list_objects.next().await {
match result {
Ok(resp) => {
for item in resp.contents {
println!("{:?}", item);
}
}
Err(e) => println!("Error: {:?}", e),
}
}
Ok(())
}

View file

@ -1,9 +1,9 @@
use gb_testing::load::{LoadTest, LoadTestConfig};
use gb_testing::load::LoadTestConfig;
use std::time::Duration;
#[tokio::test]
async fn test_auth_load() -> anyhow::Result<()> {
let config = LoadTestConfig {
let _config = LoadTestConfig {
users: 100,
duration: Duration::from_secs(300),
ramp_up: Duration::from_secs(60),