mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-04 15:09:46 +08:00
fix(outbox): serialize subscription completion delivery
This commit is contained in:
@@ -45,20 +45,33 @@ class _UnitOfWork:
|
||||
class _Outbox:
|
||||
"""记录 intent 暂存与即时收口。"""
|
||||
|
||||
def __init__(self, calls: list[tuple]) -> None:
|
||||
def __init__(self, calls: list[tuple], claim_result: bool = True) -> None:
|
||||
"""保存共享调用序列。"""
|
||||
self.calls = calls
|
||||
self.claim_result = claim_result
|
||||
|
||||
def stage(self, intent, _now: datetime) -> None:
|
||||
"""记录 durable intent。"""
|
||||
self.calls.append(("stage", intent))
|
||||
|
||||
def claim_by_event_key(self, event_key: str, _now: datetime, _lease_until: datetime) -> bool:
|
||||
"""记录同步投递认领结果。"""
|
||||
self.calls.append(("claim", event_key))
|
||||
return self.claim_result
|
||||
|
||||
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 _command(
|
||||
calls: list[tuple],
|
||||
*,
|
||||
publish_error=None,
|
||||
report_result=True,
|
||||
notify_error=None,
|
||||
claim_result=True,
|
||||
):
|
||||
"""构造可注入失败的完成命令。"""
|
||||
def notify() -> None:
|
||||
"""记录通知。"""
|
||||
@@ -80,7 +93,7 @@ def _command(calls: list[tuple], *, publish_error=None, report_result=True, noti
|
||||
return CompleteSubscriptionCommand(
|
||||
repository=_Repository(calls),
|
||||
unit_of_work=_UnitOfWork(calls),
|
||||
outbox=_Outbox(calls),
|
||||
outbox=_Outbox(calls, claim_result),
|
||||
publish=publish,
|
||||
), notify, report
|
||||
|
||||
@@ -113,10 +126,10 @@ def test_completion_stages_business_and_independent_intents_before_commit(failur
|
||||
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"]
|
||||
assert [call[0] for call in calls[5:]] == ["notify", "claim", "event"]
|
||||
else:
|
||||
assert [call[0] for call in calls[5:]] == [
|
||||
"notify", "event", "complete", "report",
|
||||
"notify", "claim", "event", "complete", "claim", "report",
|
||||
]
|
||||
|
||||
|
||||
@@ -135,10 +148,11 @@ def test_completion_success_closes_event_then_report_intent():
|
||||
|
||||
assert [call[0] for call in calls] == [
|
||||
"history", "delete", "stage", "stage", "commit",
|
||||
"notify", "event", "complete", "report", "complete",
|
||||
"notify", "claim", "event", "complete",
|
||||
"claim", "report", "complete",
|
||||
]
|
||||
assert calls[6][1]["idempotency_key"] == calls[2][1].event_key
|
||||
assert calls[8][1]["idempotency_key"] == calls[3][1].event_key
|
||||
assert calls[7][1]["idempotency_key"] == calls[2][1].event_key
|
||||
assert calls[10][1]["idempotency_key"] == calls[3][1].event_key
|
||||
|
||||
|
||||
def test_completion_stages_and_closes_notification_snapshot() -> None:
|
||||
@@ -164,3 +178,23 @@ def test_completion_stages_and_closes_notification_snapshot() -> None:
|
||||
assert staged[1].payload["message"]["title"] == "完成"
|
||||
completed = [call[1] for call in calls if call[0] == "complete"]
|
||||
assert completed[0].endswith(":notification")
|
||||
|
||||
|
||||
def test_completion_skips_sync_delivery_owned_by_outbox_dispatcher() -> None:
|
||||
"""后台已认领 intent 时同步路径不得再次发送相同副作用。"""
|
||||
calls = []
|
||||
command, notify, report = _command(calls, claim_result=False)
|
||||
|
||||
command.execute(
|
||||
7,
|
||||
{"id": 7, "media_source": "tmdb", "media_id": "123", "season": 2},
|
||||
{"title": "Test"},
|
||||
notify=notify,
|
||||
report=report,
|
||||
notification={"title": "完成", "text": "Test"},
|
||||
)
|
||||
|
||||
assert [call[0] for call in calls] == [
|
||||
"history", "delete", "stage", "stage", "stage", "commit",
|
||||
"claim", "claim", "claim",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user