Files
live-hub-py/tests/test_migration_high_value_roundtrip.py
T
yml2213 4c1aff47da feat(douyu): 高价值套装改用官方 storehome 三态判定
高价值套装的准入判定改为只认官方精英宝典 storehome 里账号级的
status:0=可兑换、1=已达兑换上限,商品缺失/接口异常=unknown。
兑换记录和积分记录只作历史展示,不参与是否拥有的判定。

原来的 high_value_owned(JSON 名单)无法区分「确认可兑换」和
「官方接口没返回商品」,也无法表达部分失败。改为两套独立状态 +
检查时间(迁移 20261106_0065),旧快照保守回填:status 缺失的
一律 unknown,不猜。

CK 判定失效时在数据层作废结论(降为 unknown),不是展示层降级。
官方 status 是账号维度的,但 CK 失效那段时间账号可能被别人手工
登录并兑换(这些号共享给客服),缓存里的「可兑换」就不成立了;
只在展示层降级的话,CK 恢复后那条陈旧的「可换」会重新变绿。
error(查不到)不作废 —— 查不到不等于账号出问题了。

高价值结论有自己的时钟:资料刷新除了看 overview_queried_at,还看
high_value_checked_at。两者会脱钩(商品接口单独失败时资料时间被刷新、
高价值时间停在原地),少了这一条这类账号会永久停在旧结论。作废的
结论按 6 小时慢速重试,但「从来没查到过」不因此重选 —— 那是接口对
该账号稳定失败,每轮都重选只会反复撞墙。

商城商品列表改用精英宝典的 manualID 拉取,不再寄生在电竞手册分支里
(否则没配电竞手册的配置就永远拉不到商城,三态全部停在待确认)。

筛选拆成三类「查不到」:unqueried(未查资料,旧口径不变)、
high_value_never(从未读到商城)、high_value_unknown(查过但结论
未知/过期)。之前第二类无处可寻 —— 它既不满足 unqueried 也不满足
high_value_unknown。
2026-11-06 12:25:08 +08:00

88 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""0065 高价值三态迁移的 upgrade/downgrade 往返验证(真实 MySQL)。"""
import secrets
from urllib.parse import quote_plus
from alembic import command
from alembic.config import Config
from sqlalchemy import create_engine, inspect, text
import web.backend.database as database_module
from tests.support.db import get_mysql_credentials
def test_0065_upgrade_downgrade_roundtrip():
creds = get_mysql_credentials()
mig_db = f"douyu_login_mig_rt_{secrets.token_hex(4)}"
mig_url = (
f"mysql+pymysql://{quote_plus(creds['user'])}:{quote_plus(creds['password'])}"
f"@{creds['host']}:{creds['port']}/{mig_db}?charset=utf8mb4"
)
admin_engine = create_engine(
f"mysql+pymysql://{quote_plus(creds['user'])}:{quote_plus(creds['password'])}"
f"@{creds['host']}:{creds['port']}/?charset=utf8mb4"
)
with admin_engine.begin() as conn:
conn.execute(text(f"CREATE DATABASE `{mig_db}` CHARACTER SET utf8mb4"))
original = database_module.DATABASE_URL
try:
database_module.DATABASE_URL = mig_url
cfg = Config("alembic.ini")
cfg.set_main_option("script_location", "web/backend/migrations")
cfg.set_main_option("sqlalchemy.url", mig_url)
command.upgrade(cfg, "head")
insp = inspect(create_engine(mig_url))
cols = {c["name"] for c in insp.get_columns("accounts")}
assert "high_value_bairimeng_status" in cols
assert "high_value_mengtu_status" in cols
assert "high_value_checked_at" in cols
assert "high_value_owned" not in cols
assert "ix_accounts_high_value_checked_at" in {
i["name"] for i in insp.get_indexes("accounts")
}
# 塞两行数据:owned/owned 与 owned/unknown,验证降级数组干净
engine = create_engine(mig_url)
with engine.begin() as conn:
for i, (b, m) in enumerate([("owned", "owned"), ("owned", "unknown")]):
conn.execute(
text(
"INSERT INTO accounts (username, password, email, email_password,"
" cookie, high_value_bairimeng_status, high_value_mengtu_status)"
" VALUES (:u, 'p', 'e@x.com', 'p', '', :b, :m)"
),
{"u": f"rt-{i}", "b": b, "m": m},
)
command.downgrade(cfg, "20261021_0064")
insp = inspect(create_engine(mig_url))
cols = {c["name"] for c in insp.get_columns("accounts")}
assert "high_value_owned" in cols
assert "high_value_bairimeng_status" not in cols
assert "ix_accounts_high_value_checked_at" not in {
i["name"] for i in insp.get_indexes("accounts")
}
with engine.begin() as conn:
rows = conn.execute(
text(
"SELECT username, high_value_owned FROM accounts ORDER BY username"
)
).fetchall()
# JSON 列读出来是字符串;重点是里面**不能有 null**(旧实现会写出 [null,...])
assert rows[0][1] == '["套装-兔兔白日梦", "套装-狂战萌兔"]', rows[0]
assert rows[1][1] == '["套装-兔兔白日梦"]', rows[1]
assert "null" not in rows[0][1] and "null" not in rows[1][1]
command.upgrade(cfg, "head")
insp = inspect(create_engine(mig_url))
cols = {c["name"] for c in insp.get_columns("accounts")}
assert "high_value_owned" not in cols
assert "high_value_checked_at" in cols
finally:
database_module.DATABASE_URL = original
with admin_engine.begin() as conn:
conn.execute(text(f"DROP DATABASE IF EXISTS `{mig_db}`"))