"""代理鉴权/限流测试(T-P1)。""" import threading import time import pytest from gateway.proxy.auth import ( KEY_PREFIX, RateLimiter, authenticate, issue_key, reset_auth_state, verify_admin, ) from gateway.proxy.errors import ( ProxyAuthError, QuotaError, SuspendedError, ) from gateway.proxy.ledger import Ledger @pytest.fixture() def env(tmp_path): reset_auth_state() led = Ledger.init_db(tmp_path / "proxy.sqlite3") sid = led.upsert_student("张三", "py24", balance_yuan=10.0, daily_cap_yuan=5.0) yield {"ledger": led, "sid": sid} reset_auth_state() def test_issue_and_authenticate_roundtrip(env): """签发 -> 鉴权通过 -> 上下文含学生信息。""" led = env["ledger"] k = issue_key(led, env["sid"], rpm_cap=10, day_cap_req=200) assert k["key"].startswith(KEY_PREFIX) ctx = authenticate(f"Bearer {k['key']}", led, _AUTH_LIMITS()) assert ctx["key_id"] == k["key_id"] assert ctx["student_id"] == env["sid"] assert ctx["student_status"] == "active" def _AUTH_LIMITS(): from gateway.proxy.auth import _AUTH_SINGLETON_LIMITS return _AUTH_SINGLETON_LIMITS def test_auth_bad_key_401(env): led = env["ledger"] with pytest.raises(ProxyAuthError): authenticate("Bearer sk-campus-wrong", led, _AUTH_LIMITS()) with pytest.raises(ProxyAuthError): authenticate("wrong-scheme", led, _AUTH_LIMITS()) with pytest.raises(ProxyAuthError): authenticate("", led, _AUTH_LIMITS()) def test_auth_revoked_401(env): """注销后鉴权 401(热缓存主动失效路径)。""" led = env["ledger"] k = issue_key(led, env["sid"]) authenticate(f"Bearer {k['key']}", led, _AUTH_LIMITS()) # 热缓存 assert led.revoke_key(k["key_id"]) is True # 注销后缓存内 ctx 仍会查库 revoked 字段?——当前实现缓存整行: # revoke 后 find_key 不再命中;这里强制走一次缓存过期模拟最坏 30s 窗口 reset_auth_state() with pytest.raises(ProxyAuthError): authenticate(f"Bearer {k['key']}", led, _AUTH_LIMITS()) def test_auth_suspended_403(env): led = env["ledger"] k = issue_key(led, env["sid"]) led.set_status(env["sid"], "suspended") reset_auth_state() with pytest.raises(SuspendedError): authenticate(f"Bearer {k['key']}", led, _AUTH_LIMITS()) def test_rpm_token_bucket_429(env): """rpm 令牌桶:突发超过容量被拒(进程内)。""" led = env["ledger"] k = issue_key(led, env["sid"], rpm_cap=3, day_cap_req=1000) results = [authenticate(f"Bearer {k['key']}", led, _AUTH_LIMITS()) for _ in range(3)] assert len(results) == 3 with pytest.raises(QuotaError): authenticate(f"Bearer {k['key']}", led, _AUTH_LIMITS()) def test_day_req_cap_429(env): """日请求上限(持久计数,跨进程语义):第 cap+1 次 429。""" led = env["ledger"] k = issue_key(led, env["sid"], rpm_cap=1000, day_cap_req=5) for _ in range(5): authenticate(f"Bearer {k['key']}", led, _AUTH_LIMITS()) with pytest.raises(QuotaError): authenticate(f"Bearer {k['key']}", led, _AUTH_LIMITS()) def test_rate_limiter_concurrent_slots(): """并发槽信号量:acquire/release 配平。""" rl = RateLimiter(concurrent_per_key=2) assert rl.acquire_slot(1) is True assert rl.acquire_slot(1) is True assert rl.acquire_slot(1) is False rl.release_slot(1) assert rl.acquire_slot(1) is True rl.release_slot(1) rl.release_slot(1) def test_verify_admin_policy(): """配置 admin_key -> compare_digest;未配置 -> 仅 loopback。""" assert verify_admin("secret", "secret", "1.2.3.4") is True assert verify_admin("wrong", "secret", "127.0.0.1") is False assert verify_admin("", "", "127.0.0.1") is True assert verify_admin("", "", "8.8.8.8") is False