joy-cli/tests/test_manhuang_chain.py
feibisi d58964c105 feat(manhuang): 面值折扣、e2e 能力探针、接码策略与本机号池路径
礼品卡 face_min/max + discount_at_*;e2e E0–E3(可选 --call);
chain-policy 接受 sms 单点;本机 codex health 路径约定;碎银折扣列。
2026-07-13 09:44:59 +08:00

200 lines
7.3 KiB
Python
Raw Permalink 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.

"""chain_v2 breaks · sms single-source · path_id · pool burn."""
from __future__ import annotations
import json
from pathlib import Path
from joy.dashboard import manhuang as mh
def test_compute_path_id_stable():
np = {
"primary": {
"name": "clash-meta-9090",
"mode": "rule",
"selected": "香港300",
"mixed_port": 7893,
},
"n2_n3_egress": {"via_proxy": {"ip": "1.2.3.4"}},
}
a = mh._compute_path_id(np)
b = mh._compute_path_id(np)
assert a and a.startswith("path_")
assert a == b
assert mh._compute_path_id({"missing": True}) is None
def test_sms_source_meta_reads_repo_catalog():
sms = mh._sms_source_meta()
assert isinstance(sms, dict)
assert "active_n" in sms
# 仓库 channels.json5sim activeSMS-Activate shutdown → 通常单点
if sms.get("active_n") == 1:
assert sms.get("single_source") is True
assert "5sim" in str(sms.get("primary") or "")
def test_build_chain_status_sms_single_and_breaks(tmp_path=None):
import tempfile
td = Path(tmp_path) if tmp_path else Path(tempfile.mkdtemp())
old = mh._POOL_QUOTA_HIST
mh._POOL_QUOTA_HIST = td / "quota.jsonl"
try:
hist = mh._POOL_QUOTA_HIST
hist.parent.mkdir(parents=True, exist_ok=True)
# 两笔历史制造 burn_record 会再写一行)
hist.write_text(
json.dumps({"epoch": 1000.0, "quota": 20, "total": 30}) + "\n"
+ json.dumps({"epoch": 4600.0, "quota": 5, "total": 30}) + "\n",
encoding="utf-8",
)
pmeta = mh._record_pool_quota(5, 30, ts="2026-07-13T00:00:00Z")
assert pmeta.get("samples", 0) >= 2
sms = {
"active_n": 1,
"watch_n": 2,
"active_ids": ["5sim-guest"],
"single_source": True,
"primary": "5sim-guest",
}
summary = {
"codex_total": 10,
"codex_quota": 5,
"codex_exhausted": 3,
"codex_dead": 2,
"live_probes_ok": 3,
"live_probes_fail": 0,
"free_endpoints": 2,
"tor_socks": True,
"tor_active": 1,
"guishi_channels": 4,
"proxies_alive": 0,
"miling_need_register": 0,
}
net_path = {
"ok": True,
"primary": {
"name": "clash-meta-9090",
"ok": True,
"mode": "rule",
"selected": "node-a",
"mixed_port": 7893,
},
"n2_n3_egress": {"via_proxy": {"ip": "9.9.9.9"}, "same_as_direct": False},
"n4_n5_isolation": {"risk": "ok"},
"system_proxy": {"enabled": True},
"headline": "ok",
}
chain = mh._build_chain_status(
summary=summary,
miling={"summary": {}},
blackmarket={"skus": [], "demand": [], "channels": []},
pool={},
net_path=net_path,
relays={"counts": {"n": 5, "ok": 5, "down": 0}},
pool_meta=pmeta,
sms_meta=sms,
)
assert chain.get("schema") == "chain_v2"
assert chain.get("path_id")
raw = next(r for r in chain["rings"] if r["id"] == "raw")
assert raw["level"] == "warn"
assert raw.get("reason") == "sms_single_source"
reasons = {b.get("reason") for b in (chain.get("breaks") or [])}
assert "sms_single_source" in reasons
assert chain.get("top_break")
finally:
mh._POOL_QUOTA_HIST = old
def test_face_discount_range_and_fixed():
import importlib.util
from pathlib import Path
p = Path(__file__).resolve().parents[1] / "scripts/manhuang/probe-ai-account-quotes.py"
spec = importlib.util.spec_from_file_location("ai_quotes2", p)
mod = importlib.util.module_from_spec(spec)
assert spec and spec.loader
spec.loader.exec_module(mod)
r = mod.face_discount_fields("Apple iTunes Gift Card 1-100 USD (USA)", 1.81)
assert r["face_kind"] == "range"
assert r["discount"] is None
assert r["discount_note"] == "multi_denom_listing_min_price"
assert abs(r["discount_at_max"] - 0.0181) < 1e-6
f = mod.face_discount_fields("Apple Gift Card 100 USD", 85.0)
assert f["face_kind"] == "fixed"
assert abs(f["discount"] - 0.85) < 1e-6
def test_ggsel_price_wme_parser():
"""内嵌 price_wme JSON 可被抽成礼品卡市价。"""
import importlib.util
from pathlib import Path
p = Path(__file__).resolve().parents[1] / "scripts/manhuang/probe-ai-account-quotes.py"
spec = importlib.util.spec_from_file_location("ai_quotes", p)
mod = importlib.util.module_from_spec(spec)
assert spec and spec.loader
spec.loader.exec_module(mod)
html = (
'{"id_goods":111,"url":"apple-itunes-gift-card-1-100-usd-usa-111",'
'"is_active":true,"name":"Apple iTunes Gift Card 1-100 USD (USA)",'
'"price_wme":"1.81","cnt_sell":10}'
'{"id_goods":222,"url":"turkey-itunes-222",'
'"name":"iTunes Gift Card 10-5000 TL Turkey AUTO","price_wme":"0.22"}'
'{"id_goods":333,"url":"apple-id-usa-333",'
'"name":"[US] USA Apple ID Solo Access","price_wme":"1.48"}'
)
us = mod.parse_ggsel_catalog(
html, r"(gift\s*card|iTunes).{0,40}(USA|US\b|USD)|Apple iTunes Gift Card.{0,20}USD"
)
assert any(abs(q["price"] - 1.81) < 0.01 for q in us), us
assert not any("Apple ID" in (q.get("title") or "") for q in us)
tr = mod.parse_ggsel_catalog(
html, r"(gift\s*card|iTunes).{0,40}(Turkey|TRY|TL\b)|Turkey.{0,20}(gift|iTunes)"
)
assert any(abs(q["price"] - 0.22) < 0.01 for q in tr), tr
def test_pay_msrp_only_reason():
pay = mh._pay_rail_meta(
quotes_skus=[
{"id": "giftcard_us", "tier": "pay", "best": {"kind": "msrp", "price": 100, "channel": "官方", "currency": "USD"}},
{"id": "virtual_card", "tier": "pay", "best": {"kind": "msrp", "price": 5, "channel": "official-msrp", "currency": "USD"}},
{"id": "sms_us", "tier": "pay", "best": {"kind": "market", "price": 0.1, "channel": "5sim guest", "currency": "USD"}},
]
)
assert pay.get("market_n") == 0
assert pay.get("msrp_n") == 2
assert pay.get("reason") == "pay_msrp_only"
ids = {r["id"] for r in pay.get("rails") or []}
assert "sms_us" not in ids
def test_pool_empty_is_crisis():
chain = mh._build_chain_status(
summary={
"codex_total": 12,
"codex_quota": 0,
"live_probes_ok": 1,
"live_probes_fail": 0,
"free_endpoints": 0,
"tor_socks": True,
"tor_active": 1,
},
miling={"summary": {}},
blackmarket={"skus": [], "demand": [], "channels": []},
pool={},
net_path={
"ok": True,
"primary": {"ok": True, "mode": "rule", "selected": "n", "name": "c", "mixed_port": 1},
"n4_n5_isolation": {"risk": "ok"},
"system_proxy": {"enabled": True},
},
sms_meta={"active_n": 2, "single_source": False, "watch_n": 0, "active_ids": ["a", "b"]},
pool_meta={},
)
assert chain["mode"] == "crisis_pool"
assert chain["crisis"] is True
pool = next(r for r in chain["rings"] if r["id"] == "pool")
assert pool["level"] == "err"
assert pool.get("reason") == "pool_empty"