From c24102054249988a5f269adfb439613b9f9d5cb1 Mon Sep 17 00:00:00 2001 From: Charles GTE Date: Thu, 17 Sep 2026 10:13:58 +0200 Subject: [PATCH 1/3] fix: mongodb options --- docker-compose.databases.yml | 2 +- src/domain/mod.rs | 2 +- src/domain/mongodb/connection.rs | 104 ++++++++++--------------------- src/domain/mongodb/mod.rs | 2 +- src/tests/domain/mongodb.rs | 99 +++++++++++++++++++++++++++++ 5 files changed, 135 insertions(+), 74 deletions(-) diff --git a/docker-compose.databases.yml b/docker-compose.databases.yml index 9f13c3c..4ce9f30 100644 --- a/docker-compose.databases.yml +++ b/docker-compose.databases.yml @@ -75,7 +75,7 @@ services: volumes: - mongodb-data-auth:/data/db healthcheck: - test: [ "CMD", "mongo", "--eval", "db.adminCommand('ping')" ] + test: [ "CMD", "mongosh", "--eval", "db.adminCommand('ping')" ] interval: 5s timeout: 5s retries: 10 diff --git a/src/domain/mod.rs b/src/domain/mod.rs index 8532784..095cbe1 100644 --- a/src/domain/mod.rs +++ b/src/domain/mod.rs @@ -1,6 +1,6 @@ pub mod docker_volume; pub mod factory; -mod mongodb; +pub mod mongodb; pub mod mysql; pub mod postgres; mod redis; diff --git a/src/domain/mongodb/connection.rs b/src/domain/mongodb/connection.rs index d33efed..86d4e94 100644 --- a/src/domain/mongodb/connection.rs +++ b/src/domain/mongodb/connection.rs @@ -1,7 +1,7 @@ use crate::services::config::DatabaseConfig; use anyhow::Result; use mongodb::Client; -use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC}; +use percent_encoding::{AsciiSet, NON_ALPHANUMERIC, utf8_percent_encode}; const USERINFO_ENCODE: &AsciiSet = &NON_ALPHANUMERIC .remove(b'-') @@ -27,7 +27,8 @@ pub fn get_mongo_uri(cfg: DatabaseConfig) -> Result { } pub fn build_mongo_uri(cfg: &DatabaseConfig, include_db: bool) -> String { - let is_srv = cfg.port == 0; + let is_multi_host = cfg.host.contains(','); + let is_srv = cfg.port == 0 && !is_multi_host; let scheme = if is_srv { "mongodb+srv" } else { "mongodb" }; let has_auth = !cfg.username.is_empty() && !cfg.password.is_empty(); @@ -41,7 +42,7 @@ pub fn build_mongo_uri(cfg: &DatabaseConfig, include_db: bool) -> String { String::new() }; - let authority = if is_srv { + let authority = if is_srv || is_multi_host { cfg.host.clone() } else { format!("{}:{}", cfg.host, cfg.port) @@ -53,7 +54,35 @@ pub fn build_mongo_uri(cfg: &DatabaseConfig, include_db: bool) -> String { "/".to_string() }; - let query = if has_auth { "?authSource=admin" } else { "" }; + let mut params: Vec = Vec::new(); + + match cfg.options.get("auth_source").and_then(|v| v.as_str()) { + Some(s) if !s.is_empty() => params.push(format!( + "authSource={}", + utf8_percent_encode(s, USERINFO_ENCODE) + )), + _ if has_auth => params.push("authSource=admin".to_string()), + _ => {} + } + + if let Some(rs) = cfg.options.get("replica_set").and_then(|v| v.as_str()) { + if !rs.is_empty() { + params.push(format!( + "replicaSet={}", + utf8_percent_encode(rs, USERINFO_ENCODE) + )); + } + } + + if cfg.options.get("tls").and_then(|v| v.as_bool()) == Some(true) { + params.push("tls=true".to_string()); + } + + let query = if params.is_empty() { + String::new() + } else { + format!("?{}", params.join("&")) + }; format!("{}://{}{}{}{}", scheme, credentials, authority, path, query) } @@ -71,70 +100,3 @@ pub fn extract_db_name(dry_output: &str) -> Option { } dbs.into_iter().next() } - -#[cfg(test)] -mod tests { - use super::*; - use crate::services::config::{DatabaseConfig, DbType}; - use std::collections::HashMap; - - fn cfg(host: &str, port: u16, user: &str, pass: &str) -> DatabaseConfig { - DatabaseConfig { - name: "t".into(), - database: "mydb".into(), - db_type: DbType::MongoDB, - username: user.into(), - password: pass.into(), - port, - host: host.into(), - generated_id: "id".into(), - path: String::new(), - max_packet_size: String::new(), - volume_name: String::new(), - container_name: None, - options: HashMap::new(), - } - } - - #[test] - fn standard_with_auth() { - let c = cfg("localhost", 27017, "user", "pass"); - assert_eq!( - build_mongo_uri(&c, true), - "mongodb://user:pass@localhost:27017/mydb?authSource=admin" - ); - } - - #[test] - fn standard_no_auth() { - let c = cfg("localhost", 27017, "", ""); - assert_eq!(build_mongo_uri(&c, true), "mongodb://localhost:27017/mydb"); - } - - #[test] - fn srv_with_auth() { - let c = cfg("cluster.example.mongodb.net", 0, "user", "pass"); - assert_eq!( - build_mongo_uri(&c, true), - "mongodb+srv://user:pass@cluster.example.mongodb.net/mydb?authSource=admin" - ); - } - - #[test] - fn srv_no_db_for_dryrun() { - let c = cfg("cluster.example.mongodb.net", 0, "user", "pass"); - assert_eq!( - build_mongo_uri(&c, false), - "mongodb+srv://user:pass@cluster.example.mongodb.net/?authSource=admin" - ); - } - - #[test] - fn encodes_special_chars_in_credentials() { - let c = cfg("cluster.example.mongodb.net", 0, "user", "p@ss:w/rd?"); - assert_eq!( - build_mongo_uri(&c, true), - "mongodb+srv://user:p%40ss%3Aw%2Frd%3F@cluster.example.mongodb.net/mydb?authSource=admin" - ); - } -} diff --git a/src/domain/mongodb/mod.rs b/src/domain/mongodb/mod.rs index a4869e1..d4b126d 100644 --- a/src/domain/mongodb/mod.rs +++ b/src/domain/mongodb/mod.rs @@ -1,5 +1,5 @@ mod backup; -mod connection; +pub mod connection; pub mod database; mod ping; mod restore; diff --git a/src/tests/domain/mongodb.rs b/src/tests/domain/mongodb.rs index c918ba9..61ae743 100644 --- a/src/tests/domain/mongodb.rs +++ b/src/tests/domain/mongodb.rs @@ -100,3 +100,102 @@ async fn mongodb_backup_restore_test() { } } } + +use crate::domain::mongodb::connection::build_mongo_uri; + +fn uri_cfg(host: &str, port: u16, user: &str, pass: &str) -> DatabaseConfig { + DatabaseConfig { + name: "t".into(), + database: "mydb".into(), + db_type: DbType::MongoDB, + username: user.into(), + password: pass.into(), + port, + host: host.into(), + generated_id: "id".into(), + path: String::new(), + max_packet_size: String::new(), + volume_name: String::new(), + container_name: None, + options: std::collections::HashMap::new(), + } +} + +#[test] +fn uri_standard_with_auth() { + let c = uri_cfg("localhost", 27017, "user", "pass"); + assert_eq!( + build_mongo_uri(&c, true), + "mongodb://user:pass@localhost:27017/mydb?authSource=admin" + ); +} + +#[test] +fn uri_standard_no_auth() { + let c = uri_cfg("localhost", 27017, "", ""); + assert_eq!(build_mongo_uri(&c, true), "mongodb://localhost:27017/mydb"); +} + +#[test] +fn uri_srv_with_auth() { + let c = uri_cfg("cluster.example.mongodb.net", 0, "user", "pass"); + assert_eq!( + build_mongo_uri(&c, true), + "mongodb+srv://user:pass@cluster.example.mongodb.net/mydb?authSource=admin" + ); +} + +#[test] +fn uri_srv_no_db_for_dryrun() { + let c = uri_cfg("cluster.example.mongodb.net", 0, "user", "pass"); + assert_eq!( + build_mongo_uri(&c, false), + "mongodb+srv://user:pass@cluster.example.mongodb.net/?authSource=admin" + ); +} + +#[test] +fn uri_options_authsource_replicaset_tls() { + let mut c = uri_cfg("localhost", 27017, "user", "pass"); + c.options.insert("auth_source".into(), "myauthdb".into()); + c.options.insert("replica_set".into(), "rs0".into()); + c.options.insert("tls".into(), serde_json::Value::Bool(true)); + assert_eq!( + build_mongo_uri(&c, true), + "mongodb://user:pass@localhost:27017/mydb?authSource=myauthdb&replicaSet=rs0&tls=true" + ); +} + +#[test] +fn uri_multi_host_replica_set() { + let mut c = uri_cfg( + "mongodb0.example.internal:27017,mongodb1.example.internal:27017,mongodb2.example.internal:27017", + 0, + "myDatabaseUser", + "D1fficultP@ssw0rd", + ); + c.database = "myDB".into(); + c.options.insert("replica_set".into(), "myRepl".into()); + assert_eq!( + build_mongo_uri(&c, true), + "mongodb://myDatabaseUser:D1fficultP%40ssw0rd@mongodb0.example.internal:27017,mongodb1.example.internal:27017,mongodb2.example.internal:27017/myDB?authSource=admin&replicaSet=myRepl" + ); +} + +#[test] +fn uri_default_authsource_when_auth() { + let c = uri_cfg("localhost", 27017, "user", "pass"); + assert_eq!( + build_mongo_uri(&c, true), + "mongodb://user:pass@localhost:27017/mydb?authSource=admin" + ); +} + +#[test] +fn uri_encodes_special_chars_in_credentials() { + let c = uri_cfg("cluster.example.mongodb.net", 0, "user", "p@ss:w/rd?"); + assert_eq!( + build_mongo_uri(&c, true), + "mongodb+srv://user:p%40ss%3Aw%2Frd%3F@cluster.example.mongodb.net/mydb?authSource=admin" + ); +} From c5806a6ea4ff28de53ec3204eea61e6232ebb100 Mon Sep 17 00:00:00 2001 From: charles-gauthereau Date: Thu, 17 Sep 2026 10:47:32 +0200 Subject: [PATCH 2/3] fix: refactoring --- .gitignore | 2 +- docker-compose.databases.yml | 4 ++-- docker-compose.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index e16c7f5..675c67b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,6 @@ .env .claude - /docs + .superpowers diff --git a/docker-compose.databases.yml b/docker-compose.databases.yml index 4ce9f30..eecb556 100644 --- a/docker-compose.databases.yml +++ b/docker-compose.databases.yml @@ -62,7 +62,7 @@ services: db-mongodb-auth: container_name: db-mongodb-auth - image: mongo:latest + image: mongo:8.0.4 ports: - "27082:27017" environment: @@ -82,7 +82,7 @@ services: db-mongodb: container_name: db-mongodb - image: mongo:latest + image: mongo:8.0.4 ports: - "27083:27017" volumes: diff --git a/docker-compose.yml b/docker-compose.yml index 699ed22..654f73e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,7 +21,7 @@ services: LOG: debug TZ: "Europe/Paris" # TMPDIR: /scratch - EDGE_KEY: "eyJzZXJ2ZXJVcmwiOiJodHRwOi8vbG9jYWxob3N0Ojg4ODciLCJhZ2VudElkIjoiYTQyNjQzNTQtZGE3Ni00OWFkLWJkYjctZDVjMjMwYzhjYmViIiwibWFzdGVyS2V5QjY0IjoiQlhWM1hvbEM2NTZTVjdkTmdjV1BHUWxrKytycExJNmxHRGk3Q1BCNWllbz0ifQ==" + EDGE_KEY: "eyJzZXJ2ZXJVcmwiOiJodHRwOi8vbG9jYWxob3N0Ojg4ODciLCJhZ2VudElkIjoiZDQwYzY4YWQtMmJhNS00NmMzLTg5MTMtMWI0OTk5MmRmNzBiIiwibWFzdGVyS2V5QjY0IjoiMUh0djdtWCtYVkJxL0IzUEV2WDlZZjlQeUdVZW5oRHlXemo5THRqNW90WT0ifQ==" #CHUNK_SIZE_MB: "1" #POOLING: 1 #RETRY_ATTEMPTS: 3 From 6f8a5c42283d0ba41ed6ffe3ce72f219a2de3096 Mon Sep 17 00:00:00 2001 From: Charles GTE Date: Thu, 17 Sep 2026 11:20:16 +0200 Subject: [PATCH 3/3] fix: mongodb options --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 654f73e..699ed22 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,7 +21,7 @@ services: LOG: debug TZ: "Europe/Paris" # TMPDIR: /scratch - EDGE_KEY: "eyJzZXJ2ZXJVcmwiOiJodHRwOi8vbG9jYWxob3N0Ojg4ODciLCJhZ2VudElkIjoiZDQwYzY4YWQtMmJhNS00NmMzLTg5MTMtMWI0OTk5MmRmNzBiIiwibWFzdGVyS2V5QjY0IjoiMUh0djdtWCtYVkJxL0IzUEV2WDlZZjlQeUdVZW5oRHlXemo5THRqNW90WT0ifQ==" + EDGE_KEY: "eyJzZXJ2ZXJVcmwiOiJodHRwOi8vbG9jYWxob3N0Ojg4ODciLCJhZ2VudElkIjoiYTQyNjQzNTQtZGE3Ni00OWFkLWJkYjctZDVjMjMwYzhjYmViIiwibWFzdGVyS2V5QjY0IjoiQlhWM1hvbEM2NTZTVjdkTmdjV1BHUWxrKytycExJNmxHRGk3Q1BCNWllbz0ifQ==" #CHUNK_SIZE_MB: "1" #POOLING: 1 #RETRY_ATTEMPTS: 3