- 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
88 lines
3.5 KiB
Python
88 lines
3.5 KiB
Python
"""代理层骨架测试(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 / 30(D-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()
|