fix(avatar): prevent takeover replies blocking across chats

This commit is contained in:
stefanfeng
2026-08-21 16:21:00 +08:00
parent e720baa21e
commit 672019830d
4 changed files with 98 additions and 27 deletions
@@ -25,7 +25,8 @@ def test_scheduler_uses_boxim_and_restart_safe_service(
boxim = MagicMock()
mock_boxim_class.return_value = boxim
takeover = MagicMock()
takeover.poll_and_process_messages = AsyncMock()
takeover.poll_messages = AsyncMock()
takeover.process_reply_tasks = AsyncMock()
mock_takeover_class.return_value = takeover
environment = {
@@ -46,14 +47,18 @@ def test_scheduler_uses_boxim_and_restart_safe_service(
assert config["BOXIM_API_BASE_URL"] == "https://im.example/api"
mock_takeover_class.assert_called_once_with(main.SessionLocal, boxim)
scheduler.add_job.assert_called_once()
scheduled_callable = scheduler.add_job.call_args.args[0]
job_options = scheduler.add_job.call_args.kwargs
assert scheduled_callable is takeover.poll_and_process_messages
assert job_options["id"] == "takeover_message_poll"
assert job_options["trigger"].interval.total_seconds() == 1
assert job_options["max_instances"] == 1
assert job_options["coalesce"] is True
assert scheduler.add_job.call_count == 2
poll_call, process_call = scheduler.add_job.call_args_list
assert poll_call.args[0] is takeover.poll_messages
assert poll_call.kwargs["id"] == "takeover_message_poll"
assert poll_call.kwargs["trigger"].interval.total_seconds() == 1
assert poll_call.kwargs["max_instances"] == 1
assert poll_call.kwargs["coalesce"] is True
assert process_call.args[0] is takeover.process_reply_tasks
assert process_call.kwargs["id"] == "takeover_reply_process"
assert process_call.kwargs["trigger"].interval.total_seconds() == 0.5
assert process_call.kwargs["max_instances"] == 1
assert process_call.kwargs["coalesce"] is True
scheduler.start.assert_called_once_with()
main.takeover_scheduler = None
@@ -1,6 +1,7 @@
"""End-to-end service tests for BOXIM takeover timing and human priority."""
from datetime import datetime, timedelta, timezone
from threading import Barrier
from unittest.mock import AsyncMock, patch
import pytest
@@ -143,6 +144,39 @@ async def test_incoming_message_is_prepared_then_sent_at_three_seconds(service_c
db.close()
@pytest.mark.asyncio
async def test_different_contacts_generate_without_blocking_each_other(service_context):
session_factory, service, boxim, clock = service_context
await service.poll_and_process_messages()
boxim.messages.extend(
[
{"id": 13, "localId": 31, "sendId": 200, "recvId": 100, "sendTime": clock.millis(), "type": 0, "content": "联系人甲"},
{"id": 14, "localId": 32, "sendId": 300, "recvId": 100, "sendTime": clock.millis(), "type": 0, "content": "联系人乙"},
]
)
both_generating = Barrier(2, timeout=2)
def resolve(_db, _avatar, prompt, _history):
both_generating.wait()
return {"answer": f"回复{prompt[-1]}"}
with patch("routers.chat._resolve_reply", side_effect=resolve):
await service.poll_and_process_messages()
clock.advance(3)
await service.process_reply_tasks()
assert {(item["peerId"], item["content"]) for item in boxim.sent} == {
("200", "回复甲"),
("300", "回复乙"),
}
db = session_factory()
try:
assert {task.status for task in db.query(TakeoverReplyTask).all()} == {"sent"}
finally:
db.close()
@pytest.mark.asyncio
async def test_read_receipt_failure_does_not_advance_cursor(service_context):
session_factory, service, boxim, clock = service_context