feat(sense): T-G0 语义分析器骨架(包结构/DDL/灰度门控挂载)
- gateway/sense/:config(SenseConfig:mode 灰度三态/特征门/conformal/consumers 档位映射,mode 非法回落 collect)、errors(EmbedderDown/ArtifactMissing,D-G4 降级)、 store(tier_observations + sense_artifacts DDL,WAL;观察写入/标签回填/ labeled 查询/180d 清理/工件登记与 active 切换)、routes(/sense/health 透出灰度状态) - settings DEFAULTS 增 sense 段(enabled 默认 False,mode 默认 collect——D-G7) - api.py include_router 门控(装配失败不拖垮主应用) - 测试 +5:门控 404/DDL 幂等/配置缺省与 mode 回落/独立挂载 health/ 观察写入-回填-计数-清理链路,全量 375 passed
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
"""语义分析器骨架测试(T-G0):enabled 门控 / DDL / 独立挂载 / 配置构建。"""
|
||||
import pytest
|
||||
|
||||
pytest.importorskip("fastapi")
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
import gateway.api as ga
|
||||
from gateway.sense.config import build_sense_config
|
||||
from gateway.sense.store import SenseStore
|
||||
|
||||
|
||||
def test_sense_disabled_by_default_404():
|
||||
"""D-G7:默认 enabled=False -> 全局 app 不注册任何 /sense 路由。"""
|
||||
client = TestClient(ga.app)
|
||||
assert client.get("/sense/health").status_code == 404
|
||||
assert client.get("/sense/admin/agreement").status_code == 404
|
||||
|
||||
|
||||
def test_ddl_creates_tables_and_indexes(tmp_path):
|
||||
"""两表 + 两索引幂等创建。"""
|
||||
store = SenseStore.init_db(tmp_path / "sense.sqlite3")
|
||||
tables = set(store.table_names())
|
||||
assert {"tier_observations", "sense_artifacts"} <= tables
|
||||
idx = set(store.index_names())
|
||||
assert {"idx_obs_ts", "idx_obs_policy"} <= idx
|
||||
SenseStore.init_db(tmp_path / "sense.sqlite3") # 幂等
|
||||
|
||||
|
||||
def test_build_sense_config_defaults():
|
||||
"""缺省值 + mode 非法回落 collect(D-G7)。"""
|
||||
cfg = build_sense_config({})
|
||||
assert cfg.enabled is False
|
||||
assert cfg.mode == "collect"
|
||||
assert cfg.embedder.dim == 1024
|
||||
assert cfg.min_labels == 500
|
||||
assert cfg.tier_pool_hint("proxy", "t1") == "local-small"
|
||||
assert cfg.tier_pool_hint("proxy", "t3") == "premium"
|
||||
assert cfg.tier_pool_hint("client", "t2") == "budget" # 无配置兜底
|
||||
bad = build_sense_config({"sense": {"mode": "turbo", "enabled": True}})
|
||||
assert bad.mode == "collect"
|
||||
|
||||
|
||||
def test_enabled_router_health(tmp_path):
|
||||
"""enabled=True 独立挂载:/sense/health 透出灰度状态。"""
|
||||
cfg = build_sense_config({"sense": {"enabled": True, "mode": "collect",
|
||||
"db_path": str(tmp_path / "s.sqlite3")}})
|
||||
app = FastAPI()
|
||||
app.include_router(
|
||||
__import__("gateway.sense", fromlist=["x"]).build_sense_router(cfg))
|
||||
client = TestClient(app)
|
||||
r = client.get("/sense/health")
|
||||
assert r.status_code == 200
|
||||
data = r.json()
|
||||
assert data["enabled"] is True and data["mode"] == "collect"
|
||||
|
||||
|
||||
def test_observation_roundtrip(tmp_path):
|
||||
"""观察写入/标签回填/计数/留存清理(store 基础链路)。"""
|
||||
store = SenseStore.init_db(tmp_path / "s.sqlite3")
|
||||
store.insert_observation({
|
||||
"request_id": "r1", "consumer": "pipeline", "decided_tier": "T2",
|
||||
"executed_tier": "T2", "probs": '{"t1":0.2,"t2":0.6,"t3":0.2}',
|
||||
"policy_version": "v0-rule", "features": '{"turns":1}'})
|
||||
store.insert_observation({
|
||||
"request_id": "r2", "consumer": "proxy", "decided_tier": "T1",
|
||||
"executed_tier": "T1", "probs": "{}", "policy_version": "v0-rule",
|
||||
"features": "{}", "outcome": "ok"})
|
||||
assert store.count_labeled() == 0
|
||||
store.set_true_tier("r1", "T2")
|
||||
assert store.count_labeled() == 1
|
||||
rows = store.labeled_rows()
|
||||
assert rows[0]["request_id"] == "r1" and rows[0]["true_tier"] == "T2"
|
||||
assert store.update_outcome("r2", "escalated", executed_tier="T2") is True
|
||||
# 180d 清理
|
||||
import time
|
||||
assert store.purge_older_than(time.time() + 10) >= 2
|
||||
assert store.count_labeled() == 0
|
||||
Reference in New Issue
Block a user