Files
projectAIpopular/tests/test_proxy_routes.py
T
tzt b109576707 feat(proxy): T-P0 代理层骨架(包结构/DDL/门控挂载/单进程校验)
- gateway/proxy/ 十文件:config(ProxyConfig:元->毫元加载期换算 D-P1、差异化售价、
  桶/峰谷/限流/semcache 配置)/ errors(8 类 HTTP 语义异常)/ ledger(四表两索引 DDL,
  WAL,ReviewQueue 连接纪律)/ auth·pricing·normalizer·semcache·upstream(§6 签名占位)
  / routes(/proxy/v1/models OpenAI 形状)/ __init__(build_proxy_router 组装点)
- settings DEFAULTS 增 proxy 段(enabled 默认 False,D-P7)
- api.py 首次 include_router(enabled 门控 + 装配失败不拖垮主应用)
- serve.py workers>1 拒绝启动(D-P9:WEB_CONCURRENCY/UVICORN_WORKERS 校验)
- 测试 +4(门控 404/DDL 幂等/毫元换算/池模型列表),全量 322 passed
2026-09-05 08:35:31 +08:00

88 lines
3.5 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.
"""代理层骨架测试(T-P0):enabled 门控 / DDL / 独立挂载的 models 端点。"""
import pytest
pytest.importorskip("fastapi")
from fastapi import FastAPI
from fastapi.testclient import TestClient
import gateway.api as ga
from gateway.proxy.config import ProxyConfig, build_proxy_config
from gateway.proxy.ledger import Ledger
def test_proxy_disabled_by_default_404():
"""D-P7:默认 enabled=False -> 全局 app 不注册任何 /proxy 路由。"""
client = TestClient(ga.app)
assert client.get("/proxy/v1/models").status_code == 404
assert client.post("/proxy/v1/chat/completions", json={}).status_code == 404
assert client.get("/proxy/admin/stats").status_code == 404
def test_ddl_creates_tables_and_indexes(tmp_path):
"""四表 + 两索引幂等创建(WAL)。"""
led = Ledger.init_db(tmp_path / "proxy.sqlite3")
tables = set(led.table_names())
for t in ("students", "proxy_keys", "usage_ledger", "semcache"):
assert t in tables
idx = set(led.index_names())
assert "idx_semcache_bucket" in idx
assert "idx_ledger_ts" in idx
# 幂等重建
led2 = Ledger.init_db(tmp_path / "proxy.sqlite3")
assert "semcache" in set(led2.table_names())
def test_build_proxy_config_defaults_and_milli_conversion():
"""缺省值兜底 + 元/1M -> 毫元/1M 加载期换算(D-P1)。"""
from gateway.settings import DEFAULTS
cfg = build_proxy_config(DEFAULTS) # DEFAULTS 自带 deepseek-chat 价格示例
assert cfg.enabled is False
assert "default" in cfg.buckets
assert cfg.sale_in == 0.5 and cfg.sale_out == 0.8 # 差异化定价
# 自带 DEFAULTS 的 deepseek-chat 价格:3.0 元 -> 3000 毫元
p = cfg.price("deepseek-chat")
assert p is not None
assert p.in_miss == 3000
assert p.in_hit == 100 # 0.1 元 -> 100 毫元
assert p.out == 9000
# in_hit 缺省 = in_miss / 30D-P3
cfg2 = build_proxy_config({"proxy": {
"pricing": {"some-model": {"in_miss": 3.0, "out": 9.0}}}})
p2 = cfg2.price("some-model")
assert p2.in_hit == 100 # 3000 // 30
# 桶解析回落 default
assert cfg.bucket("ghost").name == "default"
def test_enabled_router_lists_pool_models(tmp_path, monkeypatch):
"""enabled=True 独立挂载:/proxy/v1/models 返回池内非 mock 模型(OpenAI 形状)。"""
import gateway.model_pool as mp
from gateway.model_pool import PoolStore
mp.reset_pool()
monkeypatch.setattr(mp, "_store", PoolStore(path=tmp_path / "pool.json"))
mp.get_pool().upsert({
"id": "up1", "name": "云端", "tier": "budget", "backend": "openai",
"base_url": "https://api.example.com", "model": "deepseek-chat",
"price_in": 0.1, "price_out": 0.1, "enabled": True})
mp.get_pool().upsert({
"id": "mk", "name": "模拟", "tier": "local", "backend": "mock",
"model": "mock", "enabled": True}) # mock 不应出现
cfg = build_proxy_config({"proxy": {"enabled": True,
"db_path": str(tmp_path / "p.sqlite3")}})
app = FastAPI()
app.include_router(__import__("gateway.proxy", fromlist=["x"]).build_proxy_router(
cfg, mp.get_pool()))
client = TestClient(app)
r = client.get("/proxy/v1/models")
assert r.status_code == 200
data = r.json()
assert data["object"] == "list"
ids = [m["id"] for m in data["data"]]
assert ids == ["deepseek-chat"]
assert data["data"][0]["owned_by"] == "campus-proxy"
mp.reset_pool()