"""代理层骨架测试(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()