Implements The Housekeeper, a new expert agent for home automation following the Librarian pattern. Communicates with core-api service which wraps Home Assistant REST API. New agent features: - CoreAPIClient with 13 home automation methods - 13 tools: list_areas, list_devices, get_device_state, turn_on, turn_off, toggle, list_scenes, activate_scene, list_scripts, run_script, list_automations, toggle_automation, get_history - PydanticAI agent with butler-friendly system prompt - HouseholdCapability registration for Steward coordination - delegate_to_housekeeper() wrapper for orchestration Also includes: - Dev port changed from 8123 to 8777 (avoids Home Assistant conflict) - Config: CORE_API_HOST, CORE_API_KEY, CORE_API_TIMEOUT - 44 unit tests for client and capability 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
185 lines
5.7 KiB
Markdown
185 lines
5.7 KiB
Markdown
# End-to-End API Tests
|
|
|
|
These tests make real HTTP requests to the running Tatlock API server to verify the complete stack works correctly.
|
|
|
|
## Prerequisites
|
|
|
|
1. **Server must be running** on `http://localhost:8777` (use `./wakeup.sh`)
|
|
2. **Ollama must be running** with `mistral-nemo:latest` model
|
|
3. **Redis must be running** (for benchmarking)
|
|
4. **Qdrant must be running** on `http://localhost:6333` (for memory tests)
|
|
|
|
## Running the Tests
|
|
|
|
### Start the server first:
|
|
|
|
```bash
|
|
# Terminal 1: Start the server (auto-reload enabled)
|
|
./wakeup.sh
|
|
|
|
# Logs are written to logs/server.log - tail them in another terminal:
|
|
tail -f logs/server.log
|
|
```
|
|
|
|
### Run the E2E tests:
|
|
|
|
```bash
|
|
# Run all E2E tests
|
|
pytest tests/e2e/ -v -m e2e
|
|
|
|
# Run orchestration tests specifically
|
|
pytest tests/e2e/test_orchestration_e2e.py -v
|
|
|
|
# Run API endpoint tests
|
|
pytest tests/e2e/test_api_endpoints.py -v
|
|
```
|
|
|
|
### Run specific test categories:
|
|
|
|
```bash
|
|
# Memory system tests
|
|
pytest tests/e2e/test_orchestration_e2e.py::TestMemoryStorage -v
|
|
pytest tests/e2e/test_orchestration_e2e.py::TestMemoryRecall -v
|
|
|
|
# Steward delegation tests
|
|
pytest tests/e2e/test_orchestration_e2e.py::TestStewardDelegation -v
|
|
|
|
# Direct delegation bypass tests (new feature)
|
|
pytest tests/e2e/test_orchestration_e2e.py::TestDirectDelegationBypass -v
|
|
|
|
# User isolation tests
|
|
pytest tests/e2e/test_orchestration_e2e.py::TestUserContextIsolation -v
|
|
|
|
# Orchestration scenario tests
|
|
pytest tests/e2e/test_orchestration_e2e.py::TestScenario1WeatherWithMemory -v
|
|
pytest tests/e2e/test_orchestration_e2e.py::TestScenario4SimpleExpertDelegation -v
|
|
pytest tests/e2e/test_orchestration_e2e.py::TestScenario6WikiCreation -v
|
|
|
|
# Generate evaluation report
|
|
pytest tests/e2e/test_orchestration_e2e.py::TestEvaluationReport -v -s
|
|
```
|
|
|
|
## Test Organization
|
|
|
|
### `test_api_endpoints.py` - Core API Tests
|
|
|
|
- Chat Completions endpoint (`/v1/chat/completions`)
|
|
- Responses API endpoint (`/v1/responses`)
|
|
- Streaming responses
|
|
- Error handling
|
|
- OpenAI format compliance
|
|
|
|
### `test_orchestration_e2e.py` - Orchestration Scenario Tests
|
|
|
|
Based on `ORCHESTRATION_SCENARIOS.md`:
|
|
|
|
| Class | Scenario | What it Tests |
|
|
|-------|----------|---------------|
|
|
| `TestMemoryStorage` | Memory storage | Store -> Qdrant verification |
|
|
| `TestMemoryRecall` | Memory recall | Store -> Recall flow |
|
|
| `TestStewardDelegation` | Steward routing | Capability recommendations |
|
|
| `TestDirectDelegation` | Direct bypass | Pure memory/librarian requests |
|
|
| `TestScenario1WeatherWithMemory` | Weather check | Multi-step with memory lookup |
|
|
| `TestScenario4SimpleExpertDelegation` | Calculator/datetime | Simple tool use |
|
|
| `TestScenario6WikiCreation` | Wiki operations | Librarian delegation |
|
|
| `TestScenario8MultiExpertCoordination` | Complex requests | Multiple capabilities |
|
|
| `TestUserContextIsolation` | User isolation | llm_tester vs production |
|
|
| `TestDataVerification` | Data presence | Qdrant structure verification |
|
|
| `TestIntegrationHealth` | System health | API/Qdrant reachability |
|
|
| `TestEvaluationReport` | Diagnostic | Generates behavior reports |
|
|
|
|
## User Isolation
|
|
|
|
Tests use the `llm_tester` user (development environment default) to isolate test data from production:
|
|
|
|
- Test memories: `memories_llm_tester` (Qdrant collection)
|
|
- Production memories: `memories_jpmschweitzer` (never modified by tests)
|
|
|
|
## Handling LLM Non-Determinism
|
|
|
|
LLM outputs are non-deterministic. Tests handle this by:
|
|
|
|
1. **Flexible assertions** - Check for behavior patterns, not exact text
|
|
2. **`assert_llm_behavior()`** - Helper for pattern matching with confidence levels
|
|
3. **Soft failures (`pytest.xfail`)** - Some tests may fail due to LLM variance without failing the suite
|
|
4. **Evaluation reports** - Generate diagnostic reports for human review
|
|
|
|
Example:
|
|
```python
|
|
result = assert_llm_behavior(
|
|
message_text,
|
|
expected_patterns=[r"(remember|noted|stored)", r"purple"],
|
|
min_matches=1,
|
|
)
|
|
if not result.passed:
|
|
pytest.xfail(f"LLM response unclear: {result.evidence}")
|
|
```
|
|
|
|
## Data Verification
|
|
|
|
Tests verify data presence in Qdrant:
|
|
|
|
```python
|
|
# QdrantVerifier helper
|
|
qdrant = QdrantVerifier()
|
|
points = await qdrant.scroll_points("memories_llm_tester")
|
|
memory = await qdrant.find_memory_by_key("memories_llm_tester", "favorite_color")
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Tests fail with connection error
|
|
|
|
Make sure the server is running:
|
|
```bash
|
|
./wakeup.sh
|
|
curl http://localhost:8777/health # Should return 200
|
|
```
|
|
|
|
### Tests timeout
|
|
|
|
- Check Ollama is running: `curl http://localhost:11434/api/tags`
|
|
- Increase timeout if needed (default: 120s for LLM calls)
|
|
|
|
### Memory tests fail
|
|
|
|
- Check Qdrant is running: `curl http://localhost:6333/collections`
|
|
- Verify `memories_llm_tester` collection exists
|
|
|
|
### Inconsistent results
|
|
|
|
- LLM responses vary - this is expected
|
|
- Check the evaluation report for detailed diagnostics:
|
|
```bash
|
|
pytest tests/e2e/test_orchestration_e2e.py::TestEvaluationReport -v -s
|
|
```
|
|
|
|
### Tests pollute production data
|
|
|
|
- This shouldn't happen - tests use `llm_tester` user
|
|
- If it does, check `ENVIRONMENT` is set to `development` in `.env`
|
|
|
|
## Adding New Tests
|
|
|
|
1. Use existing fixtures (`client`, `qdrant`, `clean_test_memories`)
|
|
2. Use `assert_llm_behavior()` for flexible LLM output checking
|
|
3. Add `@pytest.mark.e2e` decorator
|
|
4. Consider adding soft failures for non-deterministic checks
|
|
5. Add test keys to `clean_test_memories` fixture if storing new memories
|
|
|
|
Example:
|
|
```python
|
|
@pytest.mark.e2e
|
|
@pytest.mark.asyncio
|
|
class TestNewScenario:
|
|
async def test_something(
|
|
self,
|
|
client: httpx.AsyncClient,
|
|
qdrant: QdrantVerifier,
|
|
clean_test_memories,
|
|
):
|
|
response = await client.post("/v1/responses", json={...})
|
|
# Use assert_llm_behavior for flexible checking
|
|
result = assert_llm_behavior(response_text, expected_patterns=[...])
|
|
```
|