fix: address config loader review feedback

This commit is contained in:
2026-04-13 14:03:39 -04:00
parent 828f707178
commit d16656a473
2 changed files with 182 additions and 1 deletions

View File

@@ -1,6 +1,5 @@
import json import json
import os import os
from pathlib import Path
from typing import Any, Dict, List from typing import Any, Dict, List
from pydantic import BaseModel, Field from pydantic import BaseModel, Field

View File

@@ -140,5 +140,187 @@ def test_load_config_reads_json_and_expands_tilde():
assert config.rag.vector_store.path == os.path.expanduser( assert config.rag.vector_store.path == os.path.expanduser(
"~/.companion/vectors.lance" "~/.companion/vectors.lance"
) )
assert config.companion.memory.persistent_store == os.path.expanduser(
"~/mem.db"
)
finally:
os.unlink(path)
def test_tilde_expansion_in_list_values():
"""Test that tilde expansion works for list values."""
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
json.dump(
{
"companion": {
"name": "SAN",
"persona": {
"role": "companion",
"tone": "reflective",
"style": "questioning",
"boundaries": [],
},
"memory": {
"session_turns": 20,
"persistent_store": "~/mem.db",
"summarize_after": 10,
},
"chat": {
"streaming": True,
"max_response_tokens": 2048,
"default_temperature": 0.7,
"allow_temperature_override": True,
},
},
"vault": {
"path": "~/test-vault",
"indexing": {
"auto_sync": False,
"auto_sync_interval_minutes": 1440,
"watch_fs_events": False,
"file_patterns": ["*.md"],
"deny_dirs": ["~/secret", ".git"],
"deny_patterns": [".*"],
},
"chunking_rules": {},
},
"rag": {
"embedding": {
"provider": "ollama",
"model": "dummy",
"base_url": "http://localhost:11434",
"dimensions": 4,
"batch_size": 2,
},
"vector_store": {
"type": "lancedb",
"path": "~/.companion/vectors.lance",
},
"search": {
"default_top_k": 8,
"max_top_k": 20,
"similarity_threshold": 0.75,
"hybrid_search": {
"enabled": False,
"keyword_weight": 0.3,
"semantic_weight": 0.7,
},
"filters": {
"date_range_enabled": True,
"tag_filter_enabled": True,
"directory_filter_enabled": True,
},
},
},
"model": {
"inference": {
"backend": "llama.cpp",
"model_path": "",
"context_length": 8192,
"gpu_layers": 35,
"batch_size": 512,
"threads": 8,
},
"fine_tuning": {
"base_model": "",
"output_dir": "",
"lora_rank": 16,
"lora_alpha": 32,
"learning_rate": 0.0002,
"batch_size": 4,
"gradient_accumulation_steps": 4,
"num_epochs": 3,
"warmup_steps": 100,
"save_steps": 500,
"eval_steps": 250,
"training_data_path": "",
"validation_split": 0.1,
},
"retrain_schedule": {
"auto_reminder": True,
"default_interval_days": 90,
"reminder_channels": [],
},
},
"api": {
"host": "127.0.0.1",
"port": 7373,
"cors_origins": [],
"auth": {"enabled": False},
},
"ui": {
"web": {
"enabled": True,
"theme": "obsidian",
"features": {
"streaming": True,
"citations": True,
"source_preview": True,
},
},
"cli": {"enabled": True, "rich_output": True},
},
"logging": {
"level": "INFO",
"file": "",
"max_size_mb": 100,
"backup_count": 5,
},
"security": {
"local_only": True,
"vault_path_traversal_check": True,
"sensitive_content_detection": True,
"sensitive_patterns": [],
"require_confirmation_for_external_apis": True,
},
},
f,
)
path = f.name
try:
config = load_config(path)
# Check tilde expansion in list values
assert config.vault.indexing.deny_dirs[0] == os.path.expanduser("~/secret")
finally:
os.unlink(path)
def test_load_config_missing_file():
"""Test that loading a missing file raises FileNotFoundError."""
with tempfile.TemporaryDirectory() as tmp:
missing_path = os.path.join(tmp, "nonexistent.json")
try:
load_config(missing_path)
assert False, "Should have raised an exception"
except FileNotFoundError:
pass # Expected
def test_load_config_malformed_json():
"""Test that malformed JSON raises JSONDecodeError."""
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
f.write("{invalid json")
path = f.name
try:
load_config(path)
assert False, "Should have raised an exception"
except json.JSONDecodeError:
pass # Expected
finally:
os.unlink(path)
def test_load_config_missing_required_field():
"""Test that missing required field raises ValidationError."""
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
# Missing required fields like "companion"
json.dump({"vault": {"path": "~/test"}}, f)
path = f.name
try:
load_config(path)
assert False, "Should have raised an exception"
except Exception as e:
# Should be a Pydantic validation error
assert "companion" in str(e).lower() or "validation" in str(e).lower()
finally: finally:
os.unlink(path) os.unlink(path)