fix(huya): 修复外部单积分复核与抢购结果误判
This commit is contained in:
@@ -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="不朽伊人大礼包")
|
||||
|
||||
Reference in New Issue
Block a user