127 lines
4.2 KiB
Python
127 lines
4.2 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""WP-PMAX-01 / WP-WX-02 离线回归:防封限流内存态逻辑
|
||
|
||
真机铁律口径:本测试**仅离线回归**内存态频控逻辑(不依赖 Redis/真机),
|
||
验证 AB-02 静默限流熔断 / AB-03 长暂停 + 高危动作互斥 / AB-06 风险分级的纯函数行为;
|
||
**不替代**真机批量发送/加友的频控 E2E。
|
||
|
||
运行:python3 sdk/tests/test_rate_limiter_antiban.py
|
||
"""
|
||
import sys
|
||
import time
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parents[2]
|
||
sys.path.insert(0, str(ROOT / "sdk" / "app"))
|
||
|
||
from services import rate_limiter as rl # noqa: E402
|
||
|
||
fails = 0
|
||
|
||
|
||
def check(cond, msg):
|
||
global fails
|
||
if cond:
|
||
print(f" ✓ {msg}")
|
||
else:
|
||
print(f" ✗ FAIL: {msg}")
|
||
fails += 1
|
||
|
||
|
||
print("[WP-PMAX-01] 防封限流离线回归")
|
||
lim = rl.AntiDetectRateLimiter()
|
||
DEV = "test_dev_offline"
|
||
|
||
# ---- AB-02 静默限流熔断 ----
|
||
print("· AB-02 静默限流熔断")
|
||
lim.reset_silent_throttle(DEV)
|
||
# 未触发时不应熔断
|
||
try:
|
||
lim._check_silent_throttle(DEV)
|
||
check(True, "初始无熔断,写类放行")
|
||
except rl.SilentThrottleCooldown:
|
||
check(False, "初始不应熔断")
|
||
|
||
cd1 = lim.trip_silent_throttle(DEV)
|
||
check(rl.SILENT_THROTTLE_COOLDOWN[0] <= cd1 <= rl.SILENT_THROTTLE_COOLDOWN[1] * 1.01,
|
||
f"首次熔断冷却在 30-60min 区间({cd1:.0f}s)")
|
||
raised = False
|
||
try:
|
||
lim._check_silent_throttle(DEV)
|
||
except rl.SilentThrottleCooldown as e:
|
||
raised = True
|
||
check(e.hits == 1, "熔断期写类被拒,hits=1")
|
||
check(raised, "熔断期 _check_silent_throttle 抛 SilentThrottleCooldown")
|
||
|
||
cd2 = lim.trip_silent_throttle(DEV)
|
||
check(cd2 > cd1, f"二次触发退避升级({cd2:.0f}s > {cd1:.0f}s)")
|
||
|
||
# 真 msg_id 回来 → reset 清零
|
||
lim.reset_silent_throttle(DEV)
|
||
try:
|
||
lim._check_silent_throttle(DEV)
|
||
check(True, "reset 后熔断解除,写类恢复")
|
||
except rl.SilentThrottleCooldown:
|
||
check(False, "reset 后不应再熔断")
|
||
|
||
# ---- AB-03 长暂停节拍 ----
|
||
print("· AB-03 长暂停 + 高危互斥")
|
||
rl._action_seq.pop(DEV, None)
|
||
rl._long_pause_until.pop(DEV, None)
|
||
rl._next_pause_at[DEV] = 3 # 强制第 3 个动作触发长暂停
|
||
pause_raised = False
|
||
for i in range(3):
|
||
try:
|
||
lim._check_long_pause(DEV)
|
||
except rl.LongPauseRequired as e:
|
||
pause_raised = True
|
||
check(rl.LONG_PAUSE_RANGE[0] <= e.wait_seconds <= rl.LONG_PAUSE_RANGE[1],
|
||
f"第{i+1}动作触发长暂停 {e.wait_seconds:.0f}s(10-30min)")
|
||
check(pause_raised, "达节拍阈值触发 LongPauseRequired")
|
||
# 暂停期内再检查应继续被拦
|
||
try:
|
||
lim._check_long_pause(DEV)
|
||
check(False, "长暂停期内应继续拦截")
|
||
except rl.LongPauseRequired:
|
||
check(True, "长暂停期内持续 fast-fail")
|
||
|
||
# 高危动作互斥
|
||
rl._last_highrisk.pop(DEV, None)
|
||
lim._check_highrisk_combo(DEV, "add_friend") # 记录首个高危
|
||
combo_raised = False
|
||
try:
|
||
lim._check_highrisk_combo(DEV, "mass_send") # 不同类高危,窗口内
|
||
except rl.HighRiskComboBlocked as e:
|
||
combo_raised = True
|
||
check(e.wait_seconds > 0, f"高危组合互斥拦截 {e.wait_seconds:.0f}s")
|
||
check(combo_raised, "短窗口内混跑不同类高危被拦")
|
||
# 同类高危不拦
|
||
try:
|
||
lim._check_highrisk_combo(DEV, "add_friend")
|
||
check(True, "同类高危动作不互斥")
|
||
except rl.HighRiskComboBlocked:
|
||
check(False, "同类高危不应互斥")
|
||
|
||
# ---- AB-06 风险分级 ----
|
||
print("· AB-06 风险分级")
|
||
check(rl.classify_risk("静默限流熔断中") == rl.RISK_NEEDS_HUMAN, "熔断→needs_human")
|
||
check(rl.classify_risk("高危动作组合互斥:add_friend") == rl.RISK_BLOCK, "高危互斥→block")
|
||
check(rl.classify_risk("当前不在操作时段") == rl.RISK_LOG, "时段→log")
|
||
check(rl.classify_risk("长会话中断,需再等 600s") == rl.RISK_LOG, "长暂停→log")
|
||
|
||
# ---- runtime 看板字段 ----
|
||
print("· AB-05 runtime 看板")
|
||
st = lim.get_runtime_state(DEV)
|
||
check("silent_throttle" in st and "long_pause" in st and "last_highrisk" in st,
|
||
"get_runtime_state 含三段运行态")
|
||
|
||
# 清理
|
||
lim.reset_silent_throttle(DEV)
|
||
rl._action_seq.pop(DEV, None)
|
||
rl._long_pause_until.pop(DEV, None)
|
||
rl._last_highrisk.pop(DEV, None)
|
||
|
||
print(f"\n[done] 失败 {fails} 条")
|
||
sys.exit(1 if fails else 0)
|