aps-agent/tests/golden/test_explore_boundary.py

97 lines
3.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ============================================================
# 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"], "主干写能力必须被切断"