aps-agent/tests/golden/test_feature_flags.py

124 lines
5.2 KiB
Python
Raw Normal View History

# ============================================================
# 功能开关(Feature Flags)黄金测试
# 覆盖:默认全开、文件配置生效、损坏/非法回退默认 + 显式 error、
# 未知键如实上报、非 bool 值该键回退、env 路径覆盖、/api/features 端点契约。
# ============================================================
from __future__ import annotations
import json
from pathlib import Path
import pytest
from fastapi.testclient import TestClient
from server.agent_core.feature_flags import FEATURE_CATALOG, load_feature_flags
from tests.auth_provider import install_test_auth
def _write(path: Path, data: object) -> str:
path.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")
return str(path)
def test_missing_file_defaults_all_enabled(tmp_path: Path):
"""文件缺失 → 全部默认开启,source=default,无 error。"""
result = load_feature_flags(str(tmp_path / "features.json"))
assert result["source"] == "default"
assert result["error"] is None
assert result["unknown"] == []
assert all(info["enabled"] for info in result["features"].values())
assert set(result["features"]) == set(FEATURE_CATALOG)
def test_file_config_disables_features(tmp_path: Path):
"""显式 false 隐藏功能;未列出的键保持默认开启;未知键如实上报。"""
path = _write(tmp_path / "features.json", {
"version": 1,
"features": {"drawing": False, "mesh": False, "unknown-thing": True},
})
result = load_feature_flags(path)
assert result["source"] == "file"
assert result["error"] is None
assert result["features"]["drawing"]["enabled"] is False
assert result["features"]["mesh"]["enabled"] is False
assert result["features"]["orders"]["enabled"] is True
assert result["unknown"] == ["unknown-thing"]
def test_corrupt_json_fails_open_with_error(tmp_path: Path):
"""损坏 JSON → fail-open 全部开启 + 显式 error(不锁死界面,不静默吞错)。"""
path = tmp_path / "features.json"
path.write_text("{not-json", encoding="utf-8")
result = load_feature_flags(str(path))
assert result["source"] == "default"
assert result["error"] is not None
assert all(info["enabled"] for info in result["features"].values())
def test_invalid_shape_and_non_bool_values(tmp_path: Path):
"""顶层结构非法 / features 非对象 / 值非 bool → 相应回退默认并显式报告。"""
bad_version = load_feature_flags(_write(tmp_path / "v.json", {"version": 2, "features": {}}))
assert bad_version["source"] == "default" and bad_version["error"] is not None
bad_section = load_feature_flags(_write(tmp_path / "s.json", {"version": 1, "features": [1]}))
assert bad_section["source"] == "default" and bad_section["error"] is not None
non_bool = load_feature_flags(_write(tmp_path / "b.json", {
"version": 1, "features": {"orders": "no", "drawing": False},
}))
assert non_bool["source"] == "file"
assert non_bool["features"]["orders"]["enabled"] is True # 非 bool 回退默认开启
assert non_bool["features"]["drawing"]["enabled"] is False # 合法 false 仍生效
assert non_bool["error"] is not None and "orders" in non_bool["error"]
def test_env_path_override(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
"""APS_FEATURES_PATH 覆盖默认路径。"""
path = _write(tmp_path / "custom.json", {"version": 1, "features": {"gov": False}})
monkeypatch.setenv("APS_FEATURES_PATH", path)
result = load_feature_flags()
assert result["source"] == "file"
assert result["path"] == path
assert result["features"]["gov"]["enabled"] is False
@pytest.fixture()
def features_app(tmp_path, monkeypatch):
monkeypatch.setenv("APS_DB_PATH", str(tmp_path / "features.db"))
monkeypatch.setenv("APS_WORLD_PATH", str(tmp_path / "world.json"))
from server.db.database import reset_engine
from server.state import store as world_store
install_test_auth(monkeypatch, "tenant-a-000000000000000000000000001")
world_store._stores.clear()
reset_engine()
from server.gateway.app import create_app
app = create_app()
yield app, monkeypatch, tmp_path
reset_engine()
world_store._stores.clear()
def test_features_endpoint_contract(features_app):
"""/api/features 端点:默认全开契约 + 配置文件生效 + 中文标签齐全。"""
app, monkeypatch, tmp_path = features_app
client = TestClient(app)
assert client.post("/api/auth/login", json={"username": "planner"}).status_code == 200
body = client.get("/api/features").json()
assert body["version"] == 1
assert body["source"] == "default"
assert all(info["enabled"] for info in body["features"].values())
assert all(info["label"] for info in body["features"].values())
config = _write(tmp_path / "features.json", {
"version": 1, "features": {"drawing": False, "regen": False},
})
monkeypatch.setenv("APS_FEATURES_PATH", config)
body = client.get("/api/features").json()
assert body["source"] == "file"
assert body["features"]["drawing"]["enabled"] is False
assert body["features"]["regen"]["enabled"] is False
assert body["features"]["orders"]["enabled"] is True