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>
141 lines
4.8 KiB
Python
141 lines
4.8 KiB
Python
"""
|
|
Tests for Housekeeper capability registration.
|
|
"""
|
|
|
|
import pytest
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from src.agents.housekeeper.capability import (
|
|
HOUSEKEEPER_CAPABILITY,
|
|
get_housekeeper_capability,
|
|
register_housekeeper,
|
|
unregister_housekeeper,
|
|
)
|
|
from src.core.household_registry import HouseholdCapability
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestHousekeeperCapability:
|
|
"""Tests for the Housekeeper capability definition."""
|
|
|
|
def test_capability_is_household_capability(self):
|
|
"""Test capability is correct type."""
|
|
assert isinstance(HOUSEKEEPER_CAPABILITY, HouseholdCapability)
|
|
|
|
def test_capability_name(self):
|
|
"""Test capability has correct name."""
|
|
assert HOUSEKEEPER_CAPABILITY.name == "housekeeper"
|
|
|
|
def test_capability_role(self):
|
|
"""Test capability has correct role."""
|
|
assert HOUSEKEEPER_CAPABILITY.role == "The Housekeeper"
|
|
|
|
def test_capability_category(self):
|
|
"""Test capability is in automation category."""
|
|
assert HOUSEKEEPER_CAPABILITY.category == "automation"
|
|
|
|
def test_capability_domains(self):
|
|
"""Test capability covers expected domains."""
|
|
domains = HOUSEKEEPER_CAPABILITY.domains
|
|
|
|
assert "lights" in domains
|
|
assert "switches" in domains
|
|
assert "automation" in domains
|
|
assert "home" in domains
|
|
assert "scene" in domains
|
|
assert "turn on" in domains
|
|
assert "turn off" in domains
|
|
|
|
def test_capability_requires_network(self):
|
|
"""Test capability requires network access."""
|
|
assert HOUSEKEEPER_CAPABILITY.requires_network is True
|
|
|
|
def test_capability_cost_is_low(self):
|
|
"""Test capability is low cost (local API calls)."""
|
|
assert HOUSEKEEPER_CAPABILITY.cost == "low"
|
|
|
|
def test_get_housekeeper_capability(self):
|
|
"""Test getter returns same capability."""
|
|
cap = get_housekeeper_capability()
|
|
|
|
assert cap is HOUSEKEEPER_CAPABILITY
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestHousekeeperRegistration:
|
|
"""Tests for Housekeeper registration functions."""
|
|
|
|
def test_register_housekeeper(self):
|
|
"""Test registering housekeeper with registry."""
|
|
mock_registry = MagicMock()
|
|
mock_registry.__contains__ = MagicMock(return_value=False)
|
|
|
|
with patch(
|
|
"src.agents.housekeeper.capability.get_household_registry",
|
|
return_value=mock_registry,
|
|
):
|
|
with patch(
|
|
"src.agents.housekeeper.capability.get_housekeeper_agent"
|
|
) as mock_get_agent:
|
|
mock_agent = MagicMock()
|
|
mock_get_agent.return_value = mock_agent
|
|
|
|
register_housekeeper()
|
|
|
|
mock_registry.register.assert_called_once()
|
|
call_kwargs = mock_registry.register.call_args[1]
|
|
|
|
assert call_kwargs["name"] == "housekeeper"
|
|
assert call_kwargs["capability"] is HOUSEKEEPER_CAPABILITY
|
|
assert call_kwargs["agent"] is mock_agent
|
|
|
|
def test_register_housekeeper_already_registered(self):
|
|
"""Test registering when already registered does nothing."""
|
|
mock_registry = MagicMock()
|
|
mock_registry.__contains__ = MagicMock(return_value=True)
|
|
|
|
with patch(
|
|
"src.agents.housekeeper.capability.get_household_registry",
|
|
return_value=mock_registry,
|
|
):
|
|
register_housekeeper()
|
|
|
|
# Should not call register since already registered
|
|
mock_registry.register.assert_not_called()
|
|
|
|
def test_unregister_housekeeper(self):
|
|
"""Test unregistering housekeeper from registry."""
|
|
mock_registry = MagicMock()
|
|
|
|
with patch(
|
|
"src.agents.housekeeper.capability.get_household_registry",
|
|
return_value=mock_registry,
|
|
):
|
|
unregister_housekeeper()
|
|
|
|
mock_registry.unregister.assert_called_once_with("housekeeper")
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestCapabilityDescription:
|
|
"""Tests for capability description."""
|
|
|
|
def test_description_mentions_device_control(self):
|
|
"""Test description mentions device control capabilities."""
|
|
desc = HOUSEKEEPER_CAPABILITY.description.lower()
|
|
assert "turn on" in desc
|
|
# Description uses "ON/OFF" format
|
|
assert "off" in desc
|
|
|
|
def test_description_mentions_scenes(self):
|
|
"""Test description mentions scene capability."""
|
|
assert "scene" in HOUSEKEEPER_CAPABILITY.description.lower()
|
|
|
|
def test_description_mentions_scripts(self):
|
|
"""Test description mentions script capability."""
|
|
assert "script" in HOUSEKEEPER_CAPABILITY.description.lower()
|
|
|
|
def test_description_mentions_automations(self):
|
|
"""Test description mentions automation management."""
|
|
assert "automation" in HOUSEKEEPER_CAPABILITY.description.lower()
|