Compare commits
7 Commits
v0.1.0
...
2569ed4072
| Author | SHA1 | Date | |
|---|---|---|---|
| 2569ed4072 | |||
| 6086d86fb1 | |||
| 7e7689f0b4 | |||
| 01585e142a | |||
| 51d343086c | |||
| 0db85a770f | |||
| 4fc482ad73 |
@@ -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/
|
||||
|
||||
35
local-llm-prompt-manager/.gitea/workflows/ci.yml
Normal file
35
local-llm-prompt-manager/.gitea/workflows/ci.yml
Normal 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/
|
||||
228
local-llm-prompt-manager/tests/test_cli.py
Normal file
228
local-llm-prompt-manager/tests/test_cli.py
Normal 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
|
||||
184
local-llm-prompt-manager/tests/test_llm_clients.py
Normal file
184
local-llm-prompt-manager/tests/test_llm_clients.py
Normal 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)
|
||||
136
local-llm-prompt-manager/tests/test_models.py
Normal file
136
local-llm-prompt-manager/tests/test_models.py
Normal 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"
|
||||
170
local-llm-prompt-manager/tests/test_storage.py
Normal file
170
local-llm-prompt-manager/tests/test_storage.py
Normal 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"
|
||||
84
local-llm-prompt-manager/tests/test_templates.py
Normal file
84
local-llm-prompt-manager/tests/test_templates.py
Normal 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
|
||||
Reference in New Issue
Block a user