CmdForge/tests/test_settings.py

194 lines
6.8 KiB
Python

"""Tests for tool settings functionality."""
import pytest
import tempfile
import shutil
from pathlib import Path
import yaml
from cmdforge.tool import ensure_settings, save_tool, load_tool, Tool
from cmdforge.runner import substitute_variables
class TestEnsureSettings:
"""Tests for ensure_settings helper function."""
def test_creates_settings_from_defaults(self, tmp_path):
"""Settings.yaml created when defaults.yaml exists."""
tool_dir = tmp_path / "my-tool"
tool_dir.mkdir()
# Create defaults.yaml
defaults_path = tool_dir / "defaults.yaml"
defaults_path.write_text("backend: piper\nendpoint: http://localhost:5001\n")
# Ensure settings.yaml doesn't exist yet
settings_path = tool_dir / "settings.yaml"
assert not settings_path.exists()
# Call ensure_settings
result = ensure_settings(tool_dir)
# Check settings.yaml was created
assert result == settings_path
assert settings_path.exists()
assert settings_path.read_text() == defaults_path.read_text()
def test_preserves_existing_settings(self, tmp_path):
"""Existing settings.yaml not overwritten."""
tool_dir = tmp_path / "my-tool"
tool_dir.mkdir()
# Create defaults.yaml
defaults_path = tool_dir / "defaults.yaml"
defaults_path.write_text("backend: piper\n")
# Create existing settings.yaml with different content
settings_path = tool_dir / "settings.yaml"
settings_path.write_text("backend: google\napi_key: my-key\n")
# Call ensure_settings
result = ensure_settings(tool_dir)
# Check settings.yaml was not overwritten
assert result == settings_path
assert settings_path.read_text() == "backend: google\napi_key: my-key\n"
def test_no_defaults_returns_none(self, tmp_path):
"""Returns None when no defaults.yaml exists."""
tool_dir = tmp_path / "my-tool"
tool_dir.mkdir()
result = ensure_settings(tool_dir)
assert result is None
assert not (tool_dir / "settings.yaml").exists()
def test_existing_settings_no_defaults_returns_path(self, tmp_path):
"""Returns settings path when settings exists but defaults doesn't."""
tool_dir = tmp_path / "my-tool"
tool_dir.mkdir()
# Create only settings.yaml
settings_path = tool_dir / "settings.yaml"
settings_path.write_text("custom: value\n")
result = ensure_settings(tool_dir)
assert result == settings_path
class TestSubstituteVariablesSettings:
"""Tests for {settings.key} substitution in templates."""
def test_settings_scalar_string(self):
"""Template substitution works for string settings."""
variables = {"settings": {"backend": "piper"}}
result = substitute_variables("Using {settings.backend}", variables)
assert result == "Using piper"
def test_settings_scalar_int(self):
"""Template substitution works for int settings."""
variables = {"settings": {"port": 5001}}
result = substitute_variables("Port: {settings.port}", variables)
assert result == "Port: 5001"
def test_settings_scalar_float(self):
"""Template substitution works for float settings."""
variables = {"settings": {"threshold": 0.75}}
result = substitute_variables("Threshold: {settings.threshold}", variables)
assert result == "Threshold: 0.75"
def test_settings_scalar_bool(self):
"""Template substitution works for bool settings."""
variables = {"settings": {"enabled": True}}
result = substitute_variables("Enabled: {settings.enabled}", variables)
assert result == "Enabled: True"
def test_settings_nonscalar_unchanged(self, capsys):
"""Non-scalar settings leave placeholder unchanged and warn."""
variables = {"settings": {"items": ["a", "b", "c"]}}
result = substitute_variables("Items: {settings.items}", variables, warn_non_scalar=True)
# Placeholder should be unchanged
assert result == "Items: {settings.items}"
# Should have printed a warning
captured = capsys.readouterr()
assert "not a scalar value" in captured.err
def test_settings_missing_key_unchanged(self):
"""Missing settings key leaves placeholder unchanged."""
variables = {"settings": {"backend": "piper"}}
result = substitute_variables("API: {settings.api_key}", variables)
assert result == "API: {settings.api_key}"
def test_settings_empty_dict(self):
"""Empty settings dict leaves placeholders unchanged."""
variables = {"settings": {}}
result = substitute_variables("Backend: {settings.backend}", variables)
assert result == "Backend: {settings.backend}"
def test_mixed_variables_and_settings(self):
"""Regular variables and settings work together."""
variables = {
"input": "Hello",
"name": "World",
"settings": {"backend": "piper"}
}
result = substitute_variables(
"{input} {name}! Using {settings.backend}.",
variables
)
assert result == "Hello World! Using piper."
def test_escaped_braces_preserved(self):
"""Escaped braces {{}} work with settings."""
variables = {"settings": {"key": "value"}}
result = substitute_variables(
"{{literal}} and {settings.key}",
variables
)
assert result == "{literal} and value"
class TestRunnerLoadsSettings:
"""Integration tests for runner loading settings."""
def test_settings_available_in_code_step(self, monkeypatch, tmp_path):
"""Settings dict is available in code steps."""
from cmdforge.tool import TOOLS_DIR
from cmdforge.runner import run_tool
# Patch TOOLS_DIR
monkeypatch.setattr("cmdforge.tool.TOOLS_DIR", tmp_path)
# Create tool directory with config and settings
tool_dir = tmp_path / "test-settings-tool"
tool_dir.mkdir()
config = {
"name": "test-settings-tool",
"description": "Test tool",
"steps": [
{
"type": "code",
"code": "result = f\"backend={settings.get('backend', 'none')}\"",
"output_var": "result"
}
],
"output": "{result}"
}
(tool_dir / "config.yaml").write_text(yaml.dump(config))
settings = {"backend": "piper", "api_key": "test-key"}
(tool_dir / "settings.yaml").write_text(yaml.dump(settings))
# Load and run the tool
tool = load_tool("test-settings-tool")
output, exit_code = run_tool(tool, "test input", {})
assert exit_code == 0
assert "backend=piper" in output