diff --git a/haiku_rag_slim/haiku/rag/config/loader.py b/haiku_rag_slim/haiku/rag/config/loader.py index 0b5cba9f..5c7a3074 100644 --- a/haiku_rag_slim/haiku/rag/config/loader.py +++ b/haiku_rag_slim/haiku/rag/config/loader.py @@ -48,47 +48,8 @@ def load_yaml_config(path: Path) -> dict: def generate_default_config() -> dict: - """Generate a default YAML config structure with documentation.""" - return { - "environment": "production", - "storage": { - "data_dir": "", - "vacuum_retention_seconds": 86400, - }, - "monitor": { - "directories": [], - "ignore_patterns": [], - "include_patterns": [], - }, - "lancedb": {"uri": "", "api_key": "", "region": ""}, - "embeddings": { - "provider": "ollama", - "model": "qwen3-embedding:4b", - "vector_dim": 2560, - }, - "reranking": {"provider": "", "model": ""}, - "qa": {"provider": "ollama", "model": "gpt-oss"}, - "research": {"provider": "", "model": ""}, - "processing": { - "chunk_size": 256, - "context_chunk_radius": 0, - "markdown_preprocessor": "", - }, - "providers": { - "ollama": {"base_url": "http://localhost:11434"}, - "vllm": { - "embeddings_base_url": "", - "rerank_base_url": "", - "qa_base_url": "", - "research_base_url": "", - }, - }, - "agui": { - "host": "0.0.0.0", - "port": 8000, - "cors_origins": ["*"], - "cors_credentials": True, - "cors_methods": ["GET", "POST", "OPTIONS"], - "cors_headers": ["*"], - }, - } + """Generate a default YAML config structure from AppConfig defaults.""" + from haiku.rag.config.models import AppConfig + + default_config = AppConfig() + return default_config.model_dump(mode="json", exclude_none=False) diff --git a/tests/test_config_loader.py b/tests/test_config.py similarity index 68% rename from tests/test_config_loader.py rename to tests/test_config.py index 468e1a52..9c3daa2e 100644 --- a/tests/test_config_loader.py +++ b/tests/test_config.py @@ -1,5 +1,7 @@ import pytest +import yaml +from haiku.rag.config import AppConfig from haiku.rag.config.loader import ( find_config_file, generate_default_config, @@ -98,19 +100,6 @@ def test_find_config_file_cli_path_not_exists(tmp_path): find_config_file(config_file) -def test_generate_default_config(): - """Test generating default config structure.""" - config = generate_default_config() - - assert config["environment"] == "production" - assert "storage" in config - assert "embeddings" in config - assert "qa" in config - assert "providers" in config - assert config["embeddings"]["provider"] == "ollama" - assert config["embeddings"]["vector_dim"] == 2560 - - def test_config_precedence_cwd_over_user(tmp_path, monkeypatch): """Test that cwd config takes precedence over user config.""" # Create separate directories for cwd and user config @@ -163,3 +152,57 @@ def test_config_precedence_env_var_over_cwd(tmp_path, monkeypatch): config = load_yaml_config(found) assert config["environment"] == "from-env-var" + + +def test_generate_default_config_completeness(): + """Test that generated config has all fields from AppConfig and validates.""" + config_data = generate_default_config() + + # Validate against AppConfig model + config = AppConfig.model_validate(config_data) + + # Get all fields from AppConfig + expected_fields = set(AppConfig.model_fields.keys()) + actual_fields = set(config_data.keys()) + + # Verify all expected fields are present + assert expected_fields == actual_fields, ( + f"Missing fields: {expected_fields - actual_fields}, " + f"Extra fields: {actual_fields - expected_fields}" + ) + + # Verify nested structures are dicts/lists, not model instances + for field_name, field_value in config_data.items(): + assert not hasattr(field_value, "model_dump"), ( + f"Field {field_name} should be dict/primitive, not Pydantic model" + ) + + # Verify config validates successfully + assert config.environment == "production" + assert config.embeddings.provider == "ollama" + assert config.qa.model.provider == "ollama" + assert config.research.model.provider == "ollama" + assert config.reranking.model is None + + +def test_init_config_creates_valid_yaml(tmp_path): + """Test that generated config can be written to YAML and loaded back.""" + config_file = tmp_path / "test-config.yaml" + + # Generate and write config + config_data = generate_default_config() + + with open(config_file, "w") as f: + f.write("# haiku.rag configuration file\n") + f.write( + "# See https://ggozad.github.io/haiku.rag/configuration/ for details\n\n" + ) + yaml.dump(config_data, f, default_flow_style=False, sort_keys=False) + + # Load it back + with open(config_file) as f: + loaded_data = yaml.safe_load(f) + + # Validate it + config = AppConfig.model_validate(loaded_data) + assert config.environment == "production"