use std::env;
use std::process::{Command, Stdio};
use std::thread;
use std::time::Duration;
use ::mongodb::bson::{doc, Document};
use ::mongodb::Client;
use anyhow::Result;
use arcula::config::{Environment, MongoConfig};
use arcula::core::sync::{SyncConfig, SyncOptions};
use arcula::utils::mongodb;
fn get_container_ip(container_name: &str) -> Result<String> {
let output = Command::new("docker")
.args([
"inspect",
"-f",
"{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}",
container_name,
])
.output()?;
if !output.status.success() {
return Err(anyhow::anyhow!("Failed to get container IP address"));
}
let ip = String::from_utf8(output.stdout)?.trim().to_string();
if ip.is_empty() {
return Err(anyhow::anyhow!("Container IP address not found"));
}
Ok(ip)
}
fn generate_container_names() -> (String, String) {
let suffix = format!(
"{}_{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
rand::random::<u16>()
);
(
format!("mongo_importer_test_source_{}", suffix),
format!("mongo_importer_test_target_{}", suffix),
)
}
const ENV_MONGO_SOURCE_URI: &str = "TEST_MONGO_SOURCE_URI";
const ENV_MONGO_TARGET_URI: &str = "TEST_MONGO_TARGET_URI";
fn setup_mongodb_containers() -> Result<((String, String), (String, String))> {
let docker_check = Command::new("docker").arg("--version").output()?;
if !docker_check.status.success() {
eprintln!("Docker is not available.");
return Err(anyhow::anyhow!("Docker is not available"));
}
let (container_name1, container_name2) = generate_container_names();
let start_source = Command::new("docker")
.args([
"run",
"--rm",
"-d",
"--name",
&container_name1,
"mongo:latest",
])
.stdout(Stdio::null())
.status()?;
if !start_source.success() {
return Err(anyhow::anyhow!("Failed to start source MongoDB container"));
}
let start_target = Command::new("docker")
.args([
"run",
"--rm",
"-d",
"--name",
&container_name2,
"mongo:latest",
])
.stdout(Stdio::null())
.status()?;
if !start_target.success() {
let _ = Command::new("docker")
.args(["rm", "-f", &container_name1])
.stdout(Stdio::null())
.status();
return Err(anyhow::anyhow!("Failed to start target MongoDB container"));
}
println!("Waiting for MongoDB containers to be ready...");
thread::sleep(Duration::from_secs(5));
let ip1 = get_container_ip(&container_name1)?;
let ip2 = get_container_ip(&container_name2)?;
println!("MongoDB containers running at IPs {} and {}", ip1, ip2);
Ok(((container_name1, container_name2), (ip1, ip2)))
}
fn teardown_mongodb_containers(container_names: &(String, String)) -> Result<()> {
let _ = Command::new("docker")
.args(["rm", "-f", &container_names.0])
.stdout(Stdio::null())
.status();
let _ = Command::new("docker")
.args(["rm", "-f", &container_names.1])
.stdout(Stdio::null())
.status();
Ok(())
}
fn get_test_configs(ips: Option<(String, String)>) -> (MongoConfig, MongoConfig) {
let source_uri = env::var(ENV_MONGO_SOURCE_URI).unwrap_or_else(|_| {
let ip = ips
.as_ref()
.map(|(ip, _)| ip.clone())
.unwrap_or_else(|| "localhost".to_string());
format!("mongodb://{}:27017", ip)
});
let target_uri = env::var(ENV_MONGO_TARGET_URI).unwrap_or_else(|_| {
let ip = ips
.as_ref()
.map(|(_, ip)| ip.clone())
.unwrap_or_else(|| "localhost".to_string());
format!("mongodb://{}:27017", ip)
});
let source_config = MongoConfig {
connection_string: source_uri,
environment: Environment::new("TEST_SOURCE"),
};
let target_config = MongoConfig {
connection_string: target_uri,
environment: Environment::new("TEST_TARGET"),
};
(source_config, target_config)
}
async fn create_test_data(config: &MongoConfig, db_name: &str) -> Result<()> {
let client_options = config.get_client_options().await?;
let client = Client::with_options(client_options)?;
let db = client.database(db_name);
let collection = db.collection::<Document>("test_collection");
for i in 0..10 {
let doc = doc! {
"test_field": format!("test_value_{}", i),
"test_number": i
};
collection.insert_one(doc).await?;
}
Ok(())
}
async fn verify_synced_data(config: &MongoConfig, db_name: &str) -> Result<bool> {
let client_options = config.get_client_options().await?;
let client = Client::with_options(client_options)?;
let db = client.database(db_name);
let collection = db.collection::<Document>("test_collection");
let count = collection.count_documents(doc! {}).await?;
Ok(count == 10)
}
#[tokio::test]
async fn test_mongodb_connection() -> Result<()> {
let external_mongo =
env::var(ENV_MONGO_SOURCE_URI).is_ok() && env::var(ENV_MONGO_TARGET_URI).is_ok();
let mut container_info = None;
if !external_mongo {
match setup_mongodb_containers() {
Ok((container_names, ips)) => {
container_info = Some((container_names, ips));
}
Err(e) => {
eprintln!("Error setting up MongoDB containers: {}", e);
return Err(anyhow::anyhow!(
"Failed to set up MongoDB containers: {}",
e
));
}
}
}
let (source_config, target_config) =
get_test_configs(container_info.as_ref().map(|(_, ips)| ips.clone()));
let source_dbs = mongodb::list_databases(&source_config).await?;
let target_dbs = mongodb::list_databases(&target_config).await?;
println!("Source DBs: {:?}", source_dbs);
println!("Target DBs: {:?}", target_dbs);
assert!(source_dbs.contains(&"admin".to_string()));
assert!(target_dbs.contains(&"admin".to_string()));
if !external_mongo {
if let Some((container_names, _)) = container_info {
teardown_mongodb_containers(&container_names)?;
}
}
Ok(())
}
#[tokio::test]
async fn test_export_import() -> Result<()> {
let external_mongo =
env::var(ENV_MONGO_SOURCE_URI).is_ok() && env::var(ENV_MONGO_TARGET_URI).is_ok();
let mut container_info = None;
if !external_mongo {
match setup_mongodb_containers() {
Ok((container_names, ips)) => {
container_info = Some((container_names, ips));
}
Err(e) => {
eprintln!("Error setting up MongoDB containers: {}", e);
return Err(anyhow::anyhow!(
"Failed to set up MongoDB containers: {}",
e
));
}
}
}
let (source_config, target_config) =
get_test_configs(container_info.as_ref().map(|(_, ips)| ips.clone()));
let test_db = "test_db";
create_test_data(&source_config, test_db).await?;
let temp_dir = tempfile::tempdir()?;
let temp_path = temp_dir.path();
let export_result = mongodb::export_database(&source_config, test_db, temp_path).await;
assert!(export_result.is_ok());
let import_result =
mongodb::import_database(&target_config, test_db, temp_path, true, false).await;
assert!(import_result.is_ok());
let verification = verify_synced_data(&target_config, test_db).await?;
assert!(verification);
if !external_mongo {
if let Some((container_names, _)) = container_info {
teardown_mongodb_containers(&container_names)?;
}
}
Ok(())
}
#[tokio::test]
async fn test_backup_restore() -> Result<()> {
let external_mongo =
env::var(ENV_MONGO_SOURCE_URI).is_ok() && env::var(ENV_MONGO_TARGET_URI).is_ok();
let mut container_info = None;
if !external_mongo {
match setup_mongodb_containers() {
Ok((container_names, ips)) => {
container_info = Some((container_names, ips));
}
Err(e) => {
eprintln!("Error setting up MongoDB containers: {}", e);
return Err(anyhow::anyhow!(
"Failed to set up MongoDB containers: {}",
e
));
}
}
}
let (source_config, _) = get_test_configs(container_info.as_ref().map(|(_, ips)| ips.clone()));
let test_db = "backup_test_db";
create_test_data(&source_config, test_db).await?;
let backup_result = mongodb::create_backup(&source_config, test_db).await;
assert!(backup_result.is_ok());
let backup_path = backup_result.unwrap();
let client_options = source_config.get_client_options().await?;
let client = Client::with_options(client_options)?;
client.database(test_db).drop().await?;
let restore_result = mongodb::restore_backup(&source_config, test_db, &backup_path).await;
assert!(restore_result.is_ok());
let verification = verify_synced_data(&source_config, test_db).await?;
assert!(verification);
if !external_mongo {
if let Some((container_names, _)) = container_info {
teardown_mongodb_containers(&container_names)?;
}
}
Ok(())
}
#[tokio::test]
async fn test_full_sync_operation() -> Result<()> {
let external_mongo =
env::var(ENV_MONGO_SOURCE_URI).is_ok() && env::var(ENV_MONGO_TARGET_URI).is_ok();
let mut container_info = None;
if !external_mongo {
match setup_mongodb_containers() {
Ok((container_names, ips)) => {
container_info = Some((container_names, ips));
}
Err(e) => {
eprintln!("Error setting up MongoDB containers: {}", e);
return Err(anyhow::anyhow!(
"Failed to set up MongoDB containers: {}",
e
));
}
}
}
let (source_config, target_config) =
get_test_configs(container_info.as_ref().map(|(_, ips)| ips.clone()));
let source_db = "sync_source_db";
let target_db = "sync_target_db";
create_test_data(&source_config, source_db).await?;
let sync_config = SyncConfig {
source_env: source_config.environment.clone(),
target_env: target_config.environment.clone(),
source_db: source_db.to_string(),
target_db: target_db.to_string(),
options: SyncOptions {
create_backup: true,
drop_collections: true,
clear_collections: false,
},
};
env::set_var("MONGO_TEST_SOURCE_URI", &source_config.connection_string);
env::set_var("MONGO_TEST_TARGET_URI", &target_config.connection_string);
let sync_result = arcula::core::sync::perform_sync(sync_config).await;
assert!(sync_result.is_ok());
let verification = verify_synced_data(&target_config, target_db).await?;
assert!(verification);
env::remove_var("MONGO_TEST_SOURCE_URI");
env::remove_var("MONGO_TEST_TARGET_URI");
if !external_mongo {
if let Some((container_names, _)) = container_info {
teardown_mongodb_containers(&container_names)?;
}
}
Ok(())
}