fix: use message reaction for query acknowledgement

This commit is contained in:
ar51
2026-09-04 15:46:18 +08:00
parent c7c5c3259c
commit 24469372fb
7 changed files with 151 additions and 49 deletions

View File

@@ -106,14 +106,14 @@ class TriggerGateTests(unittest.TestCase):
mock.patch.object(bridge, "build_report") as build_report,
mock.patch.object(
bridge,
"reply_received_acknowledgement",
) as acknowledge,
"add_received_reaction",
) as react,
mock.patch.object(bridge, "reply") as reply,
):
bridge.handle_message(object(), data)
build_report.assert_not_called()
acknowledge.assert_not_called()
react.assert_not_called()
reply.assert_not_called()
def test_valid_message_builds_and_replies(self) -> None:
@@ -129,19 +129,111 @@ class TriggerGateTests(unittest.TestCase):
),
mock.patch.object(
bridge,
"reply_received_acknowledgement",
) as acknowledge,
"add_received_reaction",
return_value=False,
) as react,
mock.patch.object(bridge, "build_report", return_value="report") as build_report,
mock.patch.object(bridge, "reply") as reply,
):
bridge.handle_message("client", data)
acknowledge.assert_called_once_with("client", "om_valid")
react.assert_called_once_with("client", "om_valid")
build_report.assert_called_once_with(
bridge.RequestInput(model="eRob70H50I-BHM-18CTC[V5]", quantity=10)
)
reply.assert_called_once_with("client", "om_valid", "report")
def test_received_reaction_targets_original_message_with_onit(self) -> None:
class ReactionBodyBuilder:
def __init__(self) -> None:
self.value: dict[str, str] | None = None
def reaction_type(self, value: dict[str, str]) -> "ReactionBodyBuilder":
self.value = value
return self
def build(self) -> SimpleNamespace:
return SimpleNamespace(reaction_type=self.value)
class ReactionRequestBuilder:
def __init__(self) -> None:
self.message_id_value: str | None = None
self.body: SimpleNamespace | None = None
def message_id(self, value: str) -> "ReactionRequestBuilder":
self.message_id_value = value
return self
def request_body(self, value: SimpleNamespace) -> "ReactionRequestBuilder":
self.body = value
return self
def build(self) -> SimpleNamespace:
return SimpleNamespace(
message_id=self.message_id_value,
request_body=self.body,
)
body_type = SimpleNamespace(builder=ReactionBodyBuilder)
request_type = SimpleNamespace(builder=ReactionRequestBuilder)
create_reaction = mock.Mock(
return_value=SimpleNamespace(
success=lambda: True,
code=0,
msg="ok",
)
)
client = SimpleNamespace(
im=SimpleNamespace(
v1=SimpleNamespace(
message_reaction=SimpleNamespace(create=create_reaction),
)
)
)
with (
mock.patch.object(
bridge.lark.im.v1,
"CreateMessageReactionRequestBody",
body_type,
create=True,
),
mock.patch.object(
bridge.lark.im.v1,
"CreateMessageReactionRequest",
request_type,
create=True,
),
):
result = bridge.add_received_reaction(client, "om_original")
self.assertTrue(result)
sent_request = create_reaction.call_args.args[0]
self.assertEqual(sent_request.message_id, "om_original")
self.assertEqual(
sent_request.request_body.reaction_type,
{"emoji_type": "OnIt"},
)
def test_received_reaction_exception_does_not_escape(self) -> None:
broken_body_type = SimpleNamespace(
builder=mock.Mock(side_effect=RuntimeError("reaction unavailable"))
)
with (
mock.patch.object(
bridge.lark.im.v1,
"CreateMessageReactionRequestBody",
broken_body_type,
create=True,
),
mock.patch.object(bridge.logging, "exception") as log_exception,
):
result = bridge.add_received_reaction(object(), "om_original")
self.assertFalse(result)
log_exception.assert_called_once()
def test_candidate_continuation_phrase_is_recognized(self) -> None:
valid_phrases = (
"还有没有其他候选型号?",
@@ -190,14 +282,14 @@ class TriggerGateTests(unittest.TestCase):
mock.patch.object(bridge, "build_report") as build_report,
mock.patch.object(
bridge,
"reply_received_acknowledgement",
) as acknowledge,
"add_received_reaction",
) as react,
mock.patch.object(bridge, "reply") as reply,
):
bridge.handle_message("client", data)
build_report.assert_not_called()
acknowledge.assert_not_called()
react.assert_not_called()
reply.assert_not_called()
def test_follow_up_continues_from_fifth_candidate_in_same_conversation(self) -> None:
@@ -219,8 +311,8 @@ class TriggerGateTests(unittest.TestCase):
) as build_report,
mock.patch.object(
bridge,
"reply_received_acknowledgement",
) as acknowledge,
"add_received_reaction",
) as react,
mock.patch.object(bridge, "reply") as reply,
):
bridge.handle_message("client", initial_data)
@@ -231,7 +323,7 @@ class TriggerGateTests(unittest.TestCase):
[mock.call(request), mock.call(request, candidate_offset=4)],
)
self.assertEqual(
acknowledge.call_args_list,
react.call_args_list,
[
mock.call("client", "om_initial"),
mock.call("client", "om_follow_up"),
@@ -258,8 +350,8 @@ class TriggerGateTests(unittest.TestCase):
),
mock.patch.object(
bridge,
"reply_received_acknowledgement",
) as acknowledge,
"add_received_reaction",
) as react,
mock.patch.object(bridge, "build_report", return_value="report") as build_report,
mock.patch.object(bridge, "reply") as reply,
):
@@ -269,7 +361,7 @@ class TriggerGateTests(unittest.TestCase):
build_report.assert_called_once_with(
bridge.RequestInput(model="eRob70H50I-BHM-18CTC[V5]", quantity=10)
)
acknowledge.assert_called_once_with("client", "om_small")
react.assert_called_once_with("client", "om_small")
reply.assert_called_once_with("client", "om_small", "report")
def test_expired_candidate_context_is_removed(self) -> None: