|
| 1 | +#!/usr/bin/env python3 |
| 2 | +"""后台决策测试 —— 逐条对齐 Mini App「蹬王之王」的 decide()。 |
| 3 | +
|
| 4 | +Mini App 的分支(packages/expiry-guardian/miniapp/node/server.mjs::decide): |
| 5 | + 未耗尽 → idle |
| 6 | + 耗尽 + 无积分 + 有卡 → use-card |
| 7 | + 耗尽 + 无积分 + 无卡 → wait |
| 8 | + 耗尽 + 有积分 + 无卡 → keep-credit |
| 9 | + 耗尽 + 都有,卡先过期 → use-card |
| 10 | + 耗尽 + 都有,积分先过期 → keep-credit |
| 11 | +
|
| 12 | +本文件逐条复刻这些分支,任一不一致即视为失败。 |
| 13 | +纯函数测试:不触网、不扣卡。 |
| 14 | +""" |
| 15 | +import importlib.util |
| 16 | +import sys |
| 17 | +import time |
| 18 | +from pathlib import Path |
| 19 | + |
| 20 | +spec = importlib.util.spec_from_file_location( |
| 21 | + "bg", Path(__file__).with_name("reset_card_autouse.py") |
| 22 | +) |
| 23 | +m = importlib.util.module_from_spec(spec) |
| 24 | +sys.modules["bg"] = m |
| 25 | +spec.loader.exec_module(m) |
| 26 | + |
| 27 | +NOW = int(time.time() * 1000) |
| 28 | +DAY = 86400000 |
| 29 | + |
| 30 | + |
| 31 | +def qs(quota_type, used, total=100.0, unlimited=False): |
| 32 | + return m.QuotaState(quota_type=quota_type, total=total, used=used, is_unlimited=unlimited) |
| 33 | + |
| 34 | + |
| 35 | +def card(days): |
| 36 | + return { |
| 37 | + "card_id": f"C{days}", |
| 38 | + "out_biz_no": f"biz:{days}", |
| 39 | + "source_type": "mcode_signin", |
| 40 | + "expire_time": NOW + days * DAY, |
| 41 | + } |
| 42 | + |
| 43 | + |
| 44 | +passed = failed = 0 |
| 45 | + |
| 46 | + |
| 47 | +def check(name, got, want): |
| 48 | + global passed, failed |
| 49 | + if got == want: |
| 50 | + passed += 1 |
| 51 | + print(f" PASS {name} -> {got}") |
| 52 | + else: |
| 53 | + failed += 1 |
| 54 | + print(f" FAIL {name} -> {got} (want {want})") |
| 55 | + |
| 56 | + |
| 57 | +def decide(used5, used7d=None, cards=None, credit_days=None): |
| 58 | + quotas = [qs(1, used5)] |
| 59 | + quotas.append(qs(2, 0, unlimited=True) if used7d is None else qs(2, used7d)) |
| 60 | + exp = None if credit_days is None else NOW + credit_days * DAY |
| 61 | + return m.decide_background(quotas, cards or [], exp, "平台积分")[0] |
| 62 | + |
| 63 | + |
| 64 | +print("=== Mini App 分支 1:未到触发线(99%) → idle ===") |
| 65 | +# 上界取 98 而不是 99:触发线就是 99%,99% 已经属于「该动手了」, |
| 66 | +# 那条在下面单独断言。早先这里写的是 99,是 99% 提前触发设计落地之前的残留。 |
| 67 | +for pct in (0, 50, 90, 98): |
| 68 | + check(f"5h 已用 {pct}% → idle", decide(pct, cards=[card(7)], credit_days=13), "idle") |
| 69 | + |
| 70 | +# 触发线本身就应该动手 —— 额度没真正耗尽,但再等一次请求就会失败。 |
| 71 | +check("5h 已用 99% → use-card(触发线)", decide(99, cards=[card(7)], credit_days=13), "use-card") |
| 72 | + |
| 73 | +print("\n=== Mini App 分支 2:耗尽 + 无积分 + 有卡 → use-card ===") |
| 74 | +check("5h 100% + 无积分 + 有卡", decide(100, cards=[card(7)], credit_days=None), "use-card") |
| 75 | + |
| 76 | +print("\n=== Mini App 分支 3:耗尽 + 无积分 + 无卡 → wait ===") |
| 77 | +check("5h 100% + 无积分 + 无卡", decide(100, cards=[], credit_days=None), "wait") |
| 78 | + |
| 79 | +print("\n=== Mini App 分支 4:耗尽 + 有积分 + 无卡 → keep-credit ===") |
| 80 | +check("5h 100% + 有积分 + 无卡", decide(100, cards=[], credit_days=13), "keep-credit") |
| 81 | + |
| 82 | +print("\n=== Mini App 分支 5:耗尽 + 都有 + 卡先过期 → use-card ===") |
| 83 | +check("卡 7 天 / 积分 13 天", decide(100, cards=[card(7)], credit_days=13), "use-card") |
| 84 | +check("卡 5 天 / 积分 30 天", decide(100, cards=[card(5)], credit_days=30), "use-card") |
| 85 | + |
| 86 | +print("\n=== Mini App 分支 6:耗尽 + 都有 + 积分先过期 → keep-credit ===") |
| 87 | +check("卡 20 天 / 积分 5 天", decide(100, cards=[card(20)], credit_days=5), "keep-credit") |
| 88 | +check("卡 30 天 / 积分 1 天", decide(100, cards=[card(30)], credit_days=1), "keep-credit") |
| 89 | + |
| 90 | +print("\n=== 周限额单独耗尽也应触发(与 Mini App 的 weekly.exhausted 一致)===") |
| 91 | +check("5h 未耗尽 + 周 100% → 进入判定", |
| 92 | + decide(10, used7d=100, cards=[card(7)], credit_days=13), "use-card") |
| 93 | +check("5h 未耗尽 + 周未耗尽 → idle", |
| 94 | + decide(10, used7d=50, cards=[card(7)], credit_days=13), "idle") |
| 95 | + |
| 96 | +print("\n=== 不限量的周限额不算耗尽(与 is_unlimited 判定一致)===") |
| 97 | +check("周不限量 + 5h 100% + 卡先过期", decide(100, cards=[card(7)], credit_days=13), "use-card") |
| 98 | + |
| 99 | +print("\n=== 理由文本 ===") |
| 100 | +cases = [ |
| 101 | + ("未耗尽", ([qs(1, 50), qs(2, 0, unlimited=True)], [card(7)], NOW + 13 * DAY, "平台积分"), "额度充足"), |
| 102 | + ("无积分用卡", ([qs(1, 100), qs(2, 0, unlimited=True)], [card(7)], None, ""), "没有可用积分"), |
| 103 | + ("无卡留积分", ([qs(1, 100), qs(2, 0, unlimited=True)], [], NOW + 13 * DAY, "平台积分"), "没有可用的重置卡"), |
| 104 | + ("卡先过期", ([qs(1, 100), qs(2, 0, unlimited=True)], [card(7)], NOW + 13 * DAY, "平台积分"), "先用卡保住积分"), |
| 105 | + ("积分先过期", ([qs(1, 100), qs(2, 0, unlimited=True)], [card(20)], NOW + 5 * DAY, "平台积分"), "让积分先消耗"), |
| 106 | +] |
| 107 | +for label, args, needle in cases: |
| 108 | + _, reason = m.decide_background(*args) |
| 109 | + ok = isinstance(reason, str) and needle in reason |
| 110 | + if ok: |
| 111 | + passed += 1 |
| 112 | + else: |
| 113 | + failed += 1 |
| 114 | + print(f" {'PASS' if ok else 'FAIL'} {label}: {reason}") |
| 115 | + |
| 116 | +print(f"\n{passed}/{passed + failed} passed") |
| 117 | +sys.exit(0 if failed == 0 else 1) |
0 commit comments