194 lines
6.8 KiB
Python
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
|