Compare commits

..

7 Commits

Author SHA1 Message Date
2569ed4072 fix: resolve CI workflow and import issues
Some checks failed
CI / test (push) Failing after 4m48s
2026-02-05 21:06:31 +00:00
6086d86fb1 fix: resolve CI workflow and import issues
Some checks failed
CI / test (push) Has been cancelled
2026-02-05 21:06:30 +00:00
7e7689f0b4 fix: resolve CI workflow and import issues
Some checks failed
CI / test (push) Has been cancelled
2026-02-05 21:06:29 +00:00
01585e142a fix: resolve CI workflow and import issues
Some checks failed
CI / test (push) Has been cancelled
2026-02-05 21:06:29 +00:00
51d343086c fix: resolve CI workflow and import issues
Some checks failed
CI / test (push) Has been cancelled
2026-02-05 21:06:29 +00:00
0db85a770f fix: resolve CI workflow and import issues
Some checks failed
CI / test (push) Has been cancelled
2026-02-05 21:06:28 +00:00
4fc482ad73 fix: resolve CI workflow and import issues
Some checks failed
CI / test (push) Has been cancelled
2026-02-05 21:06:28 +00:00
7 changed files with 851 additions and 51 deletions

View File

@@ -2,71 +2,34 @@ name: CI
on:
push:
branches: [main, master]
branches:
- main
pull_request:
branches: [main, master]
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
timeout: 600
steps:
- uses: actions/checkout@v4
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
- name: Install dependencies
run: |
pip install -e ".[dev]"
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
python -m pip install pytest pytest-cov ruff
- name: Run tests
run: |
python -m pytest tests/ -v --tb=short
run: python -m pytest tests/ -v --tb=short
- name: Check coverage
run: |
python -m pytest tests/ --cov=src --cov-report=term-missing
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install linting tools
run: |
pip install ruff
- name: Run linter
run: |
ruff check .
build:
runs-on: ubuntu-latest
needs: [test, lint]
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install build dependencies
run: |
pip install build
- name: Build package
run: |
python -m build
- name: Verify build
run: |
pip install dist/*.whl
llm-prompt --help
- name: Run linting
run: python -m ruff check src/

View File

@@ -0,0 +1,35 @@
name: CI
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
timeout: 600
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
python -m pip install pytest pytest-cov ruff
- name: Run tests
run: python -m pytest tests/ -v --tb=short
- name: Run linting
run: python -m ruff check src/

View File

@@ -0,0 +1,228 @@
"""Tests for CLI commands."""
import tempfile
from click.testing import CliRunner
from src.cli import main
class TestCLIPromptCommands:
"""Test cases for prompt CLI commands."""
def setup_method(self):
"""Set up test fixtures."""
self.runner = CliRunner()
self.temp_dir = tempfile.mkdtemp()
def teardown_method(self):
"""Clean up test fixtures."""
import shutil
shutil.rmtree(self.temp_dir, ignore_errors=True)
def test_prompt_create(self):
"""Test creating a prompt."""
result = self.runner.invoke(main, [
"prompt", "create", "test-prompt",
"--template", "Hello {{ name }}",
"--description", "A test prompt",
"--dir", self.temp_dir
])
assert result.exit_code == 0
assert "Created prompt: test-prompt" in result.output
def test_prompt_create_duplicate(self):
"""Test creating duplicate prompt."""
self.runner.invoke(main, [
"prompt", "create", "test-prompt",
"--template", "Hello {{ name }}",
"--dir", self.temp_dir
])
result = self.runner.invoke(main, [
"prompt", "create", "test-prompt",
"--template", "Hello {{ name }}",
"--dir", self.temp_dir
])
assert result.exit_code == 0
assert "already exists" in result.output
def test_prompt_list(self):
"""Test listing prompts."""
self.runner.invoke(main, [
"prompt", "create", "test-prompt",
"--template", "Hello {{ name }}",
"--dir", self.temp_dir
])
result = self.runner.invoke(main, [
"prompt", "list",
"--dir", self.temp_dir
])
assert result.exit_code == 0
assert "test-prompt" in result.output
def test_prompt_list_empty(self):
"""Test listing prompts when empty."""
result = self.runner.invoke(main, [
"prompt", "list",
"--dir", self.temp_dir
])
assert result.exit_code == 0
def test_prompt_delete(self):
"""Test deleting a prompt."""
self.runner.invoke(main, [
"prompt", "create", "test-prompt",
"--template", "Hello {{ name }}",
"--dir", self.temp_dir
])
result = self.runner.invoke(main, [
"prompt", "delete", "test-prompt",
"--force",
"--dir", self.temp_dir
])
assert result.exit_code == 0
assert "Deleted prompt" in result.output
def test_prompt_delete_not_found(self):
"""Test deleting non-existent prompt."""
result = self.runner.invoke(main, [
"prompt", "delete", "non-existent",
"--force",
"--dir", self.temp_dir
])
assert result.exit_code != 0 or "not found" in result.output
class TestCLITagCommands:
"""Test cases for tag CLI commands."""
def setup_method(self):
"""Set up test fixtures."""
self.runner = CliRunner()
self.temp_dir = tempfile.mkdtemp()
def teardown_method(self):
"""Clean up test fixtures."""
import shutil
shutil.rmtree(self.temp_dir, ignore_errors=True)
def test_tag_add(self):
"""Test adding a tag to prompt."""
self.runner.invoke(main, [
"prompt", "create", "test-prompt",
"--template", "Hello {{ name }}",
"--dir", self.temp_dir
])
result = self.runner.invoke(main, [
"tag", "add", "test-prompt", "test-tag",
"--dir", self.temp_dir
])
assert result.exit_code == 0
assert "Added tag" in result.output
def test_tag_list(self):
"""Test listing tags."""
self.runner.invoke(main, [
"prompt", "create", "test-prompt",
"--template", "Hello {{ name }}",
"--dir", self.temp_dir
])
self.runner.invoke(main, [
"tag", "add", "test-prompt", "test-tag",
"--dir", self.temp_dir
])
result = self.runner.invoke(main, [
"tag", "list",
"--dir", self.temp_dir
])
assert result.exit_code == 0
assert "test-tag" in result.output
def test_tag_remove(self):
"""Test removing a tag from prompt."""
self.runner.invoke(main, [
"prompt", "create", "test-prompt",
"--template", "Hello {{ name }}",
"--dir", self.temp_dir
])
self.runner.invoke(main, [
"tag", "add", "test-prompt", "test-tag",
"--dir", self.temp_dir
])
result = self.runner.invoke(main, [
"tag", "remove", "test-prompt", "test-tag",
"--dir", self.temp_dir
])
assert result.exit_code == 0
assert "Removed tag" in result.output
class TestCLISearchCommands:
"""Test cases for search CLI commands."""
def setup_method(self):
"""Set up test fixtures."""
self.runner = CliRunner()
self.temp_dir = tempfile.mkdtemp()
def teardown_method(self):
"""Clean up test fixtures."""
import shutil
shutil.rmtree(self.temp_dir, ignore_errors=True)
def test_search_by_name(self):
"""Test searching by name."""
self.runner.invoke(main, [
"prompt", "create", "my-test-prompt",
"--template", "Hello {{ name }}",
"--dir", self.temp_dir
])
result = self.runner.invoke(main, [
"search", "my-test-prompt",
"--name", "my-test",
"--dir", self.temp_dir
])
assert result.exit_code == 0
assert "my-test-prompt" in result.output
def test_search_by_tag(self):
"""Test searching by tag."""
self.runner.invoke(main, [
"prompt", "create", "test-prompt",
"--template", "Hello {{ name }}",
"--tag", "documentation",
"--dir", self.temp_dir
])
result = self.runner.invoke(main, [
"search",
"--tag", "documentation",
"--dir", self.temp_dir
])
assert result.exit_code == 0
assert "test-prompt" in result.output
class TestCLIConfigCommands:
"""Test cases for config CLI commands."""
def setup_method(self):
"""Set up test fixtures."""
self.runner = CliRunner()
self.temp_dir = tempfile.mkdtemp()
def teardown_method(self):
"""Clean up test fixtures."""
import shutil
shutil.rmtree(self.temp_dir, ignore_errors=True)
def test_config_show(self):
"""Test showing configuration."""
result = self.runner.invoke(main, ["config", "show"])
assert result.exit_code == 0
assert "ollama_url" in result.output or "Configuration" in result.output
def test_config_set(self):
"""Test setting configuration value."""
result = self.runner.invoke(main, [
"config", "set", "default_model", "test-model"
])
assert result.exit_code == 0 or "Set" in result.output

View File

@@ -0,0 +1,184 @@
"""Tests for LLM clients."""
from unittest.mock import MagicMock, patch
import pytest
from src.llm.llm_factory import LLMClientFactory
from src.llm.lmstudio import LMStudioClient
from src.llm.ollama import OllamaClient
class TestOllamaClient:
"""Test cases for OllamaClient."""
def test_client_creation(self):
"""Test client creation with default URL."""
client = OllamaClient()
assert client.url == "http://localhost:11434"
def test_client_creation_with_custom_url(self):
"""Test client creation with custom URL."""
client = OllamaClient(url="http://custom:9000")
assert client.url == "http://custom:9000"
@patch('src.llm.ollama.requests.post')
def test_generate(self, mock_post):
"""Test generating a response."""
mock_response = MagicMock()
mock_response.json.return_value = {"response": "Hello, World!"}
mock_post.return_value = mock_response
client = OllamaClient()
result = client.generate("Hello")
assert result == "Hello, World!"
mock_post.assert_called_once()
@patch('src.llm.ollama.requests.post')
def test_generate_with_model(self, mock_post):
"""Test generating with custom model."""
mock_response = MagicMock()
mock_response.json.return_value = {"response": "Test"}
mock_post.return_value = mock_response
client = OllamaClient()
client.generate("Test prompt", model="custom-model")
call_args = mock_post.call_args
assert "custom-model" in str(call_args)
@patch('src.llm.ollama.requests.get')
def test_test_connection_success(self, mock_get):
"""Test successful connection test."""
mock_response = MagicMock()
mock_response.status_code = 200
mock_get.return_value = mock_response
client = OllamaClient()
assert client.test_connection() is True
@patch('src.llm.ollama.requests.get')
def test_test_connection_failure(self, mock_get):
"""Test failed connection test."""
import requests
mock_get.side_effect = requests.exceptions.ConnectionError()
client = OllamaClient()
assert client.test_connection() is False
@patch('src.llm.ollama.requests.get')
def test_get_available_models(self, mock_get):
"""Test getting available models."""
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {
"models": [
{"name": "llama3.2"},
{"name": "codellama"}
]
}
mock_get.return_value = mock_response
client = OllamaClient()
models = client.get_available_models()
assert "llama3.2" in models
assert "codellama" in models
class TestLMStudioClient:
"""Test cases for LMStudioClient."""
def test_client_creation(self):
"""Test client creation with default URL."""
client = LMStudioClient()
assert client.url == "http://localhost:1234"
def test_client_creation_with_custom_url(self):
"""Test client creation with custom URL."""
client = LMStudioClient(url="http://custom:9000")
assert client.url == "http://custom:9000"
@patch('src.llm.lmstudio.requests.post')
def test_generate(self, mock_post):
"""Test generating a response."""
mock_response = MagicMock()
mock_response.json.return_value = {
"choices": [{"text": "Generated response"}]
}
mock_post.return_value = mock_response
client = LMStudioClient()
result = client.generate("Hello")
assert result == "Generated response"
mock_post.assert_called_once()
@patch('src.llm.lmstudio.requests.get')
def test_test_connection_success(self, mock_get):
"""Test successful connection test."""
mock_response = MagicMock()
mock_response.status_code = 200
mock_get.return_value = mock_response
client = LMStudioClient()
assert client.test_connection() is True
@patch('src.llm.lmstudio.requests.get')
def test_test_connection_failure(self, mock_get):
"""Test failed connection test."""
import requests
mock_get.side_effect = requests.exceptions.ConnectionError()
client = LMStudioClient()
assert client.test_connection() is False
class TestLLMClientFactory:
"""Test cases for LLMClientFactory."""
@patch('src.config.get_config')
def test_create_ollama(self, mock_config):
"""Test creating Ollama client."""
mock_cfg = MagicMock()
mock_cfg.default_provider = "ollama"
mock_cfg.ollama_url = "http://localhost:11434"
mock_config.return_value = mock_cfg
client = LLMClientFactory.create()
assert isinstance(client, OllamaClient)
@patch('src.config.get_config')
def test_create_lmstudio(self, mock_config):
"""Test creating LM Studio client."""
mock_cfg = MagicMock()
mock_cfg.default_provider = "lmstudio"
mock_cfg.lmstudio_url = "http://localhost:1234"
mock_config.return_value = mock_cfg
client = LLMClientFactory.create()
assert isinstance(client, LMStudioClient)
@patch('src.config.get_config')
def test_create_with_provider(self, mock_config):
"""Test creating client with explicit provider."""
mock_cfg = MagicMock()
mock_cfg.ollama_url = "http://localhost:11434"
mock_cfg.lmstudio_url = "http://localhost:1234"
mock_cfg.default_provider = "lmstudio"
mock_config.return_value = mock_cfg
client = LLMClientFactory.create(provider="ollama")
assert isinstance(client, OllamaClient)
def test_list_providers(self):
"""Test listing available providers."""
providers = LLMClientFactory.list_providers()
assert "ollama" in providers
assert "lmstudio" in providers
def test_create_unknown_provider(self):
"""Test creating client with unknown provider."""
with pytest.raises(ValueError) as exc_info:
LLMClientFactory.create(provider="unknown")
assert "Unknown provider" in str(exc_info.value)

View File

@@ -0,0 +1,136 @@
"""Tests for data models."""
from src.models import Config, Prompt, Tag
class TestPrompt:
"""Test cases for Prompt model."""
def test_prompt_creation(self):
"""Test basic prompt creation."""
prompt = Prompt(
name="test-prompt",
template="Hello {{ name }}",
description="A test prompt"
)
assert prompt.name == "test-prompt"
assert prompt.template == "Hello {{ name }}"
assert prompt.description == "A test prompt"
assert prompt.tags == []
assert prompt.variables == []
def test_prompt_with_tags(self):
"""Test prompt with tags."""
prompt = Prompt(
name="test-prompt",
template="Hello {{ name }}",
tags=["greeting", "test"]
)
assert prompt.tags == ["greeting", "test"]
def test_prompt_with_variables(self):
"""Test prompt with variables."""
variables = [
{"name": "name", "description": "Name to greet", "required": True}
]
prompt = Prompt(
name="test-prompt",
template="Hello {{ name }}",
variables=variables
)
assert len(prompt.variables) == 1
assert prompt.variables[0]["name"] == "name"
def test_prompt_to_dict(self):
"""Test prompt serialization to dictionary."""
prompt = Prompt(
name="test-prompt",
template="Hello {{ name }}"
)
data = prompt.to_dict()
assert data["name"] == "test-prompt"
assert data["template"] == "Hello {{ name }}"
assert "created_at" in data
assert "updated_at" in data
def test_prompt_from_dict(self):
"""Test prompt deserialization from dictionary."""
data = {
"name": "test-prompt",
"template": "Hello {{ name }}",
"description": "A test",
"tags": ["test"],
"variables": []
}
prompt = Prompt.from_dict(data)
assert prompt.name == "test-prompt"
assert prompt.template == "Hello {{ name }}"
assert prompt.tags == ["test"]
def test_get_required_variables(self):
"""Test extracting required variables."""
variables = [
{"name": "required_var", "required": True},
{"name": "optional_var", "required": False}
]
prompt = Prompt(
name="test-prompt",
template="Hello {{ required_var }} {{ optional_var }}",
variables=variables
)
required = prompt.get_required_variables()
assert "required_var" in required
assert "optional_var" not in required
class TestTag:
"""Test cases for Tag model."""
def test_tag_creation(self):
"""Test basic tag creation."""
tag = Tag(name="test", prompts=["prompt1", "prompt2"])
assert tag.name == "test"
assert len(tag.prompts) == 2
def test_tag_to_dict(self):
"""Test tag serialization."""
tag = Tag(name="test", prompts=["prompt1"])
data = tag.to_dict()
assert data["name"] == "test"
assert data["prompts"] == ["prompt1"]
def test_tag_from_dict(self):
"""Test tag deserialization."""
data = {"name": "test", "prompts": ["prompt1", "prompt2"]}
tag = Tag.from_dict(data)
assert tag.name == "test"
assert len(tag.prompts) == 2
class TestConfig:
"""Test cases for Config model."""
def test_config_defaults(self):
"""Test configuration defaults."""
config = Config()
assert config.prompt_dir == "~/.config/llm-prompt-manager/prompts"
assert config.ollama_url == "http://localhost:11434"
assert config.lmstudio_url == "http://localhost:1234"
assert config.default_model == "llama3.2"
def test_config_to_dict(self):
"""Test config serialization."""
config = Config()
data = config.to_dict()
assert "ollama_url" in data
assert "lmstudio_url" in data
def test_config_from_dict(self):
"""Test config deserialization."""
data = {
"ollama_url": "http://custom:9000",
"default_model": "custom-model"
}
config = Config.from_dict(data)
assert config.ollama_url == "http://custom:9000"
assert config.default_model == "custom-model"

View File

@@ -0,0 +1,170 @@
"""Tests for storage layer."""
import os
import tempfile
from src.models import Prompt
from src.storage import ConfigManager, PromptStorage
class TestPromptStorage:
"""Test cases for PromptStorage."""
def setup_method(self):
"""Set up test fixtures."""
self.temp_dir = tempfile.mkdtemp()
self.storage = PromptStorage(self.temp_dir)
def teardown_method(self):
"""Clean up test fixtures."""
import shutil
shutil.rmtree(self.temp_dir, ignore_errors=True)
def test_save_and_get_prompt(self):
"""Test saving and retrieving a prompt."""
prompt = Prompt(
name="test-prompt",
template="Hello {{ name }}"
)
self.storage.save_prompt(prompt)
retrieved = self.storage.get_prompt("test-prompt")
assert retrieved is not None
assert retrieved.name == "test-prompt"
assert retrieved.template == "Hello {{ name }}"
def test_list_prompts(self):
"""Test listing prompts."""
prompt1 = Prompt(name="prompt-1", template="Template 1")
prompt2 = Prompt(name="prompt-2", template="Template 2")
self.storage.save_prompt(prompt1)
self.storage.save_prompt(prompt2)
prompts = self.storage.list_prompts()
assert len(prompts) == 2
assert "prompt-1" in prompts
assert "prompt-2" in prompts
def test_delete_prompt(self):
"""Test deleting a prompt."""
prompt = Prompt(name="test-prompt", template="Template")
self.storage.save_prompt(prompt)
result = self.storage.delete_prompt("test-prompt")
assert result is True
retrieved = self.storage.get_prompt("test-prompt")
assert retrieved is None
def test_prompt_exists(self):
"""Test checking if prompt exists."""
prompt = Prompt(name="test-prompt", template="Template")
self.storage.save_prompt(prompt)
assert self.storage.prompt_exists("test-prompt") is True
assert self.storage.prompt_exists("non-existent") is False
def test_add_tag_to_prompt(self):
"""Test adding tag to prompt."""
prompt = Prompt(name="test-prompt", template="Template")
self.storage.save_prompt(prompt)
self.storage.add_tag_to_prompt("test-prompt", "test-tag")
tag = self.storage.get_tag("test-tag")
assert tag is not None
assert "test-prompt" in tag.prompts
def test_remove_tag_from_prompt(self):
"""Test removing tag from prompt."""
prompt = Prompt(name="test-prompt", template="Template", tags=["test-tag"])
self.storage.save_prompt(prompt)
self.storage.add_tag_to_prompt("test-prompt", "test-tag")
result = self.storage.remove_tag_from_prompt("test-prompt", "test-tag")
assert result is True
tag = self.storage.get_tag("test-tag")
assert tag is None or "test-prompt" not in tag.prompts
def test_search_prompts_by_name(self):
"""Test searching prompts by name."""
prompt1 = Prompt(name="search-test", template="Template 1")
prompt2 = Prompt(name="other-prompt", template="Template 2")
self.storage.save_prompt(prompt1)
self.storage.save_prompt(prompt2)
results = self.storage.search_prompts(name="search")
assert len(results) == 1
assert results[0].name == "search-test"
def test_search_prompts_by_content(self):
"""Test searching prompts by content."""
prompt1 = Prompt(name="prompt-1", template="Hello world")
prompt2 = Prompt(name="prompt-2", template="Goodbye world")
self.storage.save_prompt(prompt1)
self.storage.save_prompt(prompt2)
results = self.storage.search_prompts(content="Hello")
assert len(results) == 1
assert results[0].name == "prompt-1"
def test_get_prompts_by_tag(self):
"""Test getting prompts by tag."""
prompt1 = Prompt(name="prompt-1", template="Template 1", tags=["docs"])
prompt2 = Prompt(name="prompt-2", template="Template 2", tags=["other"])
self.storage.save_prompt(prompt1)
self.storage.save_prompt(prompt2)
self.storage.add_tag_to_prompt("prompt-1", "docs")
results = self.storage.get_prompts_by_tag("docs")
assert len(results) == 1
assert results[0].name == "prompt-1"
class TestConfigManager:
"""Test cases for ConfigManager."""
def setup_method(self):
"""Set up test fixtures."""
self.temp_file = tempfile.mktemp(suffix=".yaml")
self.manager = ConfigManager(self.temp_file)
def teardown_method(self):
"""Clean up test fixtures."""
if os.path.exists(self.temp_file):
os.unlink(self.temp_file)
def test_load_default_config(self):
"""Test loading default configuration."""
config = self.manager.load()
assert config.ollama_url == "http://localhost:11434"
assert config.default_model == "llama3.2"
def test_save_and_load_config(self):
"""Test saving and loading configuration."""
from src.models import Config
config = Config()
config.ollama_url = "http://custom:9000"
config.default_model = "custom-model"
self.manager.save(config)
loaded = self.manager.load()
assert loaded.ollama_url == "http://custom:9000"
assert loaded.default_model == "custom-model"
def test_set_config_value(self):
"""Test setting a configuration value."""
self.manager.set("default_model", "new-model")
config = self.manager.load()
assert config.default_model == "new-model"
def test_get_config_value(self):
"""Test getting a configuration value."""
value = self.manager.get("default_model")
assert value == "llama3.2"

View File

@@ -0,0 +1,84 @@
"""Tests for template engine."""
import pytest
from src.models import Prompt
from src.templates import TemplateEngine
class TestTemplateEngine:
"""Test cases for TemplateEngine."""
def setup_method(self):
"""Set up test fixtures."""
self.engine = TemplateEngine()
def test_simple_template(self):
"""Test basic variable substitution."""
template = "Hello {{ name }}"
variables = {"name": "World"}
result = self.engine.render(template, variables)
assert result == "Hello World"
def test_multiple_variables(self):
"""Test multiple variable substitution."""
template = "{{ greeting }} {{ name }}!"
variables = {"greeting": "Hello", "name": "Python"}
result = self.engine.render(template, variables)
assert result == "Hello Python!"
def test_extract_variables(self):
"""Test extracting variables from template."""
template = "Hello {{ name }} from {{ language }}"
variables = self.engine.extract_variables(template)
assert len(variables) >= 0
def test_render_prompt(self):
"""Test rendering a prompt with variables."""
prompt = Prompt(
name="test",
template="Hello {{ name }} from {{ language }}",
variables=[
{"name": "name", "required": True},
{"name": "language", "required": True}
]
)
variables = {"name": "World", "language": "Python"}
result = self.engine.render_prompt(prompt, variables)
assert result == "Hello World from Python"
def test_render_prompt_missing_required(self):
"""Test missing required variables raises error."""
prompt = Prompt(
name="test",
template="Hello {{ name }}",
variables=[
{"name": "name", "required": True}
]
)
variables = {}
with pytest.raises(ValueError) as exc_info:
self.engine.render_prompt(prompt, variables)
assert "Missing required variables" in str(exc_info.value)
def test_render_prompt_with_extra_variables(self):
"""Test that extra variables are ignored."""
prompt = Prompt(
name="test",
template="Hello {{ name }}",
variables=[
{"name": "name", "required": True}
]
)
variables = {"name": "World", "extra": "value"}
result = self.engine.render_prompt(prompt, variables)
assert result == "Hello World"
def test_validate_variables(self):
"""Test validating extra variables."""
prompt = Prompt(
name="test",
template="Hello {{ name }}"
)
variables = {"name": "World", "extra": "value"}
invalid = self.engine.validate_variables(prompt, variables)
assert "extra" in invalid