From 9b040ffc6273ec26be7be91ad955de25b7ea5d5a Mon Sep 17 00:00:00 2001 From: Pim Hutting Date: Tue, 27 Jan 2026 11:46:24 +0100 Subject: [PATCH] chore: applied feedback --- .../user_interrupt/test_user_interrupt.py | 192 ++++++++++-------- 1 file changed, 110 insertions(+), 82 deletions(-) diff --git a/test/unit/agents/user_interrupt/test_user_interrupt.py b/test/unit/agents/user_interrupt/test_user_interrupt.py index b535952..a69a830 100644 --- a/test/unit/agents/user_interrupt/test_user_interrupt.py +++ b/test/unit/agents/user_interrupt/test_user_interrupt.py @@ -337,108 +337,136 @@ async def test_setup(agent): @pytest.mark.asyncio -async def test_receive_loop_advanced_scenarios(agent): - """ - Covers: - - JSONDecodeError (lines 86-88) - - Override: Trigger found (lines 108-109) - - Override: Norm found (lines 114-115) - - Override: Nothing found (line 134) - - Override Unachieve: Success & Fail (lines 136-145) - - Pause: Context true/false logs (lines 150-157) - - Next Phase (line 160) - """ - # 1. Setup Data Maps - agent._trigger_map["101"] = "trigger_slug" - agent._cond_norm_map["202"] = "norm_slug" - - # 2. Define Payloads - # A. Invalid JSON - bad_json = b"INVALID{JSON" - - # B. Override -> Trigger - override_trigger = json.dumps({"type": "override", "context": "101"}).encode() - - # C. Override -> Norm - override_norm = json.dumps({"type": "override", "context": "202"}).encode() - - # D. Override -> Unknown - override_fail = json.dumps({"type": "override", "context": "999"}).encode() - - # E. Unachieve -> Success - unachieve_success = json.dumps({"type": "override_unachieve", "context": "202"}).encode() - - # F. Unachieve -> Fail - unachieve_fail = json.dumps({"type": "override_unachieve", "context": "999"}).encode() - - # G. Pause (True) - pause_true = json.dumps({"type": "pause", "context": "true"}).encode() - - # H. Pause (False/Resume) - pause_false = json.dumps({"type": "pause", "context": ""}).encode() - - # I. Next Phase - next_phase = json.dumps({"type": "next_phase", "context": ""}).encode() - - # 3. Setup Socket +async def test_receive_loop_json_error(agent): + """Verify that malformed JSON is caught and logged without crashing the loop.""" agent.sub_socket.recv_multipart.side_effect = [ - (b"topic", bad_json), - (b"topic", override_trigger), - (b"topic", override_norm), - (b"topic", override_fail), - (b"topic", unachieve_success), - (b"topic", unachieve_fail), - (b"topic", pause_true), - (b"topic", pause_false), - (b"topic", next_phase), - asyncio.CancelledError, # End loop + (b"topic", b"INVALID{JSON"), + asyncio.CancelledError, ] - # Mock internal helpers to verify calls - agent._send_to_bdi = AsyncMock() - agent._send_to_bdi_belief = AsyncMock() - agent._send_pause_command = AsyncMock() - agent._send_experiment_control_to_bdi_core = AsyncMock() - - # 4. Run Loop try: await agent._receive_button_event() except asyncio.CancelledError: pass - # 5. Assertions - - # JSON Error agent.logger.error.assert_called_with("Received invalid JSON payload on topic %s", b"topic") - # Override Trigger - agent._send_to_bdi.assert_awaited_with("force_trigger", "trigger_slug") - # Override Norm - # We expect _send_to_bdi_belief to be called for the norm - # Note: The loop calls _send_to_bdi_belief(asl_cond_norm, "cond_norm") - agent._send_to_bdi_belief.assert_any_call("norm_slug", "cond_norm") +@pytest.mark.asyncio +async def test_receive_loop_override_trigger(agent): + """Verify routing 'override' to a Trigger.""" + agent._trigger_map["101"] = "trigger_slug" + payload = json.dumps({"type": "override", "context": "101"}).encode() - # Override Fail (Warning log) - agent.logger.warning.assert_any_call("Could not determine which element to override.") + agent.sub_socket.recv_multipart.side_effect = [(b"topic", payload), asyncio.CancelledError] + agent._send_to_bdi = AsyncMock() - # Unachieve Success - # Loop calls _send_to_bdi_belief(asl_cond_norm, "cond_norm", True) + try: + await agent._receive_button_event() + except asyncio.CancelledError: + pass + + agent._send_to_bdi.assert_awaited_once_with("force_trigger", "trigger_slug") + + +@pytest.mark.asyncio +async def test_receive_loop_override_norm(agent): + """Verify routing 'override' to a Conditional Norm.""" + agent._cond_norm_map["202"] = "norm_slug" + payload = json.dumps({"type": "override", "context": "202"}).encode() + + agent.sub_socket.recv_multipart.side_effect = [(b"topic", payload), asyncio.CancelledError] + agent._send_to_bdi_belief = AsyncMock() + + try: + await agent._receive_button_event() + except asyncio.CancelledError: + pass + + agent._send_to_bdi_belief.assert_awaited_once_with("norm_slug", "cond_norm") + + +@pytest.mark.asyncio +async def test_receive_loop_override_missing(agent): + """Verify warning log when an override ID is not found in any map.""" + payload = json.dumps({"type": "override", "context": "999"}).encode() + + agent.sub_socket.recv_multipart.side_effect = [(b"topic", payload), asyncio.CancelledError] + + try: + await agent._receive_button_event() + except asyncio.CancelledError: + pass + + agent.logger.warning.assert_called_with("Could not determine which element to override.") + + +@pytest.mark.asyncio +async def test_receive_loop_unachieve_logic(agent): + """Verify success and failure paths for override_unachieve.""" + agent._cond_norm_map["202"] = "norm_slug" + + success_payload = json.dumps({"type": "override_unachieve", "context": "202"}).encode() + fail_payload = json.dumps({"type": "override_unachieve", "context": "999"}).encode() + + agent.sub_socket.recv_multipart.side_effect = [ + (b"topic", success_payload), + (b"topic", fail_payload), + asyncio.CancelledError, + ] + agent._send_to_bdi_belief = AsyncMock() + + try: + await agent._receive_button_event() + except asyncio.CancelledError: + pass + + # Assert success call (True flag for unachieve) agent._send_to_bdi_belief.assert_any_call("norm_slug", "cond_norm", True) + # Assert failure log + agent.logger.warning.assert_called_with( + "Could not determine which conditional norm to unachieve." + ) - # Unachieve Fail - agent.logger.warning.assert_any_call("Could not determine which conditional norm to unachieve.") - # Pause Logic +@pytest.mark.asyncio +async def test_receive_loop_pause_resume(agent): + """Verify pause and resume toggle logic and logging.""" + pause_payload = json.dumps({"type": "pause", "context": "true"}).encode() + resume_payload = json.dumps({"type": "pause", "context": ""}).encode() + + agent.sub_socket.recv_multipart.side_effect = [ + (b"topic", pause_payload), + (b"topic", resume_payload), + asyncio.CancelledError, + ] + agent._send_pause_command = AsyncMock() + + try: + await agent._receive_button_event() + except asyncio.CancelledError: + pass + agent._send_pause_command.assert_any_call("true") - agent.logger.info.assert_any_call("Sent pause command.") - - # Resume Logic agent._send_pause_command.assert_any_call("") + agent.logger.info.assert_any_call("Sent pause command.") agent.logger.info.assert_any_call("Sent resume command.") - # Next Phase - agent._send_experiment_control_to_bdi_core.assert_awaited_with("next_phase") + +@pytest.mark.asyncio +async def test_receive_loop_phase_control(agent): + """Verify experiment flow control (next_phase).""" + payload = json.dumps({"type": "next_phase", "context": ""}).encode() + + agent.sub_socket.recv_multipart.side_effect = [(b"topic", payload), asyncio.CancelledError] + agent._send_experiment_control_to_bdi_core = AsyncMock() + + try: + await agent._receive_button_event() + except asyncio.CancelledError: + pass + + agent._send_experiment_control_to_bdi_core.assert_awaited_once_with("next_phase") @pytest.mark.asyncio