fix(huya): 修复外部单积分复核与抢购结果误判

This commit is contained in:
yml
2026-11-11 18:41:16 +08:00
parent 26809dd116
commit c9814ab4d2
6 changed files with 372 additions and 50 deletions
+156 -3
View File
@@ -10,6 +10,7 @@
from __future__ import annotations
import json
from datetime import UTC, datetime, timedelta
from types import SimpleNamespace
import pytest
@@ -36,6 +37,7 @@ from web.backend.schemas import (
HuyaExternalOrderCreate,
HuyaExternalOrderListOut,
HuyaExternalOrderOut,
HuyaExternalOrderRechargeOut,
HuyaExternalOrderStatusBatchRequest,
HuyaExternalOrderUpdate,
)
@@ -2373,7 +2375,8 @@ class FakeRecordsResp:
"score": 200,
"status_label": "已发放",
"order_id": "H-ORDER-1",
"exchange_time": 1792000000,
# 测试接口拿不到微秒;加 1 秒,避免同一秒内被 not_before 边界误伤。
"exchange_time": int(datetime.now(UTC).timestamp()) + 1,
}
]
)
@@ -2483,6 +2486,7 @@ class TestCollabRecharge(TestCollabBase):
orders,
threshold=threshold,
client=client,
confirm_delay_seconds=0,
)
def test_success_recharges_whole_group_and_snapshots_every_order(self):
@@ -2537,6 +2541,7 @@ class TestCollabRecharge(TestCollabBase):
first = self._ready("RC-4", "1003", gift_name="礼包A", required_points=200)
second = self._ready("RC-5", "1003", gift_name="礼包B", required_points=300)
client = FakeRechargeClient(
FakeBindResp(nick=self.BIND, role=self.ROLE),
FakeBindResp(nick=self.BIND, role=self.ROLE),
score=self._score(400),
)
@@ -2552,6 +2557,7 @@ class TestCollabRecharge(TestCollabBase):
def test_excess_marks_whole_group_points_error(self):
order = self._ready("RC-6", "1004", gift_name="礼包A", required_points=500)
client = FakeRechargeClient(
FakeBindResp(nick=self.BIND, role=self.ROLE),
FakeBindResp(nick=self.BIND, role=self.ROLE),
score=self._score(650),
)
@@ -2563,6 +2569,93 @@ class TestCollabRecharge(TestCollabBase):
assert order.status == collab.STATUS_POINTS_ERROR
assert "多出 150 分" in order.points_check_message
def test_zero_balance_is_rechecked_before_marking_points_error(self):
"""首次 0 分可能尚未同步,二次确认到账后应直接通过。"""
order = self._ready(
"RC-RETRY-1", "1015", gift_name="礼包A", required_points=988
)
client = FakeRechargeClient(
FakeBindResp(nick=self.BIND, role=self.ROLE),
FakeBindResp(nick=self.BIND, role=self.ROLE),
scores=[self._score(0), self._score(988)],
)
result = self._check([order], client)
group = result["groups"][0]
assert group["status"] == collab.STATUS_RECHARGED
assert group["balance"] == 988
assert order.status == collab.STATUS_RECHARGED
assert len(client.score_calls) == 2
confirmation = order.points_check_detail["confirmation"]
assert confirmation["attempts"] == 2
assert confirmation["first_balance"] == 0
assert confirmation["first_reason"] == "shortage"
assert result["confirmed_groups"] == 1
dumped = HuyaExternalOrderRechargeOut(**result).model_dump()
assert dumped["confirmed_groups"] == 1
assert dumped["confirmation_delay_seconds"] == 0
assert dumped["groups"][0]["status"] == collab.STATUS_RECHARGED
def test_points_error_is_only_written_after_two_failed_reads(self):
order = self._ready(
"RC-RETRY-2", "1016", gift_name="礼包A", required_points=988
)
client = FakeRechargeClient(
FakeBindResp(nick=self.BIND, role=self.ROLE),
FakeBindResp(nick=self.BIND, role=self.ROLE),
scores=[self._score(0), self._score(0)],
)
result = self._check([order], client)
assert result["confirmed_groups"] == 1
assert len(client.score_calls) == 2
assert order.status == collab.STATUS_POINTS_ERROR
assert order.points_check_reason == "shortage"
assert order.points_check_detail["confirmation"]["first_balance"] == 0
def test_confirmation_waits_once_for_whole_batch(self, monkeypatch):
first = self._ready(
"RC-RETRY-3",
"1017",
gift_name="礼包A",
required_points=988,
role="角色A",
)
second = self._ready(
"RC-RETRY-4",
"1018",
gift_name="礼包B",
required_points=988,
role="角色B",
)
client = FakeRechargeClient(
FakeBindResp(nick=self.BIND, role="角色A"),
FakeBindResp(nick=self.BIND, role="角色B"),
FakeBindResp(nick=self.BIND, role="角色A"),
FakeBindResp(nick=self.BIND, role="角色B"),
scores=[self._score(0), self._score(0), self._score(0), self._score(0)],
)
sleeps: list[float] = []
monkeypatch.setattr(collab.time, "sleep", sleeps.append)
result = collab.check_recharge_groups(
self.db,
self.user,
[first, second],
threshold=100,
client=client,
confirm_delay_seconds=30,
)
assert sleeps == [30]
assert result["confirmed_groups"] == 2
assert [group["status"] for group in result["groups"]] == [
collab.STATUS_POINTS_ERROR,
collab.STATUS_POINTS_ERROR,
]
def test_multiple_uids_are_summed_and_each_queried_once(self):
first = self._ready("RC-7", "1005", gift_name="礼包A", required_points=300)
second = self._ready("RC-8", "1006", gift_name="礼包B", required_points=300)
@@ -2991,7 +3084,8 @@ class TestCollabActivityLifecycle(TestCollabBase):
row.action for row in self._audits(order)
]
def test_missing_gift_marks_failed_without_clearing_scripted_time(self):
def test_missing_gift_in_settle_window_stays_unknown(self):
"""生产回归:官方记录会晚于脚本查询落库,窗口内不能自动判失败。"""
order = self._scripted_order("ACT-2")
client = FakeActivityClient(
records=FakeRecordsResp(
@@ -3001,9 +3095,30 @@ class TestCollabActivityLifecycle(TestCollabBase):
result = collab.query_order_activity(self.db, self.user, order, client=client)
assert result["verdict"] == collab.VERDICT_UNKNOWN
assert result["auto_status"] == ""
assert order.status == collab.STATUS_SCRIPTED
assert order.scripted_at is not None
assert order.done_at is None
assert "抢购确认窗口" in result["message"]
def test_missing_gift_after_settle_window_marks_failed(self):
order = self._scripted_order("ACT-2-OLD")
order.scripted_at = datetime.now(UTC) - timedelta(
seconds=collab.ACTIVITY_RESULT_SETTLE_SECONDS + 1
)
self.db.commit()
client = FakeActivityClient(
records=FakeRecordsResp(
records=[{"prize_name": "别的礼包", "status_label": "已发放"}]
)
)
result = collab.query_order_activity(self.db, self.user, order, client=client)
assert result["verdict"] == collab.VERDICT_FAILED
assert result["auto_status"] == "failed"
assert order.status == "failed"
assert order.status == collab.STATUS_FAILED
# failed 是旁路终态:上脚本的时间戳必须留着(不然不知道什么时候上的)
assert order.scripted_at is not None
assert order.done_at is None
@@ -3015,6 +3130,44 @@ class TestCollabActivityLifecycle(TestCollabBase):
collab.set_status(self.db, self.user, order, "scripted", rollback=True)
assert order.status == "scripted"
def test_late_matching_record_promotes_failed_to_done(self):
order = self._scripted_order("ACT-2-LATE")
order.status = collab.STATUS_FAILED
order.scripted_at = datetime.now(UTC) - timedelta(minutes=15)
self.db.commit()
result = collab.query_order_activity(
self.db, self.user, order, client=FakeActivityClient()
)
assert result["verdict"] == collab.VERDICT_SUCCESS
assert result["auto_status"] == collab.STATUS_DONE
assert order.status == collab.STATUS_DONE
assert order.done_at is not None
def test_matching_record_before_script_does_not_count_as_current_success(self):
order = self._scripted_order("ACT-2-OLD-MATCH")
old_record = {
"prize_name": "不朽伊人大礼包",
"score": 200,
"status_label": "已发放",
"order_id": "H-OLD",
"exchange_time": int(
(datetime.now(UTC) - timedelta(minutes=20)).timestamp()
),
}
result = collab.query_order_activity(
self.db,
self.user,
order,
client=FakeActivityClient(records=FakeRecordsResp(records=[old_record])),
)
assert result["verdict"] == collab.VERDICT_UNKNOWN
assert result["records_matched"] == 0
assert order.status == collab.STATUS_SCRIPTED
def test_status_untouched_when_order_is_not_scripted(self):
order = self._bind_ready(
self._create(order_no="ACT-3", gift_name="不朽伊人大礼包")