generated from coulomb/repo-seed
WP-0001 — Foundation & GAAF Baseline - SCOPE.md, ARCHITECTURE-LAYERS.md, contracts/ tree - .claude/rules/ stubs filled (architecture, stack, boundary) - 57 tests (pytest), pyproject.toml with ruff+mypy, CI workflow WP-0002 — Core Extensions (FR-4 + FR-3) - FR-4: BudgetTracker (thread-safe) + LLMBudgetExceededError + optional RunConfig.budget_tracker + enforcement in all adapters - FR-3: async_execute_prompt on LLMAdapter ABC (asyncio.to_thread fallback) + native asyncio.create_subprocess_exec in ClaudeCodeAdapter 81 tests passing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
78 lines
3.1 KiB
Python
78 lines
3.1 KiB
Python
"""
|
|
Tests for MockLLMAdapter and ErrorLLMAdapter (Core adapter utilities).
|
|
"""
|
|
|
|
import pytest
|
|
from llm_connect.adapter import MockLLMAdapter, ErrorLLMAdapter
|
|
from llm_connect.models import RunConfig, LLMResponse
|
|
|
|
|
|
class TestMockLLMAdapter:
|
|
def test_returns_mock_response(self, mock_adapter, run_config):
|
|
response = mock_adapter.execute_prompt("hello", run_config)
|
|
assert response.content == "test response"
|
|
|
|
def test_returns_llm_response(self, mock_adapter, run_config):
|
|
response = mock_adapter.execute_prompt("hello", run_config)
|
|
assert isinstance(response, LLMResponse)
|
|
|
|
def test_call_count_increments(self, mock_adapter, run_config):
|
|
assert mock_adapter.call_count == 0
|
|
mock_adapter.execute_prompt("a", run_config)
|
|
mock_adapter.execute_prompt("b", run_config)
|
|
assert mock_adapter.call_count == 2
|
|
|
|
def test_records_last_prompt(self, mock_adapter, run_config):
|
|
mock_adapter.execute_prompt("my prompt", run_config)
|
|
assert mock_adapter.last_prompt == "my prompt"
|
|
|
|
def test_records_last_config(self, mock_adapter, run_config):
|
|
mock_adapter.execute_prompt("x", run_config)
|
|
assert mock_adapter.last_config is run_config
|
|
|
|
def test_reset_clears_state(self, mock_adapter, run_config):
|
|
mock_adapter.execute_prompt("x", run_config)
|
|
mock_adapter.reset()
|
|
assert mock_adapter.call_count == 0
|
|
assert mock_adapter.last_prompt is None
|
|
assert mock_adapter.last_config is None
|
|
|
|
def test_validate_config_always_true(self, mock_adapter, run_config):
|
|
assert mock_adapter.validate_config(run_config) is True
|
|
|
|
def test_usage_contains_expected_keys(self, mock_adapter, run_config):
|
|
response = mock_adapter.execute_prompt("prompt text", run_config)
|
|
assert "prompt_tokens" in response.usage
|
|
assert "completion_tokens" in response.usage
|
|
assert "total_tokens" in response.usage
|
|
|
|
def test_custom_response_text(self, run_config):
|
|
adapter = MockLLMAdapter(mock_response="custom answer")
|
|
response = adapter.execute_prompt("q", run_config)
|
|
assert response.content == "custom answer"
|
|
|
|
def test_default_response_text(self, run_config):
|
|
adapter = MockLLMAdapter()
|
|
response = adapter.execute_prompt("q", run_config)
|
|
assert response.content == "Mock LLM response"
|
|
|
|
def test_metadata_marks_as_mock(self, mock_adapter, run_config):
|
|
response = mock_adapter.execute_prompt("q", run_config)
|
|
assert response.metadata.get("mock") is True
|
|
|
|
|
|
class TestErrorLLMAdapter:
|
|
def test_raises_on_execute(self, run_config):
|
|
adapter = ErrorLLMAdapter()
|
|
with pytest.raises(RuntimeError):
|
|
adapter.execute_prompt("q", run_config)
|
|
|
|
def test_raises_with_custom_message(self, run_config):
|
|
adapter = ErrorLLMAdapter(error_message="boom")
|
|
with pytest.raises(RuntimeError, match="boom"):
|
|
adapter.execute_prompt("q", run_config)
|
|
|
|
def test_validate_config_returns_true(self, run_config):
|
|
adapter = ErrorLLMAdapter()
|
|
assert adapter.validate_config(run_config) is True
|