From fd0a4b50f9328fda657bbbb20efe28137185cbd7 Mon Sep 17 00:00:00 2001 From: Chuck <33324927+ChuckBuilds@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:40:31 -0400 Subject: [PATCH] test: the on-demand routes' other callers ack over the socket, not the mailbox test_api_v3_bool_coercion, test_api_v3_lazy_plugin_discovery and test_web_api's stop test checked the mailbox write; they now get an ack from a mocked control socket. The stop route touches no manager any more, so it leaves the removed-catch-all sample. A request that was sent is never 'not listening' (pins the sent check the mutation run let through). Co-Authored-By: Claude Opus 5.5 --- test/test_api_v3_bool_coercion.py | 5 ++++- test/test_api_v3_lazy_plugin_discovery.py | 19 +++++++++++++++---- test/test_ipc_stage4.py | 6 ++++++ test/test_web_api.py | 19 +++++++++++-------- .../test_api_v3_unhandled_errors.py | 1 - 5 files changed, 36 insertions(+), 14 deletions(-) diff --git a/test/test_api_v3_bool_coercion.py b/test/test_api_v3_bool_coercion.py index 8899206f..e87faab0 100644 --- a/test/test_api_v3_bool_coercion.py +++ b/test/test_api_v3_bool_coercion.py @@ -54,7 +54,10 @@ class TestOnDemandStart: def service(self, api_v3_module): api_v3_module.api_v3.plugin_catalog = None api_v3_module.api_v3.config_manager = None - with patch("web_interface.blueprints.api_v3.display._get_display_service_status", + # The display answers the control socket (the only way in). + with patch("web_interface.blueprints.api_v3.display.control_client.on_demand_start", + side_effect=lambda request_id, *a: {"accepted": True}), \ + patch("web_interface.blueprints.api_v3.display._get_display_service_status", return_value={"active": True}), \ patch("web_interface.blueprints.api_v3.display._stop_display_service") as stop, \ patch("web_interface.blueprints.api_v3.display._ensure_display_service_running", diff --git a/test/test_api_v3_lazy_plugin_discovery.py b/test/test_api_v3_lazy_plugin_discovery.py index c3c30e1c..ead147b3 100644 --- a/test/test_api_v3_lazy_plugin_discovery.py +++ b/test/test_api_v3_lazy_plugin_discovery.py @@ -77,11 +77,22 @@ def fresh_web_process(api_v3_module, plugins_dir): @pytest.fixture def display_service(api_v3_module): - """Keep on-demand start away from systemctl and the real cache.""" - cache = api_v3_module.api_v3.cache_manager = MagicMock() - with patch('web_interface.blueprints.api_v3.display._get_display_service_status') as status: + """Keep on-demand start away from systemctl and the real cache; the + display's control socket is a mock that acks. What it was sent is + recorded as ``.set(cmd, request)`` calls on the yielded mock.""" + api_v3_module.api_v3.cache_manager = MagicMock() + sent = MagicMock() + + def ack(request_id, plugin_id, mode, *a, **kw): + sent.set('on_demand.start', {'request_id': request_id, + 'plugin_id': plugin_id, 'mode': mode}) + return {'accepted': True} + + with patch('web_interface.blueprints.api_v3.display._get_display_service_status') as status, \ + patch('web_interface.blueprints.api_v3.display.control_client.on_demand_start', + side_effect=ack): status.return_value = {'active': True} - yield cache + yield sent def _start(client, **body): diff --git a/test/test_ipc_stage4.py b/test/test_ipc_stage4.py index 99380758..a202aee7 100644 --- a/test/test_ipc_stage4.py +++ b/test/test_ipc_stage4.py @@ -144,6 +144,12 @@ class TestSent: def test_a_client_without_the_socket_is_not_waiting_for_a_display(self, reason): assert not client.display_not_listening(client.ControlError(reason)) + @pytest.mark.parametrize('reason', sorted(client.NOT_LISTENING_REASONS)) + def test_a_request_that_was_sent_had_a_display(self, reason): + # Only a request that never left can be waited on and sent again: one + # that was sent may have been applied. + assert not client.display_not_listening(client.ControlError(reason, sent=True)) + def test_a_client_bug_is_not_a_missing_display(self): assert not client.display_not_listening(RuntimeError('boom')) diff --git a/test/test_web_api.py b/test/test_web_api.py index 5b5a42f4..58cb4262 100644 --- a/test/test_web_api.py +++ b/test/test_web_api.py @@ -528,14 +528,17 @@ class TestDisplayAPI: from web_interface.blueprints.api_v3 import api_v3 mock_cache_manager = api_v3.cache_manager = MagicMock() - - response = client.post('/api/v3/display/on-demand/stop') - - # May return 200 if successful or 500 on error - assert response.status_code in [200, 500] - # Verify stop request was set in cache if successful - if response.status_code == 200: - assert mock_cache_manager.set.called + + with patch('web_interface.blueprints.api_v3.display.control_client.on_demand_stop', + side_effect=lambda request_id, **kw: {'accepted': True}) as stop: + response = client.post('/api/v3/display/on-demand/stop') + + assert response.status_code == 200 + assert response.get_json()['data']['transport'] == 'socket' + stop.assert_called_once() + # The request goes over the control socket; nothing is written to + # the cache (the file mailbox is gone). + mock_cache_manager.set.assert_not_called() class TestPluginsAPI: diff --git a/test/web_interface/test_api_v3_unhandled_errors.py b/test/web_interface/test_api_v3_unhandled_errors.py index 49ab0dbc..5dc3a8c5 100644 --- a/test/web_interface/test_api_v3_unhandled_errors.py +++ b/test/web_interface/test_api_v3_unhandled_errors.py @@ -81,7 +81,6 @@ REMOVED_CATCH_ALLS = [ ("GET", "/api/v3/config/main", None), ("GET", "/api/v3/config/secrets", None), ("GET", "/api/v3/display/modes", None), - ("POST", "/api/v3/display/on-demand/stop", {}), ("GET", "/api/v3/cache/list", None), ("GET", "/api/v3/plugins/installed", None), ("GET", "/api/v3/plugins/health", None),