fix(overview): 锁定核心高价值套装并修复未查询空值误判
- 高价值套装精确匹配「套装-兔兔白日梦」与「套装-狂战萌兔」,排除头盔/背包/手榴弹散件干扰 - 增加 is_suit_queried_expr,未查询或 NULL 账号严禁误判为双套均可兑换 - 修复前后端序列化空值保持,完善列表与详情页准入状态和判定依据展示 - 新增头盔排除与 NULL 统计防错回归单测,全量通过 689 个用例
This commit is contained in:
@@ -69,15 +69,28 @@ class TestOwnedJudgement:
|
||||
|
||||
def test_owned_comes_from_official_records(self):
|
||||
got = extract_high_value_goods(
|
||||
[GOODS_AVAILABLE], [RABBIT], [{"commodityName": "破片手榴弹-兔兔白日梦"}]
|
||||
[GOODS_AVAILABLE], [RABBIT], [{"commodityName": "套装-兔兔白日梦"}]
|
||||
)
|
||||
assert got[0]["owned"] is True
|
||||
assert got[0]["obtained_names"] == ["破片手榴弹-兔兔白日梦"]
|
||||
assert got[0]["obtained_names"] == ["套装-兔兔白日梦"]
|
||||
|
||||
def test_not_owned_when_records_empty(self):
|
||||
got = extract_high_value_goods([GOODS_AVAILABLE], [RABBIT], [])
|
||||
assert got[0]["owned"] is False
|
||||
|
||||
def test_accessories_never_imply_suit_owned(self):
|
||||
"""散件配件(手榴弹/头盔/背包)绝不能误判为拥有了高价值套装。"""
|
||||
got = extract_high_value_goods(
|
||||
[GOODS_AVAILABLE],
|
||||
[RABBIT],
|
||||
[
|
||||
{"commodityName": "破片手榴弹-兔兔白日梦"},
|
||||
{"commodityName": "兔兔白日梦头盔"},
|
||||
],
|
||||
)
|
||||
assert got[0]["owned"] is False
|
||||
assert got[0]["obtained_names"] == []
|
||||
|
||||
def test_global_stock_never_implies_owned(self):
|
||||
"""回归用例:全局库存为 0 或售罄,但 status==0 时,不能推出「这个账号已拥有」"""
|
||||
sold_out = {
|
||||
@@ -99,12 +112,12 @@ class TestOwnedJudgement:
|
||||
assert got[0]["owned"] is True
|
||||
assert got[0]["count"] == 0 # 全局库存如实展示
|
||||
|
||||
def test_records_match_by_substring_family(self):
|
||||
"""同一件东西有多个商品名(套装- / 破片手榴弹-),都要算数。"""
|
||||
def test_records_match_suit_names(self):
|
||||
"""兑换记录精确匹配套装名,过滤无关商品。"""
|
||||
got = extract_high_value_goods(
|
||||
[GOODS_AVAILABLE],
|
||||
[RABBIT],
|
||||
[{"commodityName": "破片手榴弹-兔兔白日梦"}, {"commodityName": "别的"}],
|
||||
[{"commodityName": "套装-兔兔白日梦"}, {"commodityName": "别的商品"}],
|
||||
)
|
||||
assert got[0]["owned"] is True
|
||||
assert len(got[0]["obtained_names"]) == 1
|
||||
@@ -1966,3 +1979,40 @@ class TestHighValueAccountFilters:
|
||||
assert hv["unqueried"] == 1 # acc_unqueried
|
||||
assert hv["high_points"] == 2 # 500, 800
|
||||
assert hv["high_value_broken"] == 1 # acc_broken
|
||||
|
||||
def test_helmet_and_backpack_never_match_over_suit(self):
|
||||
"""当商品列表中头盔排在前面时,必须精准匹配到套装-兔兔白日梦,严禁命中头盔。"""
|
||||
items = [
|
||||
{"commodityName": "兔兔白日梦头盔", "commodityId": "helmet_1", "status": 0},
|
||||
{"commodityName": "兔兔白日梦背包", "commodityId": "bag_1", "status": 0},
|
||||
{"commodityName": "套装-兔兔白日梦", "commodityId": "suit_1", "status": 1},
|
||||
]
|
||||
got = extract_high_value_goods(items, ["套装-兔兔白日梦"], [])
|
||||
assert len(got) == 1
|
||||
assert got[0]["name"] == "套装-兔兔白日梦"
|
||||
assert got[0]["commodity_id"] == "suit_1"
|
||||
assert got[0]["owned"] is True
|
||||
assert got[0]["reason"] == "官方商城显示已达兑换上限"
|
||||
|
||||
def test_null_high_value_owned_not_treated_as_both_available(self):
|
||||
"""high_value_owned 为 None 的账号(如被重置过),绝不能误判为双套均可兑换。"""
|
||||
from datetime import datetime, UTC
|
||||
from web.backend.models import Account
|
||||
from web.backend.routers.accounts import accounts_summary
|
||||
|
||||
acc = Account(
|
||||
username="acc_reset_null",
|
||||
password="p",
|
||||
email="e@test.com",
|
||||
email_password="ep",
|
||||
high_value_owned=None,
|
||||
overview_queried_at=datetime.now(UTC), # 虽然时间不为空,但字段是 None
|
||||
)
|
||||
self.session.add(acc)
|
||||
self.session.commit()
|
||||
|
||||
res = accounts_summary(db=self.session, current=self.admin)
|
||||
hv = res.get("high_value", {})
|
||||
# 绝不计入 both_available,应计入 unqueried
|
||||
assert hv["both_available"] == 2
|
||||
assert hv["unqueried"] == 2
|
||||
|
||||
Reference in New Issue
Block a user