mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-05 05:16:03 +00:00
test(unit): add config loader unit tests
U-005: Config Loader tests - Valid YAML config loading - Valid JSON config loading - Invalid YAML/JSON error behavior - Missing config file creation from template - Template completion for missing keys - ConfigManager load/dump operations - Exists check for both YAML and JSON All tests use tmp_path fixture, no real project config. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
310
tests/unit_tests/config/test_config_loader.py
Normal file
310
tests/unit_tests/config/test_config_loader.py
Normal file
@@ -0,0 +1,310 @@
|
||||
"""
|
||||
Unit tests for configuration loading and overrides.
|
||||
|
||||
Tests cover:
|
||||
- Valid YAML config loading
|
||||
- Valid JSON config loading
|
||||
- Invalid YAML/JSON error behavior
|
||||
- Missing config file behavior
|
||||
- Template completion
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
import os
|
||||
import json
|
||||
|
||||
from langbot.pkg.config.impls.yaml import YAMLConfigFile
|
||||
from langbot.pkg.config.impls.json import JSONConfigFile
|
||||
from langbot.pkg.config.manager import ConfigManager
|
||||
|
||||
|
||||
class TestYAMLConfigFile:
|
||||
"""Tests for YAML config file handling."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_valid_yaml_loads(self, tmp_path):
|
||||
"""Valid YAML config should load correctly."""
|
||||
config_file = tmp_path / "test_config.yaml"
|
||||
|
||||
# Write valid YAML
|
||||
config_file.write_text("""
|
||||
name: test_app
|
||||
version: 1.0
|
||||
settings:
|
||||
debug: true
|
||||
port: 8080
|
||||
""")
|
||||
|
||||
yaml_file = YAMLConfigFile(
|
||||
str(config_file),
|
||||
template_data={'name': 'default', 'version': '0.1'},
|
||||
)
|
||||
|
||||
result = await yaml_file.load(completion=False)
|
||||
|
||||
assert result['name'] == 'test_app'
|
||||
assert result['version'] == 1.0
|
||||
assert result['settings']['debug'] is True
|
||||
assert result['settings']['port'] == 8080
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_invalid_yaml_raises_error(self, tmp_path):
|
||||
"""Invalid YAML should raise clear error."""
|
||||
config_file = tmp_path / "invalid.yaml"
|
||||
|
||||
# Write invalid YAML (unclosed bracket)
|
||||
config_file.write_text("""
|
||||
name: test
|
||||
settings:
|
||||
- item1
|
||||
- item2
|
||||
- [unclosed
|
||||
""")
|
||||
|
||||
yaml_file = YAMLConfigFile(
|
||||
str(config_file),
|
||||
template_data={'name': 'default'},
|
||||
)
|
||||
|
||||
with pytest.raises(Exception, match="Syntax error"):
|
||||
await yaml_file.load(completion=False)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_missing_config_creates_from_template(self, tmp_path):
|
||||
"""Missing config file should be created from template."""
|
||||
config_file = tmp_path / "new_config.yaml"
|
||||
|
||||
# File doesn't exist yet
|
||||
assert not config_file.exists()
|
||||
|
||||
yaml_file = YAMLConfigFile(
|
||||
str(config_file),
|
||||
template_data={'name': 'new_app', 'version': '1.0'},
|
||||
)
|
||||
|
||||
result = await yaml_file.load()
|
||||
|
||||
assert config_file.exists()
|
||||
assert result['name'] == 'new_app'
|
||||
assert result['version'] == '1.0'
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_template_completion(self, tmp_path):
|
||||
"""Config should be completed with template defaults."""
|
||||
config_file = tmp_path / "partial.yaml"
|
||||
|
||||
# Write partial config missing some template keys
|
||||
config_file.write_text("""
|
||||
name: custom_name
|
||||
""")
|
||||
|
||||
yaml_file = YAMLConfigFile(
|
||||
str(config_file),
|
||||
template_data={'name': 'default_name', 'version': '2.0', 'debug': False},
|
||||
)
|
||||
|
||||
result = await yaml_file.load(completion=True)
|
||||
|
||||
# Existing key preserved
|
||||
assert result['name'] == 'custom_name'
|
||||
# Missing keys filled from template
|
||||
assert result['version'] == '2.0'
|
||||
assert result['debug'] is False
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_yaml_save(self, tmp_path):
|
||||
"""YAML config can be saved."""
|
||||
config_file = tmp_path / "save_test.yaml"
|
||||
|
||||
yaml_file = YAMLConfigFile(
|
||||
str(config_file),
|
||||
template_data={'name': 'test'},
|
||||
)
|
||||
|
||||
await yaml_file.save({'name': 'saved_app', 'new_key': 'new_value'})
|
||||
|
||||
assert config_file.exists()
|
||||
content = config_file.read_text()
|
||||
assert 'saved_app' in content
|
||||
assert 'new_key' in content
|
||||
|
||||
def test_yaml_save_sync(self, tmp_path):
|
||||
"""YAML config can be saved synchronously."""
|
||||
config_file = tmp_path / "sync_save.yaml"
|
||||
|
||||
yaml_file = YAMLConfigFile(
|
||||
str(config_file),
|
||||
template_data={'name': 'test'},
|
||||
)
|
||||
|
||||
yaml_file.save_sync({'name': 'sync_saved'})
|
||||
|
||||
assert config_file.exists()
|
||||
content = config_file.read_text()
|
||||
assert 'sync_saved' in content
|
||||
|
||||
|
||||
class TestJSONConfigFile:
|
||||
"""Tests for JSON config file handling."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_valid_json_loads(self, tmp_path):
|
||||
"""Valid JSON config should load correctly."""
|
||||
config_file = tmp_path / "test_config.json"
|
||||
|
||||
# Write valid JSON
|
||||
config_file.write_text(json.dumps({
|
||||
'name': 'json_app',
|
||||
'version': '1.0',
|
||||
'settings': {'debug': True, 'port': 8080},
|
||||
}))
|
||||
|
||||
json_file = JSONConfigFile(
|
||||
str(config_file),
|
||||
template_data={'name': 'default', 'version': '0.1'},
|
||||
)
|
||||
|
||||
result = await json_file.load(completion=False)
|
||||
|
||||
assert result['name'] == 'json_app'
|
||||
assert result['version'] == '1.0'
|
||||
assert result['settings']['debug'] is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_invalid_json_raises_error(self, tmp_path):
|
||||
"""Invalid JSON should raise clear error."""
|
||||
config_file = tmp_path / "invalid.json"
|
||||
|
||||
# Write invalid JSON (missing closing brace)
|
||||
config_file.write_text('{"name": "test", "unclosed": ')
|
||||
|
||||
json_file = JSONConfigFile(
|
||||
str(config_file),
|
||||
template_data={'name': 'default'},
|
||||
)
|
||||
|
||||
with pytest.raises(Exception, match="Syntax error"):
|
||||
await json_file.load(completion=False)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_missing_json_creates_from_template(self, tmp_path):
|
||||
"""Missing JSON file should be created from template."""
|
||||
config_file = tmp_path / "new_config.json"
|
||||
|
||||
json_file = JSONConfigFile(
|
||||
str(config_file),
|
||||
template_data={'name': 'new_json_app', 'version': '1.0'},
|
||||
)
|
||||
|
||||
result = await json_file.load()
|
||||
|
||||
assert config_file.exists()
|
||||
assert result['name'] == 'new_json_app'
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_json_save(self, tmp_path):
|
||||
"""JSON config can be saved."""
|
||||
config_file = tmp_path / "save_test.json"
|
||||
|
||||
json_file = JSONConfigFile(
|
||||
str(config_file),
|
||||
template_data={'name': 'test'},
|
||||
)
|
||||
|
||||
await json_file.save({'name': 'saved_json', 'new_key': 'value'})
|
||||
|
||||
assert config_file.exists()
|
||||
content = config_file.read_text()
|
||||
data = json.loads(content)
|
||||
assert data['name'] == 'saved_json'
|
||||
|
||||
|
||||
class TestConfigManager:
|
||||
"""Tests for ConfigManager."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_config_manager_load(self, tmp_path):
|
||||
"""ConfigManager loads config correctly."""
|
||||
config_file = tmp_path / "manager_test.yaml"
|
||||
config_file.write_text('name: managed_app\nversion: "1.0"\n')
|
||||
|
||||
yaml_file = YAMLConfigFile(
|
||||
str(config_file),
|
||||
template_data={'name': 'default', 'version': '0.1'},
|
||||
)
|
||||
|
||||
manager = ConfigManager(yaml_file)
|
||||
await manager.load_config()
|
||||
|
||||
assert manager.data['name'] == 'managed_app'
|
||||
assert manager.data['version'] == '1.0'
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_config_manager_dump(self, tmp_path):
|
||||
"""ConfigManager can dump config."""
|
||||
config_file = tmp_path / "dump_test.yaml"
|
||||
|
||||
yaml_file = YAMLConfigFile(
|
||||
str(config_file),
|
||||
template_data={'name': 'default'},
|
||||
)
|
||||
|
||||
manager = ConfigManager(yaml_file)
|
||||
manager.data = {'name': 'dumped', 'new_field': 'value'}
|
||||
|
||||
await manager.dump_config()
|
||||
|
||||
content = config_file.read_text()
|
||||
assert 'dumped' in content
|
||||
|
||||
def test_config_manager_dump_sync(self, tmp_path):
|
||||
"""ConfigManager can dump config synchronously."""
|
||||
config_file = tmp_path / "sync_dump.yaml"
|
||||
|
||||
yaml_file = YAMLConfigFile(
|
||||
str(config_file),
|
||||
template_data={'name': 'default'},
|
||||
)
|
||||
|
||||
manager = ConfigManager(yaml_file)
|
||||
manager.data = {'name': 'sync_dumped'}
|
||||
|
||||
manager.dump_config_sync()
|
||||
|
||||
assert config_file.exists()
|
||||
|
||||
|
||||
class TestConfigExists:
|
||||
"""Tests for config file existence check."""
|
||||
|
||||
def test_yaml_exists_true(self, tmp_path):
|
||||
"""exists() returns True for existing file."""
|
||||
config_file = tmp_path / "exists.yaml"
|
||||
config_file.write_text('name: test')
|
||||
|
||||
yaml_file = YAMLConfigFile(str(config_file), template_data={})
|
||||
assert yaml_file.exists() is True
|
||||
|
||||
def test_yaml_exists_false(self, tmp_path):
|
||||
"""exists() returns False for missing file."""
|
||||
config_file = tmp_path / "missing.yaml"
|
||||
|
||||
yaml_file = YAMLConfigFile(str(config_file), template_data={})
|
||||
assert yaml_file.exists() is False
|
||||
|
||||
def test_json_exists_true(self, tmp_path):
|
||||
"""exists() returns True for existing JSON file."""
|
||||
config_file = tmp_path / "exists.json"
|
||||
config_file.write_text('{}')
|
||||
|
||||
json_file = JSONConfigFile(str(config_file), template_data={})
|
||||
assert json_file.exists() is True
|
||||
|
||||
def test_json_exists_false(self, tmp_path):
|
||||
"""exists() returns False for missing JSON file."""
|
||||
config_file = tmp_path / "missing.json"
|
||||
|
||||
json_file = JSONConfigFile(str(config_file), template_data={})
|
||||
assert json_file.exists() is False
|
||||
Reference in New Issue
Block a user