aps-agent/tests/golden/test_explore_boundary.py

97 lines
3.6 KiB
Python
Raw Normal View History

# ============================================================
# Explore 与 Runtime 数据边界黄金测试(plan.md §5.1 / 矩阵 55 行)
# 覆盖:主干只读视图写保护(fail closed)、统一沙盒通道隔离、
# Explore 工具无法取得主干写能力(越权与污染测试)。
# ============================================================
from __future__ import annotations
import json
from server.aps_domain.explore_boundary import readonly_view, run_explore
from server.state.seed import seed_world
def _world():
w = seed_world()
w["salesOrders"].append({"id": "so-1", "orderNo": "SO-1"})
return w
def test_readonly_view_blocks_top_level_write():
"""主干只读视图:尝试写顶层键必须抛 PermissionError(fail closed)。"""
world = _world()
view = readonly_view(world)
for op in (
lambda v: v.__setitem__("salesOrders", []),
lambda v: v.__delitem__("salesOrders"),
lambda v: v.setdefault("x", 1),
lambda v: v.update({"y": 2}),
lambda v: v.clear(),
):
try:
op(view)
raise AssertionError("应抛 PermissionError")
except PermissionError:
pass
# 主干未被污染
assert world["salesOrders"], "只读视图不应能改动主干"
def test_readonly_view_blocks_nested_write():
"""嵌套容器:列表 append / dict set 同样被拦截(递归只读视图)。"""
world = _world()
view = readonly_view(world)
orders = view["salesOrders"] # 应返回只读列表
n_before = len(world["salesOrders"])
try:
orders.append({"id": "hack"})
raise AssertionError("嵌套写应抛 PermissionError")
except PermissionError:
pass
assert len(world["salesOrders"]) == n_before, "嵌套写不应影响主干"
def test_run_explore_sandbox_isolates_world():
"""统一通道:fn 拿到的沙盒可写,但主干完全不改变(深拷贝隔离)。"""
world = _world()
before = json.dumps(world, ensure_ascii=False, sort_keys=True)
before_count = len(world["salesOrders"])
def explorer(sandbox):
sandbox["salesOrders"].append({"id": "sandbox-only"})
sandbox["newKey"] = "explore"
return len(sandbox["salesOrders"])
result = run_explore(world, explorer)
assert result == len(world["salesOrders"]) + 1 # 沙盒内多 1 条
assert len(world["salesOrders"]) == before_count # 主干不变
assert "newKey" not in world
assert json.dumps(world, ensure_ascii=False, sort_keys=True) == before
def test_run_explore_nested_mutation_isolated():
"""嵌套变异(列表内 dict 修改)在沙盒内生效、主干不变。"""
world = _world()
def explorer(sandbox):
sandbox["salesOrders"][0]["orderNo"] = "CHANGED"
sandbox["salesOrders"][0]["extra"] = True
return sandbox["salesOrders"][0]["orderNo"]
result = run_explore(world, explorer)
assert result == "CHANGED"
assert any(o["orderNo"] == "SO-1" for o in world["salesOrders"]), "主干 orderNo 不变"
assert all("extra" not in o for o in world["salesOrders"]), "主干无额外字段"
def test_explore_tool_cannot_gain_main_write():
"""越权测试:Explore 工具即使拿到 view 也无法绕过只读边界写主干。"""
world = _world()
view = readonly_view(world)
try:
view["salesOrders"] = [] # 工具尝试直接替换主干表
raise AssertionError("应抛 PermissionError")
except PermissionError:
pass
assert world["salesOrders"], "主干写能力必须被切断"