"""CUSTOMER_AGENT must never put an unsubstituted {placeholder} in front of a customer, and must not send a literal "unknown" customer id. The DispatchAgent assignment-failure path knows only a booking id, so this is the realistic caller: it supplies order_id and new_eta and no customer. """ import pytest from agents.customer_agent import ( CustomerAgent, NotificationChannel, NotificationType, _UNFILLED_PLACEHOLDER, ) def make_agent(captured): agent = CustomerAgent.__new__(CustomerAgent) agent.agent_id = "CUSTOMER_AGENT" agent._notifications = {} agent._templates = CustomerAgent._init_templates(agent) async def fake_post(url, json=None, headers=None, timeout=None): captured.append(json) return {"ok": True} import agents.customer_agent as mod mod.api_post = fake_post return agent @pytest.fixture(autouse=True) def restore_api_post(): import agents.customer_agent as mod original = mod.api_post yield mod.api_post = original @pytest.mark.asyncio async def test_missing_template_var_is_refused_not_sent(): sent = [] agent = make_agent(sent) result = await agent._send_via_channel( order_id="36", customer_id="unknown", channel=NotificationChannel.SMS, notification_type=NotificationType.DELAYED, template_vars={"order_id": "36"}, # new_eta omitted — the old bug ) assert result["status"] == "failed" assert "new_eta" in result["reason"] assert sent == [] # nothing reached the backend @pytest.mark.asyncio async def test_all_vars_supplied_sends_clean_message(): sent = [] agent = make_agent(sent) result = await agent._send_via_channel( order_id="36", customer_id="unknown", channel=NotificationChannel.SMS, notification_type=NotificationType.DELAYED, template_vars={"order_id": "36", "new_eta": "being confirmed"}, ) assert result["status"] == "sent" assert len(sent) == 1 assert not _UNFILLED_PLACEHOLDER.findall(sent[0]["message"]) assert sent[0]["message"] == ( "Delay alert: Order 36 may arrive later than expected. New ETA: being confirmed" ) @pytest.mark.asyncio async def test_unknown_customer_id_is_omitted_from_payload(): sent = [] agent = make_agent(sent) await agent._send_via_channel( order_id="36", customer_id="unknown", channel=NotificationChannel.SMS, notification_type=NotificationType.DELAYED, template_vars={"order_id": "36", "new_eta": "being confirmed"}, ) assert "customer_id" not in sent[0] # backend resolves from order_id assert sent[0]["order_id"] == "36" @pytest.mark.asyncio async def test_real_customer_id_is_sent(): sent = [] agent = make_agent(sent) await agent._send_via_channel( order_id="36", customer_id="CUST-9", channel=NotificationChannel.SMS, notification_type=NotificationType.DELAYED, template_vars={"order_id": "36", "new_eta": "being confirmed"}, ) assert sent[0]["customer_id"] == "CUST-9" @pytest.mark.asyncio async def test_dispatch_supplies_every_template_var(): """The DispatchAgent payload must satisfy the DELAYED template exactly.""" from agents.dispatch_agent import DispatchAgent sent_msgs = [] async def capture(recipient, message_type, payload, correlation_id=None): sent_msgs.append(payload) agent = DispatchAgent.__new__(DispatchAgent) agent.send_message = capture await agent._notify_customer_delay("36") payload = sent_msgs[0] assert payload["task_type"] == "send_notification" template = CustomerAgent._init_templates(CustomerAgent.__new__(CustomerAgent))[ NotificationType.DELAYED]["sms"] required = set(_UNFILLED_PLACEHOLDER.findall(template)) assert required <= set(payload["template_vars"]), ( f"template needs {required}, dispatch supplies {set(payload['template_vars'])}" )