From 1e7c2ba229d008ee390d5aa6aa7688635a6658a2 Mon Sep 17 00:00:00 2001 From: Pim Hutting Date: Mon, 19 Jan 2026 16:01:59 +0100 Subject: [PATCH] chore: added missing tests --- .../user_interrupt/test_user_interrupt.py | 189 ++++++++++++++++++ 1 file changed, 189 insertions(+) diff --git a/test/unit/agents/user_interrupt/test_user_interrupt.py b/test/unit/agents/user_interrupt/test_user_interrupt.py index 9f325f3..3786f8d 100644 --- a/test/unit/agents/user_interrupt/test_user_interrupt.py +++ b/test/unit/agents/user_interrupt/test_user_interrupt.py @@ -577,3 +577,192 @@ async def test_create_mapping_recursive_goals(agent): assert str(child_goal_id) in agent._goal_map assert agent._goal_map[str(child_goal_id)] == "child_goal" assert agent._goal_reverse_map["child_goal"] == str(child_goal_id) + + +@pytest.mark.asyncio +async def test_setup(agent): + """Test the setup method initializes sockets correctly.""" + with patch("control_backend.agents.user_interrupt.user_interrupt_agent.Context") as MockContext: + mock_ctx_instance = MagicMock() + MockContext.instance.return_value = mock_ctx_instance + + mock_sub = MagicMock() + mock_pub = MagicMock() + mock_ctx_instance.socket.side_effect = [mock_sub, mock_pub] + + # MOCK add_behavior so we don't rely on internal attributes + agent.add_behavior = MagicMock() + + await agent.setup() + + # Check sockets + mock_sub.connect.assert_called_with(settings.zmq_settings.internal_sub_address) + mock_pub.connect.assert_called_with(settings.zmq_settings.internal_pub_address) + + # Verify add_behavior was called + agent.add_behavior.assert_called_once() + + +@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 + 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 + ] + + # 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") + + # Override Fail (Warning log) + agent.logger.warning.assert_any_call("Could not determine which element to override.") + + # Unachieve Success + # Loop calls _send_to_bdi_belief(asl_cond_norm, "cond_norm", True) + agent._send_to_bdi_belief.assert_any_call("norm_slug", "cond_norm", True) + + # Unachieve Fail + agent.logger.warning.assert_any_call("Could not determine which conditional norm to unachieve.") + + # Pause Logic + 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 resume command.") + + # Next Phase + agent._send_experiment_control_to_bdi_core.assert_awaited_with("next_phase") + + +@pytest.mark.asyncio +async def test_handle_message_unknown_thread(agent): + """Test handling of an unknown message thread (lines 213-214).""" + msg = InternalMessage(to="me", thread="unknown_thread", body="test") + await agent.handle_message(msg) + + agent.logger.debug.assert_called_with( + "Received internal message on unhandled thread: unknown_thread" + ) + + +@pytest.mark.asyncio +async def test_send_to_bdi_belief_edge_cases(agent): + """ + Covers: + - Unknown asl_type warning (lines 326-328) + - unachieve=True logic (lines 334-337) + """ + # 1. Unknown Type + await agent._send_to_bdi_belief("slug", "unknown_type") + + agent.logger.warning.assert_called_with("Tried to send belief with unknown type") + agent.send.assert_not_called() + + # Reset mock for part 2 + agent.send.reset_mock() + + # 2. Unachieve = True + await agent._send_to_bdi_belief("slug", "cond_norm", unachieve=True) + + agent.send.assert_awaited() + sent_msg = agent.send.call_args.args[0] + + # Verify it is a delete operation + body_obj = BeliefMessage.model_validate_json(sent_msg.body) + + # Verify 'delete' has content + assert body_obj.delete is not None + assert len(body_obj.delete) == 1 + assert body_obj.delete[0].name == "force_slug" + + # Verify 'create' is empty (handling both None and []) + assert not body_obj.create + + +@pytest.mark.asyncio +async def test_send_experiment_control_unknown(agent): + """Test sending an unknown experiment control type (lines 366-367).""" + await agent._send_experiment_control_to_bdi_core("invalid_command") + + agent.logger.warning.assert_called_with( + "Received unknown experiment control type '%s' to send to BDI Core.", "invalid_command" + ) + + # Ensure it still sends an empty message (as per code logic, though thread is empty) + agent.send.assert_awaited() + msg = agent.send.call_args[0][0] + assert msg.thread == ""