更新
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
|
||||
BACKEND_DIR = Path(__file__).resolve().parents[1]
|
||||
os.environ["KEFU_DB_TYPE"] = "sqlite"
|
||||
os.environ["KEFU_DATABASE_URL"] = ""
|
||||
os.environ["KEFU_DB_PATH"] = str(BACKEND_DIR / "kefu.db")
|
||||
if str(BACKEND_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(BACKEND_DIR))
|
||||
|
||||
import main
|
||||
|
||||
|
||||
def _queue_item(account_id: int, job_id: str = "job-1") -> dict:
|
||||
return {
|
||||
"job_id": job_id,
|
||||
"account_id": account_id,
|
||||
"position": 1,
|
||||
"status": "waiting",
|
||||
"expedited": False,
|
||||
"description": "回复 测试用户",
|
||||
"sender_name": "测试用户",
|
||||
"sender_id": "peer-1",
|
||||
"sender_avatar": None,
|
||||
"conversation_id": "conv-1",
|
||||
"incoming_content": "你好",
|
||||
"incoming_contents": ["你好", "第二条"],
|
||||
"message_count": 2,
|
||||
"replies": ["您好"],
|
||||
"interval_seconds": 60,
|
||||
"enqueued_at": "2026-07-20T10:00:00+00:00",
|
||||
"scheduled_at": "2026-07-20T10:01:00+00:00",
|
||||
"remaining_seconds": 60,
|
||||
}
|
||||
|
||||
|
||||
class _FakeService:
|
||||
def __init__(self, account_id: int, items=None, action=None):
|
||||
self.account_id = account_id
|
||||
self._running = True
|
||||
self.items = list(items or [])
|
||||
self.action = action or {
|
||||
"status": "accepted",
|
||||
"job_id": "job-1",
|
||||
"shifted_count": 2,
|
||||
}
|
||||
|
||||
async def get_reply_queue_snapshot(self):
|
||||
return list(self.items)
|
||||
|
||||
async def send_queued_reply_now(self, job_id: str):
|
||||
return {**self.action, "job_id": job_id}
|
||||
|
||||
|
||||
class ReplyQueueApiTests(unittest.IsolatedAsyncioTestCase):
|
||||
async def asyncSetUp(self):
|
||||
self.original_workers = main.manager.workers
|
||||
main.manager.workers = {}
|
||||
|
||||
async def asyncTearDown(self):
|
||||
main.manager.workers = self.original_workers
|
||||
|
||||
async def test_summary_filters_accounts_by_owner_scope(self):
|
||||
service_one = _FakeService(1, [_queue_item(1)])
|
||||
service_two = _FakeService(2, [_queue_item(2, "job-2")])
|
||||
main.manager.workers = {
|
||||
1: SimpleNamespace(is_running=True, _im_service=service_one),
|
||||
2: SimpleNamespace(is_running=True, _im_service=service_two),
|
||||
}
|
||||
|
||||
with patch.object(main, "owned_account_ids", AsyncMock(return_value={1})):
|
||||
response = await main.get_reply_queue_summaries(
|
||||
db=object(),
|
||||
user=SimpleNamespace(id=10, role="operator"),
|
||||
)
|
||||
|
||||
self.assertEqual(response.total_pending, 1)
|
||||
self.assertEqual([item.account_id for item in response.items], [1])
|
||||
|
||||
async def test_offline_account_detail_returns_empty_snapshot(self):
|
||||
account = SimpleNamespace(id=1, reply_delay_seconds=0)
|
||||
with (
|
||||
patch.object(main, "get_owned_account", AsyncMock(return_value=account)),
|
||||
patch.object(main, "_global_reply_delay_seconds", return_value=0),
|
||||
):
|
||||
response = await main.get_account_reply_queue(
|
||||
account_id=1,
|
||||
db=object(),
|
||||
user=SimpleNamespace(id=10, role="operator"),
|
||||
)
|
||||
|
||||
self.assertFalse(response.running)
|
||||
self.assertEqual(response.pending_count, 0)
|
||||
self.assertEqual(response.items, [])
|
||||
|
||||
async def test_detail_preserves_merged_incoming_messages(self):
|
||||
account = SimpleNamespace(id=1, reply_delay_seconds=60)
|
||||
service = _FakeService(1, [_queue_item(1)])
|
||||
main.manager.workers = {
|
||||
1: SimpleNamespace(is_running=True, _im_service=service),
|
||||
}
|
||||
with (
|
||||
patch.object(main, "get_owned_account", AsyncMock(return_value=account)),
|
||||
patch.object(main, "_resolve_effective_reply_delay", return_value=60),
|
||||
):
|
||||
response = await main.get_account_reply_queue(
|
||||
account_id=1,
|
||||
db=object(),
|
||||
user=SimpleNamespace(id=10, role="operator"),
|
||||
)
|
||||
|
||||
self.assertEqual(response.pending_count, 1)
|
||||
self.assertEqual(response.items[0].incoming_contents, ["你好", "第二条"])
|
||||
self.assertEqual(response.items[0].message_count, 2)
|
||||
self.assertEqual(response.items[0].replies, ["您好"])
|
||||
|
||||
async def test_send_now_returns_shifted_count(self):
|
||||
service = _FakeService(1)
|
||||
main.manager.workers = {
|
||||
1: SimpleNamespace(is_running=True, _im_service=service),
|
||||
}
|
||||
with (
|
||||
patch.object(main, "get_owned_account", AsyncMock(return_value=SimpleNamespace(id=1))),
|
||||
patch.object(main.system_logger, "record"),
|
||||
):
|
||||
response = await main.send_account_queued_reply_now(
|
||||
account_id=1,
|
||||
job_id="job-1",
|
||||
db=object(),
|
||||
user=SimpleNamespace(id=10, role="operator"),
|
||||
)
|
||||
|
||||
self.assertEqual(response.status, "accepted")
|
||||
self.assertEqual(response.shifted_count, 2)
|
||||
|
||||
async def test_cross_account_or_finished_job_returns_not_found(self):
|
||||
service = _FakeService(1, action={"status": "not_found"})
|
||||
main.manager.workers = {
|
||||
1: SimpleNamespace(is_running=True, _im_service=service),
|
||||
}
|
||||
with patch.object(
|
||||
main,
|
||||
"get_owned_account",
|
||||
AsyncMock(return_value=SimpleNamespace(id=1)),
|
||||
):
|
||||
with self.assertRaises(main.HTTPException) as caught:
|
||||
await main.send_account_queued_reply_now(
|
||||
account_id=1,
|
||||
job_id="other-account-job",
|
||||
db=object(),
|
||||
user=SimpleNamespace(id=10, role="operator"),
|
||||
)
|
||||
|
||||
self.assertEqual(caught.exception.status_code, 404)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user