fix(account): 补上电竞绑定映射漏掉的 game_not_bound(连生产库才发现的)

把生产库克隆到本地做部署演练时,拉了三列绑定状态的**真实取值全集**,发现
`esports_bind_status` 里存在 `game_not_bound`(生产 26 行),而我的映射表里没有它
—— 会被归成 `unknown`,可它明明在说「没绑定」。

第一版映射只覆盖了文档里举的几个例子。**教训:映射表必须拿真实取值全集核对。**

修法:Python 映射与迁移 `0044` 的 CASE 同步补上 `game_not_bound → unbound`
(两者有逐字比对用例,不会分叉),并新增一条用**生产实测取值全集**驱动的用例
(17 + 11 + 9 个取值),以后往这三列加新取值时会被提醒确认语义,
而不是默默变成 `unknown`。

已在生产克隆库上验证:电竞列 unbound 从 0 变成 26(正是那批)。
This commit is contained in:
yml2213
2026-10-04 17:49:02 +08:00
parent eec42aa8a7
commit 4c1fe0c84f
3 changed files with 108 additions and 0 deletions
+102
View File
@@ -354,3 +354,105 @@ class TestNoFreeTextWriters:
"绑定状态只能由 account_binding.set_bind_status 写;"
"旧 bind_status 列不允许任何写入方:\n" + "\n".join(offenders)
)
class TestRealProductionValuesCovered:
"""用**生产库真实取值全集**核对映射表。
这是被真实数据打脸换来的用例:第一版映射只覆盖了文档里举的例子,
而生产的电竞列里还有 `game_not_bound`(26 行)—— 漏了它会被归成
`unknown`,可它明明在说「没绑定」。
⚠️ 下面的取值列表是从生产库 `GROUP BY` 拉出来的**全集**。以后往这三列加
新取值时,这里应该同步补上;新增取值如果不在映射里,用例会提醒你
确认它到底该归成哪种状态,而不是默默变成 `unknown`。
"""
#: 生产库实测(2026-10-04,15,182 个账号)
PRODUCTION_VALUES: ClassVar[dict[str, list[str | None]]] = {
"elite": [
"goods_exchanged",
"limited_goods_queried",
"gift_donated",
"",
"change_time_queried",
"game_queried",
"bind_confirmed",
"gold_recharged",
"gold_balance_queried",
"points_queried",
"elite_status_queried",
"bind_qr_generated",
"game_not_bound",
"elite_qr_created",
"elite_opened",
"gold_api_order_created",
"gold_qr_created",
],
"esports": [
"",
"esports_gift_donated",
"esports_bound",
"esports_goods_exchanged",
"esports_points_queried",
"esports_bind_ready",
"game_not_bound",
"esports_goods_refreshed",
"esports_qr_created",
"esports_opened",
"esports_bind_confirming",
],
"peace": [
"",
None,
"xpd_purchase_records_queried",
"xpd_bound",
"xpd_balance_queried",
"xpd_goods_exchanged",
"xpd_bind_qr_ready",
"xpd_fragments_queried",
"xpd_goods_refreshed",
],
}
#: 明确陈述了绑定关系、必须映射成非 unknown 的取值
MUST_BE_MEANINGFUL: ClassVar[dict[tuple[str, str], str]] = {
("elite", "bind_confirmed"): "bound",
("elite", "bind_qr_generated"): "pending",
("elite", "game_not_bound"): "unbound",
("esports", "esports_bound"): "bound",
("esports", "esports_bind_ready"): "pending",
("esports", "esports_bind_confirming"): "pending",
("esports", "esports_qr_created"): "pending",
# ← 生产实测发现的那一条
("esports", "game_not_bound"): "unbound",
("peace", "xpd_bound"): "bound",
("peace", "xpd_bind_qr_ready"): "pending",
}
@pytest.mark.parametrize(
("scope", "legacy", "expected"),
[(*key, value) for key, value in MUST_BE_MEANINGFUL.items()],
)
def test_meaningful_values_map_exactly(self, scope, legacy, expected):
assert binding.map_legacy_bind_status(scope, legacy) == expected
def test_every_production_value_maps_to_a_valid_state(self):
for scope, values in self.PRODUCTION_VALUES.items():
for value in values:
state = binding.map_legacy_bind_status(scope, value)
assert state in binding.ALL_BIND_STATES, f"{scope}/{value!r}"
def test_no_production_value_is_silently_unknown_when_it_should_not_be(self):
"""反向检查:该有含义的值不能全落成 unknown。"""
for scope, values in self.PRODUCTION_VALUES.items():
meaningful = {
v for (s, v), _ in self.MUST_BE_MEANINGFUL.items() if s == scope
}
mapped = {
v
for v in values
if binding.map_legacy_bind_status(scope, v) != "unknown"
}
missing = meaningful - mapped
assert not missing, f"{scope} 里这些值被错归成 unknown:{missing}"
@@ -87,6 +87,7 @@ def normalise_esports_bind_status(bind) -> None:
'esports_qr_created',
'esports_role_switching'
) THEN 'pending'
WHEN esports_bind_status = 'game_not_bound' THEN 'unbound'
ELSE 'unknown'
END
"""
+5
View File
@@ -105,6 +105,11 @@ LEGACY_ESPORTS_BIND_MAP: dict[str, str] = {
"esports_bind_confirming": BIND_PENDING,
"esports_qr_created": BIND_PENDING,
"esports_role_switching": BIND_PENDING,
# ⚠️ 这一条是**连生产库看真实数据**才发现的:电竞列里确实存在
# `game_not_bound`(生产 26 行)。漏了它就会被归成 `unknown`,
# 而它明明在说「没绑定」。教训:映射表必须拿**真实取值全集**核对,
# 不能只看文档里举的几个例子。
"game_not_bound": BIND_UNBOUND,
}
LEGACY_XPD_BIND_MAP: dict[str, str] = {