Files
MoviePilot/tests/test_subscription_completion_command.py
2026-08-23 02:35:16 +08:00

167 lines
5.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""订阅完成命令的原子写入、时序与 durable intent 测试。"""
from datetime import datetime
import pytest
from app.application.subscription.complete import CompleteSubscriptionCommand
class _Repository:
"""记录历史暂存和订阅删除顺序。"""
def __init__(self, calls: list[tuple]) -> None:
"""保存共享调用序列。"""
self.calls = calls
def add_history(self, **payload) -> None:
"""记录历史快照。"""
self.calls.append(("history", payload))
def delete(self, subscribe_id: int) -> None:
"""记录待删除订阅。"""
self.calls.append(("delete", subscribe_id))
class _UnitOfWork:
"""记录提交和回滚。"""
def __init__(self, calls: list[tuple], error: Exception | None = None) -> None:
"""保存调用序列与可选提交异常。"""
self.calls = calls
self.error = error
def commit(self) -> None:
"""记录提交并按需失败。"""
self.calls.append(("commit",))
if self.error:
raise self.error
def rollback(self) -> None:
"""记录回滚。"""
self.calls.append(("rollback",))
class _Outbox:
"""记录 intent 暂存与即时收口。"""
def __init__(self, calls: list[tuple]) -> None:
"""保存共享调用序列。"""
self.calls = calls
def stage(self, intent, _now: datetime) -> None:
"""记录 durable intent。"""
self.calls.append(("stage", intent))
def complete_by_event_key(self, event_key: str, _now: datetime) -> None:
"""记录成功副作用对应的 intent 收口。"""
self.calls.append(("complete", event_key))
def _command(calls: list[tuple], *, publish_error=None, report_result=True, notify_error=None):
"""构造可注入失败的完成命令。"""
def notify() -> None:
"""记录通知。"""
calls.append(("notify",))
if notify_error:
raise notify_error
def publish(payload) -> None:
"""记录完成事件。"""
calls.append(("event", payload))
if publish_error:
raise publish_error
def report(payload) -> bool:
"""记录完成统计。"""
calls.append(("report", payload))
return report_result
return CompleteSubscriptionCommand(
repository=_Repository(calls),
unit_of_work=_UnitOfWork(calls),
outbox=_Outbox(calls),
publish=publish,
), notify, report
@pytest.mark.parametrize("failure", ["event", "report", "notify"])
def test_completion_stages_business_and_independent_intents_before_commit(failure):
"""完成事务先提交业务和两个 intent提交后按通知、事件、统计顺序执行。"""
calls = []
command, notify, report = _command(
calls,
publish_error=RuntimeError("event failed") if failure == "event" else None,
report_result=False if failure == "report" else True,
notify_error=RuntimeError("notify failed") if failure == "notify" else None,
)
with pytest.raises(RuntimeError):
command.execute(
7,
{"id": 7, "media_source": "tmdb", "media_id": "123", "season": 2},
{"title": "Test"},
notify=notify,
report=report,
)
assert [call[0] for call in calls[:5]] == [
"history", "delete", "stage", "stage", "commit",
]
assert calls[2][1].topic == "subscribe.complete"
assert calls[3][1].topic == "subscribe.complete.report"
if failure == "notify":
assert [call[0] for call in calls[5:]] == ["notify"]
elif failure == "event":
assert [call[0] for call in calls[5:]] == ["notify", "event"]
else:
assert [call[0] for call in calls[5:]] == [
"notify", "event", "complete", "report",
]
def test_completion_success_closes_event_then_report_intent():
"""成功完成按兼容顺序通知、事件、统计,并分别收口两个 intent。"""
calls = []
command, notify, report = _command(calls)
command.execute(
7,
{"id": 7, "media_source": "tmdb", "media_id": "123", "season": 2},
{"title": "Test"},
notify=notify,
report=report,
)
assert [call[0] for call in calls] == [
"history", "delete", "stage", "stage", "commit",
"notify", "event", "complete", "report", "complete",
]
assert calls[6][1]["idempotency_key"] == calls[2][1].event_key
assert calls[8][1]["idempotency_key"] == calls[3][1].event_key
def test_completion_stages_and_closes_notification_snapshot() -> None:
"""完成通知快照与业务事务同提交,成功即时投递后独立收口。"""
calls = []
command, notify, report = _command(calls)
command.execute(
7,
{"id": 7, "media_source": "tmdb", "media_id": "123", "season": 2},
{"title": "Test"},
notify=notify,
report=report,
notification={"title": "完成", "text": "Test"},
)
staged = [call[1] for call in calls if call[0] == "stage"]
assert [intent.topic for intent in staged] == [
"subscribe.complete",
"subscribe.complete.notification",
"subscribe.complete.report",
]
assert staged[1].payload["message"]["title"] == "完成"
completed = [call[1] for call in calls if call[0] == "complete"]
assert completed[0].endswith(":notification")