"""Tests for obsidian_rag.config — loader, path resolution, defaults.""" from __future__ import annotations import json import tempfile from pathlib import Path import pytest from obsidian_rag.config import ( EmbeddingConfig, ObsidianRagConfig, load_config, resolve_vector_db_path, resolve_vault_path, ) # ---------------------------------------------------------------------- # Config loading # ---------------------------------------------------------------------- def test_load_config_parses_valid_json(tmp_path: Path): config_path = tmp_path / "config.json" config_path.write_text( json.dumps({ "vault_path": "/path/to/vault", "embedding": {"model": "custom-model:tag", "dimensions": 512}, "vector_store": {"path": "/vectors/db"}, }) ) config = load_config(config_path) assert config.vault_path == "/path/to/vault" assert config.embedding.model == "custom-model:tag" assert config.embedding.dimensions == 512 # overridden def test_load_config_missing_file_raises(tmp_path: Path): with pytest.raises(FileNotFoundError): load_config(tmp_path / "nonexistent.json") def test_load_config_merges_partial_json(tmp_path: Path): config_path = tmp_path / "config.json" config_path.write_text(json.dumps({"vault_path": "/custom/vault"})) config = load_config(config_path) # Unspecified fields fall back to defaults assert config.vault_path == "/custom/vault" assert config.embedding.base_url == "http://localhost:11434" # default assert config.indexing.chunk_size == 500 # default # ---------------------------------------------------------------------- # resolve_vault_path # ---------------------------------------------------------------------- def test_resolve_vault_path_absolute(): cfg = ObsidianRagConfig(vault_path="/absolute/vault") assert resolve_vault_path(cfg) == Path("/absolute/vault") def test_resolve_vault_path_relative_defaults_to_project_root(): cfg = ObsidianRagConfig(vault_path="KnowledgeVault/Default") result = resolve_vault_path(cfg) # Should resolve relative to python/obsidian_rag/ → project root assert result.name == "Default" assert result.parent.name == "KnowledgeVault" # ---------------------------------------------------------------------- # resolve_vector_db_path # ---------------------------------------------------------------------- def test_resolve_vector_db_path_string_absolute(): """VectorStoreConfig stores path as a string; Path objects should be converted first.""" from obsidian_rag.config import VectorStoreConfig # Using a string path (the actual usage) cfg = ObsidianRagConfig(vector_store=VectorStoreConfig(path="/my/vectors.lance")) result = resolve_vector_db_path(cfg) assert result == Path("/my/vectors.lance") def test_resolve_vector_db_path_string_relative(tmp_path: Path): """Relative paths are resolved against the data directory.""" import obsidian_rag.config as cfg_mod # Set up data dir + vault marker (required by _resolve_data_dir) # Note: the dev data dir is "obsidian-rag" (without leading dot) data_dir = tmp_path / "obsidian-rag" data_dir.mkdir() (tmp_path / "KnowledgeVault").mkdir() vector_file = data_dir / "vectors.lance" vector_file.touch() cfg = ObsidianRagConfig(vector_store=cfg_mod.VectorStoreConfig(path="vectors.lance")) orig = cfg_mod.DEFAULT_CONFIG_DIR cfg_mod.DEFAULT_CONFIG_DIR = tmp_path try: result = resolve_vector_db_path(cfg) finally: cfg_mod.DEFAULT_CONFIG_DIR = orig # Resolves to data_dir / vectors.lance assert result.parent.name == "obsidian-rag" # dev dir is "obsidian-rag" (no leading dot) assert result.name == "vectors.lance" # ---------------------------------------------------------------------- # Dataclass defaults # ---------------------------------------------------------------------- def test_embedding_config_defaults(): cfg = EmbeddingConfig() assert cfg.model == "mxbai-embed-large" assert cfg.dimensions == 1024 assert cfg.batch_size == 64 def test_security_config_defaults(): from obsidian_rag.config import SecurityConfig cfg = SecurityConfig() assert "#mentalhealth" in cfg.sensitive_sections assert "health" in cfg.require_confirmation_for