落地空运单聊询价字段与线路选择。

必填核对后再建单;多条线路走 H5 点选;费用只展示该线路 TMS 回包,不套用整单空运费。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-22 15:02:32 +08:00
co-authored by Cursor
parent 8afe909230
commit cfbce2fd69
53 changed files with 3470 additions and 372 deletions
+53
View File
@@ -37,6 +37,18 @@ class AibotInboundNormalizeTests(unittest.TestCase):
self.assertIn("WO202609160009", parsed.content)
self.assertEqual(parsed.raw.get("source"), "aibot")
def test_keeps_aibot_req_id_for_quote_reply(self) -> None:
parsed = normalize_aibot_payload(
{
"sender_id": "air1",
"message_id": "m2",
"content": "锁舱",
"chat_id": "wr_air_1",
"aibot_req_id": "cb_lock_1",
}
)
self.assertEqual(parsed.raw.get("aibot_req_id"), "cb_lock_1")
class AibotReplyTests(unittest.TestCase):
def test_empty_content_skips(self) -> None:
@@ -64,6 +76,7 @@ class AibotReplyTests(unittest.TestCase):
self.assertEqual(payload["chatid"], "wr_air_1")
self.assertEqual(payload["operation"], "text")
self.assertEqual(payload["content"], "附件已保存")
self.assertEqual(payload.get("aibot_req_id"), "")
self.assertEqual(
fake_http.post.call_args.kwargs["headers"]["X-Aibot-Bridge-Token"],
"tok",
@@ -92,6 +105,46 @@ class AibotReplyTests(unittest.TestCase):
self.assertEqual(payload["chatid"], "wr_air_1")
self.assertEqual(payload["filename"], "WO1_quote.xlsx")
def test_at_reply_does_not_post_stream_payload(self) -> None:
"""
企微 stream 回复会自带「内容由AI生成」和赞踩。
@ 当场也只走桥主动发言,不往 response_url 推 stream。
"""
client = AibotReplyClient("https://example.com/reply")
fake_http = MagicMock()
fake_http.post.return_value.status_code = 200
fake_http.post.return_value.text = '{"status":"sent"}'
settings = MagicMock()
settings.wecom_aibot_bridge_url = "http://127.0.0.1:8913"
settings.wecom_aibot_bridge_token = "tok"
with patch("agent.channel.aibot.reply.httpx", fake_http), patch(
"agent.channel.aibot.reply.get_settings", return_value=settings
):
out = client.send_group(chat_id="wr_air_1", content="工单号:WO202609210001")
self.assertTrue(out["ok"])
payloads = [c.kwargs.get("json") or {} for c in fake_http.post.call_args_list]
self.assertFalse(any(p.get("msgtype") == "stream" for p in payloads))
self.assertFalse(
any(isinstance(p.get("stream"), dict) for p in payloads)
)
urls = [c.args[0] for c in fake_http.post.call_args_list]
self.assertFalse(any("example.com/reply" in str(u) for u in urls))
def test_at_reply_passes_callback_req_id(self) -> None:
client = AibotReplyClient("", callback_req_id="cb_lock_1")
fake_http = MagicMock()
fake_http.post.return_value.status_code = 200
fake_http.post.return_value.text = '{"status":"sent"}'
settings = MagicMock()
settings.wecom_aibot_bridge_url = "http://127.0.0.1:8913"
settings.wecom_aibot_bridge_token = "tok"
with patch("agent.channel.aibot.reply.httpx", fake_http), patch(
"agent.channel.aibot.reply.get_settings", return_value=settings
):
client.send_group(chat_id="wr_air_1", content="工单WO1 空运锁舱失败。")
payload = fake_http.post.call_args.kwargs["json"]
self.assertEqual(payload["aibot_req_id"], "cb_lock_1")
def test_at_reply_puts_brief_on_same_channel_as_file(self) -> None:
"""
@ 当场流式回话群里经常只看得见后面的文件。