From a725b881ad2b9a34283907f18959fd236b0d5663 Mon Sep 17 00:00:00 2001 From: Daniel Cardona Rojas Date: Tue, 21 Apr 2026 20:44:03 -0500 Subject: [PATCH 1/2] Databases config --- docs/config.default.toml | 11 + src/cli/handlers.rs | 46 +++- src/config.rs | 154 +++++++++++++ tests/cli_integration.rs | 462 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 665 insertions(+), 8 deletions(-) diff --git a/docs/config.default.toml b/docs/config.default.toml index ae4c7c4..0cf1a73 100644 --- a/docs/config.default.toml +++ b/docs/config.default.toml @@ -80,3 +80,14 @@ enabled = true # Set to null to disable threshold filtering (return all results). # Typical values: 0.3-0.5 for l2/cosine, 0.7-0.9 for ip # threshold = 0.4 + +[databases] +# Additional database paths to include in read-only cross-repo queries. +# The primary database (current repo) is always included. +# This setting is only recognized in .codemark/config.toml (local repo config). +# Supports ~ expansion and relative paths (relative to repo root). +# Additional databases are read-only; write operations affect only the primary DB. +# additional = [ +# "../shared-library/.codemark/codemark.db", +# "~/projects/dependency-repo/.codemark/codemark.db", +# ] diff --git a/src/cli/handlers.rs b/src/cli/handlers.rs index 9a3f231..9c86c87 100644 --- a/src/cli/handlers.rs +++ b/src/cli/handlers.rs @@ -173,19 +173,49 @@ fn load_config(cli: &Cli) -> Config { /// /// Returns (source_label, database) pairs. Falls back to single auto-detected db. /// Returns Error::NotInitialized if any of the specified databases do not exist. +/// +/// Database loading strategy: +/// 1. If CLI `--db` is specified, only those paths are used (override mode) +/// 2. Otherwise, uses auto-detected primary DB + configured additional databases fn open_all_dbs(cli: &Cli) -> Result> { - if cli.db.is_empty() { - let db = open_db(cli)?; - let label = source_label_from_cli(cli); - return Ok(vec![(label, db)]); + // If CLI specified explicit --db paths, use only those (override mode) + if !cli.db.is_empty() { + let mut dbs = Vec::new(); + for path in &cli.db { + if path.exists() { + let label = source_label_from_path(path); + dbs.push((label, Database::open(path)?)); + } + } + return Ok(dbs); } + + // No CLI --db specified: use auto-detected primary + configured additional let mut dbs = Vec::new(); - for path in &cli.db { - if path.exists() { - let label = source_label_from_path(path); - dbs.push((label, Database::open(path)?)); + + // Always include primary DB (auto-detected from git root) + let primary_db = open_db(cli)?; + let primary_label = source_label_from_cli(cli); + dbs.push((primary_label, primary_db)); + + // Load additional DBs from local config + let cwd = std::env::current_dir()?; + if let Some(ctx) = git_context::detect_context(&cwd) { + let codemark_dir = ctx.repo_root.join(".codemark"); + let config = Config::load_layered(&codemark_dir); + let additional_paths = config.databases.resolve_additional_paths(&ctx.repo_root); + + for path in additional_paths { + if path.exists() { + let label = source_label_from_path(&path); + // Only add if not already present (avoid duplicates) + if !dbs.iter().any(|(l, _)| l == &label) { + dbs.push((label, Database::open(&path)?)); + } + } } } + Ok(dbs) } diff --git a/src/config.rs b/src/config.rs index 218cad2..1990255 100644 --- a/src/config.rs +++ b/src/config.rs @@ -62,6 +62,8 @@ pub struct Config { pub semantic: SemanticConfig, #[serde(default)] pub open: OpenConfig, + #[serde(default)] + pub databases: DatabasesConfig, } /// Semantic search configuration wrapper. @@ -146,6 +148,86 @@ impl HealthConfig { } } +/// Additional database paths configuration for cross-repo queries. +/// +/// This configuration is only supported in local config (`.codemark/config.toml`), +/// not in global config. Each repo defines its own related databases. +#[derive(Debug, Default, Deserialize, Serialize)] +#[serde(default)] +pub struct DatabasesConfig { + /// Additional database paths for cross-repo queries. + /// Paths are relative to the repo root containing `.codemark/config.toml`. + /// Supports `~` expansion for home directory. + pub additional: Vec, +} + +impl DatabasesConfig { + /// Resolve additional database paths, expanding `~` and resolving relative paths. + /// + /// `repo_root` is the git repository root containing the `.codemark` directory. + /// Relative paths are resolved from the repo root (not from `.codemark/`). + pub fn resolve_additional_paths(&self, repo_root: &Path) -> Vec { + self.additional + .iter() + .map(|p| { + let expanded = shellexpand::tilde(p); + let path = PathBuf::from(expanded.as_ref()); + let resolved = if path.is_absolute() { + path + } else { + // Relative to repo root (where .codemark/ lives) + repo_root.join(path) + }; + // Normalize the path to resolve . and .. components + normalize_path(&resolved) + }) + .collect() + } +} + +/// Normalize a path by resolving . and .. components. +/// Unlike std::fs::canonicalize, this doesn't require the path to exist. +fn normalize_path(path: &Path) -> PathBuf { + use std::path::Component; + + let mut result = PathBuf::new(); + let mut has_root = false; + + for component in path.components() { + match component { + Component::Prefix(prefix) => { + // Prefix needs special handling - it's part of the path's structure + // We need to reconstruct it properly + let mut temp = PathBuf::new(); + temp.push(prefix.as_os_str()); + result = temp; + } + Component::RootDir => { + if !has_root { + result.push(component); + has_root = true; + } + } + Component::CurDir => { + // Skip . components + } + Component::ParentDir => { + // Pop if possible, but don't go above root + if !result.pop() || !has_root { + // If we can't pop, we're at root - keep the .. + result.push(component); + } + } + Component::Normal(normal) => result.push(normal), + } + } + if result.as_os_str().is_empty() { + PathBuf::from(".") + } else { + result + } +} + /// Editor configuration for the `codemark open` command. #[derive(Debug, Default, Deserialize, Serialize)] #[serde(default)] @@ -367,6 +449,12 @@ impl Config { self.open.editor_types.gui.push(editor); } } + + // Databases config - local-only setting, replace entire section if set + // This is only respected in local config (.codemark/config.toml) + if !other.databases.additional.is_empty() { + self.databases.additional = other.databases.additional; + } } /// Write the default config file to the global config directory. @@ -825,4 +913,70 @@ terminal = ["vim", "emacs"] global.open.editor_types.terminal.iter().filter(|x| x.as_str() == "vim").count(); assert_eq!(vim_count, 1); } + + #[test] + fn parse_databases_config() { + let toml = r#" +[databases] +additional = [ + "../shared-lib/.codemark/codemark.db", + "~/projects/another-repo/.codemark/codemark.db", +] +"#; + let config: Config = toml::from_str(toml).unwrap(); + assert_eq!(config.databases.additional.len(), 2); + assert_eq!( + config.databases.additional[0], + "../shared-lib/.codemark/codemark.db" + ); + assert_eq!( + config.databases.additional[1], + "~/projects/another-repo/.codemark/codemark.db" + ); + } + + #[test] + fn resolve_additional_paths_empty() { + let config = DatabasesConfig::default(); + let repo_root = Path::new("/test/repo"); + let paths = config.resolve_additional_paths(repo_root); + assert!(paths.is_empty()); + } + + #[test] + fn resolve_additional_paths_relative() { + let mut config = DatabasesConfig::default(); + config.additional = vec![ + "../shared-lib/.codemark/codemark.db".to_string(), + "./local.db".to_string(), + ]; + let repo_root = Path::new("/test/repo"); + let paths = config.resolve_additional_paths(repo_root); + assert_eq!(paths.len(), 2); + // Paths should be normalized + assert_eq!(paths[0], PathBuf::from("/test/shared-lib/.codemark/codemark.db")); + assert_eq!(paths[1], PathBuf::from("/test/repo/local.db")); + } + + #[test] + fn resolve_additional_paths_absolute() { + let mut config = DatabasesConfig::default(); + config.additional = vec!["/absolute/path/to/db.sqlite".to_string()]; + let repo_root = Path::new("/test/repo"); + let paths = config.resolve_additional_paths(repo_root); + assert_eq!(paths.len(), 1); + assert_eq!(paths[0], PathBuf::from("/absolute/path/to/db.sqlite")); + } + + #[test] + fn resolve_additional_paths_tilde_expansion() { + let mut config = DatabasesConfig::default(); + config.additional = vec!["~/projects/db.sqlite".to_string()]; + let repo_root = Path::new("/test/repo"); + let paths = config.resolve_additional_paths(repo_root); + assert_eq!(paths.len(), 1); + // Tilde should be expanded to the home directory + assert!(paths[0].to_string_lossy().contains("projects")); + assert!(paths[0].is_absolute()); + } } diff --git a/tests/cli_integration.rs b/tests/cli_integration.rs index eeb13aa..e50d9fe 100644 --- a/tests/cli_integration.rs +++ b/tests/cli_integration.rs @@ -167,6 +167,22 @@ impl Codemark { } } + /// Run codemark without specifying --db (uses auto-detection + config). + /// This is used to test config-based multi-database behavior. + fn run_no_db(&self, args: &[&str]) -> CmdResult { + let output = Command::new(&self.binary) + .args(args) + .current_dir(&self.work_dir) + .output() + .expect("failed to run codemark"); + + CmdResult { + stdout: String::from_utf8_lossy(&output.stdout).to_string(), + stderr: String::from_utf8_lossy(&output.stderr).to_string(), + status: output.status.code().unwrap_or(-1), + } + } + fn run_json(&self, args: &[&str]) -> serde_json::Value { let mut full_args = vec!["--format", "json"]; full_args.extend_from_slice(args); @@ -176,6 +192,17 @@ impl Codemark { .unwrap_or_else(|e| panic!("invalid JSON: {e}\nstdout: {}", result.stdout)) } + /// Run codemark with JSON output but without specifying --db. + /// Uses auto-detection + config for database paths. + fn run_json_no_db(&self, args: &[&str]) -> serde_json::Value { + let mut full_args = vec!["--format", "json"]; + full_args.extend_from_slice(args); + let result = self.run_no_db(&full_args); + assert_eq!(result.status, 0, "command failed: {}\n{}", result.stderr, result.stdout); + serde_json::from_str(&result.stdout) + .unwrap_or_else(|e| panic!("invalid JSON: {e}\nstdout: {}", result.stdout)) + } + /// Fixture path relative to the project root (only works for non-git-repo tests). fn fixture(&self, name: &str) -> String { format!("tests/fixtures/{name}") @@ -2249,3 +2276,438 @@ fn test_function() { // OFFSET should be >= LINE (offset is center of range, line is start) assert!(offset >= line_num, "OFFSET ({}) should be >= LINE ({})", offset, line_num); } + +// --- Multi-database configuration tests --- + +#[test] +fn config_additional_databases_relative_path() { + // Create a parent tempdir for both repos + let parent_temp = tempfile::tempdir().unwrap(); + + // Create main repo + let parent_path = parent_temp.path().to_path_buf(); + let main_repo_path = parent_path.join("main-repo"); + std::fs::create_dir_all(&main_repo_path).unwrap(); + let _main_repo = git2::Repository::init(&main_repo_path).unwrap(); + let main_codemark_dir = main_repo_path.join(".codemark"); + std::fs::create_dir_all(&main_codemark_dir).unwrap(); + let main_db_path = main_codemark_dir.join("codemark.db"); + + let cm_main = Codemark { + db_path: main_db_path.clone(), + binary: PathBuf::from(env!("CARGO_BIN_EXE_codemark")), + temp_dir: Some(parent_temp), + work_dir: main_repo_path.clone(), + }; + + // Create a shared lib repo (sibling directory) + let shared_repo_path = parent_path.join("shared-lib"); + std::fs::create_dir_all(&shared_repo_path).unwrap(); + + // Initialize shared lib as git repo + let _shared_repo = git2::Repository::init(&shared_repo_path).unwrap(); + let shared_codemark_dir = shared_repo_path.join(".codemark"); + std::fs::create_dir_all(&shared_codemark_dir).unwrap(); + let shared_db_path = shared_codemark_dir.join("codemark.db"); + + // Add a bookmark to the shared lib + let shared_cm = Codemark { + db_path: shared_db_path.clone(), + binary: cm_main.binary.clone(), + temp_dir: None, // Don't double-drop the parent temp + work_dir: shared_repo_path.clone(), + }; + + // Create a test file in shared lib + let content = "fn shared_function() { println!(\"shared\"); }"; + std::fs::write(shared_repo_path.join("shared.rs"), content).unwrap(); + + shared_cm.run_json(&[ + "add", + "--file", + &shared_cm.file_path("shared.rs"), + "--range", + "1", + "--note", + "shared lib bookmark", + ]); + + // Verify the shared db has the bookmark + let json = shared_cm.run_json(&["list"]); + assert_eq!(json["data"].as_array().unwrap().len(), 1); + + // Create config in main repo with relative path to shared lib + let config_content = r#"[databases] +additional = [ + "../shared-lib/.codemark/codemark.db", +] +"#; + std::fs::write(cm_main.work_dir.join(".codemark/config.toml"), config_content).unwrap(); + + // List should include bookmarks from both databases + let json = cm_main.run_json_no_db(&["list"]); + let bookmarks = json["data"].as_array().unwrap(); + + // Should have at least the shared lib bookmark + assert!( + bookmarks.len() >= 1, + "Expected at least 1 bookmark, got {}", + bookmarks.len() + ); + + // Check that we have a bookmark from the shared lib + // Notes are in the annotations array + let found_shared = bookmarks + .iter() + .any(|b| { + b["annotations"] + .as_array() + .and_then(|arr| arr.first()) + .and_then(|ann| ann["notes"].as_str()) + .unwrap_or("") + == "shared lib bookmark" + }); + assert!(found_shared, "Expected to find shared lib bookmark"); +} + +#[test] +fn config_additional_databases_absolute_path() { + let cm = Codemark::with_git_repo(); + + // Create a separate database file + let temp_dir = tempfile::tempdir().unwrap(); + let other_db_path = temp_dir.path().join("other.db"); + + // Add a bookmark to the other database using direct --db + let other_cm = Codemark { + db_path: other_db_path.clone(), + binary: cm.binary.clone(), + temp_dir: Some(temp_dir), + work_dir: cm.work_dir.clone(), + }; + + let content = "fn other_function() { println!(\"other\"); }"; + std::fs::write(cm.work_dir.join("other.rs"), content).unwrap(); + + other_cm.run_json(&[ + "add", + "--file", + &other_cm.file_path("other.rs"), + "--range", + "1", + "--note", + "other db bookmark", + ]); + + // Create config with absolute path + let config_content = format!( + r#"[databases] +additional = [ + "{}", +] +"#, + other_db_path.display() + ); + std::fs::write(cm.work_dir.join(".codemark/config.toml"), config_content).unwrap(); + + // List should include bookmarks from both databases + let json = cm.run_json_no_db(&["list"]); + let bookmarks = json["data"].as_array().unwrap(); + + // Should have at least the other db bookmark + assert!( + bookmarks.len() >= 1, + "Expected at least 1 bookmark, got {}", + bookmarks.len() + ); + + let found_other = bookmarks + .iter() + .any(|b| { + b["annotations"] + .as_array() + .and_then(|arr| arr.first()) + .and_then(|ann| ann["notes"].as_str()) + .unwrap_or("") + == "other db bookmark" + }); + assert!(found_other, "Expected to find other db bookmark"); +} + +#[test] +fn cli_db_overrides_config() { + let cm = Codemark::with_git_repo(); + + // Create a separate database file + let temp_dir = tempfile::tempdir().unwrap(); + let other_db_path = temp_dir.path().join("other.db"); + + // Add a bookmark to the main database + let content = "fn main_function() { println!(\"main\"); }"; + std::fs::write(cm.work_dir.join("main.rs"), content).unwrap(); + + cm.run_json(&[ + "add", + "--file", + &cm.file_path("main.rs"), + "--range", + "1", + "--note", + "main db bookmark", + ]); + + // Add a bookmark to the other database + let other_cm = Codemark { + db_path: other_db_path.clone(), + binary: cm.binary.clone(), + temp_dir: Some(temp_dir), + work_dir: cm.work_dir.clone(), + }; + + let other_content = "fn other_function() { println!(\"other\"); }"; + std::fs::write(cm.work_dir.join("other.rs"), other_content).unwrap(); + + other_cm.run_json(&[ + "add", + "--file", + &other_cm.file_path("other.rs"), + "--range", + "1", + "--note", + "other db bookmark", + ]); + + // Create config that includes other db + let config_content = format!( + r#"[databases] +additional = [ + "{}", +] +"#, + other_db_path.display() + ); + std::fs::write(cm.work_dir.join(".codemark/config.toml"), config_content).unwrap(); + + // Without --db, should see both bookmarks + let json = cm.run_json_no_db(&["list"]); + let bookmarks_all = json["data"].as_array().unwrap(); + assert!( + bookmarks_all.len() >= 2, + "Expected at least 2 bookmarks with config, got {}", + bookmarks_all.len() + ); + + // With explicit --db pointing only to main db, should only see main db bookmark + let json = cm.run(&["--db", &cm.db_path.to_string_lossy(), "list", "--format", "json"]); + let result: serde_json::Value = serde_json::from_str(&json.stdout).unwrap(); + let bookmarks_cli = result["data"].as_array().unwrap(); + + // Should only have the main db bookmark + let found_main = bookmarks_cli + .iter() + .any(|b| { + b["annotations"] + .as_array() + .and_then(|arr| arr.first()) + .and_then(|ann| ann["notes"].as_str()) + .unwrap_or("") + == "main db bookmark" + }); + let found_other = bookmarks_cli + .iter() + .any(|b| { + b["annotations"] + .as_array() + .and_then(|arr| arr.first()) + .and_then(|ann| ann["notes"].as_str()) + .unwrap_or("") + == "other db bookmark" + }); + + assert!(found_main, "Expected to find main db bookmark with --db override"); + assert!(!found_other, "Did not expect other db bookmark with --db override"); +} + +#[test] +fn write_operations_only_affect_primary_db() { + let cm = Codemark::with_git_repo(); + + // Create a separate database file + let temp_dir = tempfile::tempdir().unwrap(); + let other_db_path = temp_dir.path().join("other.db"); + + // Add a bookmark to the other database + let other_cm = Codemark { + db_path: other_db_path.clone(), + binary: cm.binary.clone(), + temp_dir: Some(temp_dir), + work_dir: cm.work_dir.clone(), + }; + + let content = "fn existing_function() { println!(\"existing\"); }"; + std::fs::write(cm.work_dir.join("existing.rs"), content).unwrap(); + + let json = other_cm.run_json(&[ + "add", + "--file", + &other_cm.file_path("existing.rs"), + "--range", + "1", + "--note", + "existing bookmark", + ]); + let existing_id = json["data"]["id"].as_str().unwrap(); + + // Create config that includes other db + let config_content = format!( + r#"[databases] +additional = [ + "{}", +] +"#, + other_db_path.display() + ); + std::fs::write(cm.work_dir.join(".codemark/config.toml"), config_content).unwrap(); + + // Verify we can see the bookmark from other db + let json = cm.run_json_no_db(&["list"]); + let bookmarks_before = json["data"].as_array().unwrap(); + assert!( + bookmarks_before + .iter() + .any(|b| { + b["annotations"] + .as_array() + .and_then(|arr| arr.first()) + .and_then(|ann| ann["notes"].as_str()) + .unwrap_or("") + == "existing bookmark" + }), + "Expected to see bookmark from additional db" + ); + + // Try to remove the bookmark (this should only affect primary db) + let result = cm.run_no_db(&["remove", &existing_id[..8]]); + // The remove command should succeed but not actually remove from the other db + // because write operations only affect the primary db + assert!(result.status == 0 || result.stderr.contains("not found")); + + // Verify the bookmark still exists in the other db + let json = other_cm.run_json(&["list"]); + let bookmarks_other = json["data"].as_array().unwrap(); + assert!( + bookmarks_other + .iter() + .any(|b| b["id"].as_str().unwrap_or("").starts_with(existing_id)), + "Bookmark should still exist in other db after remove from primary" + ); +} + +#[test] +fn config_empty_additional_list_behaves_same_as_no_config() { + let cm = Codemark::with_git_repo(); + + // Add a bookmark to main db + let content = "fn main_function() { println!(\"main\"); }"; + std::fs::write(cm.work_dir.join("main.rs"), content).unwrap(); + + cm.run_json(&[ + "add", + "--file", + &cm.file_path("main.rs"), + "--range", + "1", + "--note", + "main bookmark", + ]); + + // Create config with empty additional list + let config_content = r#"[databases] +additional = [] +"#; + std::fs::write(cm.work_dir.join(".codemark/config.toml"), config_content).unwrap(); + + // List should only show main db bookmark + let json = cm.run_json_no_db(&["list"]); + let bookmarks = json["data"].as_array().unwrap(); + assert_eq!(bookmarks.len(), 1, "Expected only 1 bookmark with empty additional list"); + assert_eq!( + bookmarks[0]["annotations"] + .as_array() + .and_then(|arr| arr.first()) + .and_then(|ann| ann["notes"].as_str()) + .unwrap(), + "main bookmark" + ); +} + +#[test] +fn databases_config_only_in_local_config() { + // This test verifies that databases config in global config is ignored + // Only local config (.codemark/config.toml) should be respected + let cm = Codemark::with_git_repo(); + + // Create a separate database file + let temp_dir = tempfile::tempdir().unwrap(); + let other_db_path = temp_dir.path().join("other.db"); + + // Add a bookmark to the other database + let other_cm = Codemark { + db_path: other_db_path.clone(), + binary: cm.binary.clone(), + temp_dir: Some(temp_dir), + work_dir: cm.work_dir.clone(), + }; + + let content = "fn other_function() { println!(\"other\"); }"; + std::fs::write(cm.work_dir.join("other.rs"), content).unwrap(); + + other_cm.run_json(&[ + "add", + "--file", + &other_cm.file_path("other.rs"), + "--range", + "1", + "--note", + "other db bookmark", + ]); + + // Create global config directory and add databases config there + let global_config_dir = std::env::temp_dir().join(format!("codemark_global_{}", uuid())); + std::fs::create_dir_all(&global_config_dir).unwrap(); + + let global_config = format!( + r#"[databases] +additional = [ + "{}", +] +"#, + other_db_path.display() + ); + std::fs::write(global_config_dir.join("config.toml"), global_config).unwrap(); + + // Set XDG_CONFIG_HOME to use our test global config + unsafe { std::env::set_var("XDG_CONFIG_HOME", &global_config_dir) }; + + // Without local config, global databases config should be ignored + let json = cm.run_json_no_db(&["list"]); + let bookmarks = json["data"].as_array().unwrap(); + + // Should NOT have the other db bookmark because databases config is local-only + let found_other = bookmarks + .iter() + .any(|b| { + b["annotations"] + .as_array() + .and_then(|arr| arr.first()) + .and_then(|ann| ann["notes"].as_str()) + .unwrap_or("") + == "other db bookmark" + }); + assert!(!found_other, "Global databases config should be ignored"); + + // Cleanup + let _ = std::fs::remove_dir_all(&global_config_dir); + unsafe { std::env::remove_var("XDG_CONFIG_HOME") }; +} + From 4159fd7bd90ae19e3af535f6163a7e9b4b1165e7 Mon Sep 17 00:00:00 2001 From: Daniel Cardona Rojas Date: Tue, 21 Apr 2026 20:47:44 -0500 Subject: [PATCH 2/2] Format --- src/config.rs | 22 ++----- tests/cli_integration.rs | 127 ++++++++++++++++----------------------- 2 files changed, 57 insertions(+), 92 deletions(-) diff --git a/src/config.rs b/src/config.rs index 1990255..a157097 100644 --- a/src/config.rs +++ b/src/config.rs @@ -221,11 +221,7 @@ fn normalize_path(path: &Path) -> PathBuf { Component::Normal(normal) => result.push(normal), } } - if result.as_os_str().is_empty() { - PathBuf::from(".") - } else { - result - } + if result.as_os_str().is_empty() { PathBuf::from(".") } else { result } } /// Editor configuration for the `codemark open` command. @@ -925,14 +921,8 @@ additional = [ "#; let config: Config = toml::from_str(toml).unwrap(); assert_eq!(config.databases.additional.len(), 2); - assert_eq!( - config.databases.additional[0], - "../shared-lib/.codemark/codemark.db" - ); - assert_eq!( - config.databases.additional[1], - "~/projects/another-repo/.codemark/codemark.db" - ); + assert_eq!(config.databases.additional[0], "../shared-lib/.codemark/codemark.db"); + assert_eq!(config.databases.additional[1], "~/projects/another-repo/.codemark/codemark.db"); } #[test] @@ -946,10 +936,8 @@ additional = [ #[test] fn resolve_additional_paths_relative() { let mut config = DatabasesConfig::default(); - config.additional = vec![ - "../shared-lib/.codemark/codemark.db".to_string(), - "./local.db".to_string(), - ]; + config.additional = + vec!["../shared-lib/.codemark/codemark.db".to_string(), "./local.db".to_string()]; let repo_root = Path::new("/test/repo"); let paths = config.resolve_additional_paths(repo_root); assert_eq!(paths.len(), 2); diff --git a/tests/cli_integration.rs b/tests/cli_integration.rs index e50d9fe..90f7a18 100644 --- a/tests/cli_integration.rs +++ b/tests/cli_integration.rs @@ -2314,7 +2314,7 @@ fn config_additional_databases_relative_path() { let shared_cm = Codemark { db_path: shared_db_path.clone(), binary: cm_main.binary.clone(), - temp_dir: None, // Don't double-drop the parent temp + temp_dir: None, // Don't double-drop the parent temp work_dir: shared_repo_path.clone(), }; @@ -2349,24 +2349,18 @@ additional = [ let bookmarks = json["data"].as_array().unwrap(); // Should have at least the shared lib bookmark - assert!( - bookmarks.len() >= 1, - "Expected at least 1 bookmark, got {}", - bookmarks.len() - ); + assert!(bookmarks.len() >= 1, "Expected at least 1 bookmark, got {}", bookmarks.len()); // Check that we have a bookmark from the shared lib // Notes are in the annotations array - let found_shared = bookmarks - .iter() - .any(|b| { - b["annotations"] - .as_array() - .and_then(|arr| arr.first()) - .and_then(|ann| ann["notes"].as_str()) - .unwrap_or("") - == "shared lib bookmark" - }); + let found_shared = bookmarks.iter().any(|b| { + b["annotations"] + .as_array() + .and_then(|arr| arr.first()) + .and_then(|ann| ann["notes"].as_str()) + .unwrap_or("") + == "shared lib bookmark" + }); assert!(found_shared, "Expected to find shared lib bookmark"); } @@ -2415,22 +2409,16 @@ additional = [ let bookmarks = json["data"].as_array().unwrap(); // Should have at least the other db bookmark - assert!( - bookmarks.len() >= 1, - "Expected at least 1 bookmark, got {}", - bookmarks.len() - ); + assert!(bookmarks.len() >= 1, "Expected at least 1 bookmark, got {}", bookmarks.len()); - let found_other = bookmarks - .iter() - .any(|b| { - b["annotations"] - .as_array() - .and_then(|arr| arr.first()) - .and_then(|ann| ann["notes"].as_str()) - .unwrap_or("") - == "other db bookmark" - }); + let found_other = bookmarks.iter().any(|b| { + b["annotations"] + .as_array() + .and_then(|arr| arr.first()) + .and_then(|ann| ann["notes"].as_str()) + .unwrap_or("") + == "other db bookmark" + }); assert!(found_other, "Expected to find other db bookmark"); } @@ -2503,26 +2491,22 @@ additional = [ let bookmarks_cli = result["data"].as_array().unwrap(); // Should only have the main db bookmark - let found_main = bookmarks_cli - .iter() - .any(|b| { - b["annotations"] - .as_array() - .and_then(|arr| arr.first()) - .and_then(|ann| ann["notes"].as_str()) - .unwrap_or("") - == "main db bookmark" - }); - let found_other = bookmarks_cli - .iter() - .any(|b| { - b["annotations"] - .as_array() - .and_then(|arr| arr.first()) - .and_then(|ann| ann["notes"].as_str()) - .unwrap_or("") - == "other db bookmark" - }); + let found_main = bookmarks_cli.iter().any(|b| { + b["annotations"] + .as_array() + .and_then(|arr| arr.first()) + .and_then(|ann| ann["notes"].as_str()) + .unwrap_or("") + == "main db bookmark" + }); + let found_other = bookmarks_cli.iter().any(|b| { + b["annotations"] + .as_array() + .and_then(|arr| arr.first()) + .and_then(|ann| ann["notes"].as_str()) + .unwrap_or("") + == "other db bookmark" + }); assert!(found_main, "Expected to find main db bookmark with --db override"); assert!(!found_other, "Did not expect other db bookmark with --db override"); @@ -2573,16 +2557,14 @@ additional = [ let json = cm.run_json_no_db(&["list"]); let bookmarks_before = json["data"].as_array().unwrap(); assert!( - bookmarks_before - .iter() - .any(|b| { - b["annotations"] - .as_array() - .and_then(|arr| arr.first()) - .and_then(|ann| ann["notes"].as_str()) - .unwrap_or("") - == "existing bookmark" - }), + bookmarks_before.iter().any(|b| { + b["annotations"] + .as_array() + .and_then(|arr| arr.first()) + .and_then(|ann| ann["notes"].as_str()) + .unwrap_or("") + == "existing bookmark" + }), "Expected to see bookmark from additional db" ); @@ -2596,9 +2578,7 @@ additional = [ let json = other_cm.run_json(&["list"]); let bookmarks_other = json["data"].as_array().unwrap(); assert!( - bookmarks_other - .iter() - .any(|b| b["id"].as_str().unwrap_or("").starts_with(existing_id)), + bookmarks_other.iter().any(|b| b["id"].as_str().unwrap_or("").starts_with(existing_id)), "Bookmark should still exist in other db after remove from primary" ); } @@ -2694,20 +2674,17 @@ additional = [ let bookmarks = json["data"].as_array().unwrap(); // Should NOT have the other db bookmark because databases config is local-only - let found_other = bookmarks - .iter() - .any(|b| { - b["annotations"] - .as_array() - .and_then(|arr| arr.first()) - .and_then(|ann| ann["notes"].as_str()) - .unwrap_or("") - == "other db bookmark" - }); + let found_other = bookmarks.iter().any(|b| { + b["annotations"] + .as_array() + .and_then(|arr| arr.first()) + .and_then(|ann| ann["notes"].as_str()) + .unwrap_or("") + == "other db bookmark" + }); assert!(!found_other, "Global databases config should be ignored"); // Cleanup let _ = std::fs::remove_dir_all(&global_config_dir); unsafe { std::env::remove_var("XDG_CONFIG_HOME") }; } -