From 080bb837461a3cfee4c97ebebbf2cca6f3a96a02 Mon Sep 17 00:00:00 2001 From: Jeeves Assistant Date: Wed, 29 Jul 2026 20:19:28 -0700 Subject: [PATCH] test(homeassistant): prevent unit tests from calling live instances MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two tests made real HTTP calls to homeassistant.local:8123 — on a LAN with an actual Home Assistant instance they could turn on real lights, and otherwise burned ~10s in network timeouts. Replace with AsyncMock at _async_call_service and assert the exact production call signature (domain, service, entity_id, data). 35 pass in ~0.2s. Salvaged from PR #72634 by @jeeves-assistant. Co-authored-by: Jeeves Assistant --- tests/tools/test_homeassistant_tool.py | 34 +++++++++++++++++--------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/tests/tools/test_homeassistant_tool.py b/tests/tools/test_homeassistant_tool.py index c8d6b1174b6..3bfb486cdaf 100644 --- a/tests/tools/test_homeassistant_tool.py +++ b/tests/tools/test_homeassistant_tool.py @@ -5,7 +5,7 @@ handler validation, and availability gating. """ import json -from unittest.mock import patch +from unittest.mock import AsyncMock, patch import pytest @@ -141,16 +141,20 @@ class TestDomainBlocklist: assert "error" in result assert "blocked" in result["error"].lower() - def test_safe_domain_not_blocked(self): - """Safe domains like 'light' should not be blocked (will fail on network, not blocklist).""" - # This will try to make a real HTTP call and fail, but the important thing - # is it does NOT return a "blocked" error + @patch("tools.homeassistant_tool._async_call_service", new_callable=AsyncMock) + def test_safe_domain_not_blocked(self, mock_call_service): + """Safe domains like ``light`` reach the service-call layer.""" + mock_call_service.return_value = {"success": True} result = json.loads(_handle_call_service({ "domain": "light", "service": "turn_on", "entity_id": "light.test" })) - # Should fail with a network/connection error, not a "blocked" error - if "error" in result: - assert "blocked" not in result["error"].lower() + assert result["result"]["success"] is True + mock_call_service.assert_awaited_once_with( + "light", + "turn_on", + "light.test", + None, + ) def test_blocked_domains_include_shell_command(self): assert "shell_command" in _BLOCKED_DOMAINS @@ -179,14 +183,20 @@ class TestEntityIdValidation: assert _ENTITY_ID_RE.match("../api/config") is None - def test_call_service_allows_no_entity_id(self): + @patch("tools.homeassistant_tool._async_call_service", new_callable=AsyncMock) + def test_call_service_allows_no_entity_id(self, mock_call_service): """Some services (like scene.turn_on) don't need entity_id.""" - # Will fail on network, but should NOT fail on entity_id validation + mock_call_service.return_value = {"success": True} result = json.loads(_handle_call_service({ "domain": "scene", "service": "turn_on" })) - if "error" in result: - assert "Invalid entity_id" not in result["error"] + assert result["result"]["success"] is True + mock_call_service.assert_awaited_once_with( + "scene", + "turn_on", + None, + None, + ) # ---------------------------------------------------------------------------