8Regression tests for :mod:`oct.mcp.policy` — ``TOOL_MANIFEST``,
9``ToolSpec``, ``McpPolicy``, and ``PolicyDecision``.
13- Verify all profile behaviours (default, strict, airgapped).
14- Verify safe_mode enforcement and unknown tool rejection.
15- Verify manifest completeness for registered tools.
22 L3 — assertion details
27- Tests are in-memory policy checks; no subprocess or filesystem writes.
30from __future__
import annotations
47 "oct_lint",
"oct_health",
"oct_skeleton",
"oct_deps",
48 "oct_test",
"oct_typecheck",
"oct_diag",
49 "oct_git_status",
"oct_git_check",
53 "oct_format",
"oct_docs",
"oct_export_source",
"oct_new",
54 "oct_scaffold",
"oct_clean",
"oct_install_hooks",
55 "oct_git_commit",
"oct_git_hooks_install",
"oct_git_init",
62 assert len(TOOL_MANIFEST) == 20
65 assert _PHASE_5A_TOOLS.issubset(set(TOOL_MANIFEST.keys()))
68 assert _PHASE_5B_TOOLS.issubset(set(TOOL_MANIFEST.keys()))
71 for name
in _PHASE_5A_TOOLS:
72 spec = TOOL_MANIFEST[name]
73 assert spec.destructive
is False, f
"{name} should not be destructive"
76 for name
in _PHASE_5B_TOOLS:
77 spec = TOOL_MANIFEST[name]
78 assert spec.destructive
is True, f
"{name} should be destructive"
81 for name
in _PHASE_5A_TOOLS:
82 spec = TOOL_MANIFEST[name]
83 assert spec.auto_approve
is True, f
"{name} should be auto_approve"
86 for name
in _PHASE_5B_TOOLS:
87 spec = TOOL_MANIFEST[name]
88 assert spec.auto_approve
is False, f
"{name} should not be auto_approve"
91 for name
in _PHASE_5A_TOOLS:
92 spec = TOOL_MANIFEST[name]
93 assert "read" in spec.permissions, f
"{name} must have read permission"
96 for name
in _PHASE_5B_TOOLS:
97 spec = TOOL_MANIFEST[name]
98 assert "write" in spec.permissions, f
"{name} must have write permission"
101 for name, spec
in TOOL_MANIFEST.items():
102 assert spec.cli, f
"{name} must have a cli subcommand"
111 spec =
ToolSpec(cli=
"lint", permissions={
"read"})
112 assert isinstance(spec.permissions, frozenset)
115 spec =
ToolSpec(cli=
"lint", permissions=frozenset({
"read"}))
116 with pytest.raises((AttributeError, TypeError)):
127 assert policy.profile ==
"default"
131 assert policy.profile ==
"strict"
135 assert policy.profile ==
"airgapped"
138 with pytest.raises(ValueError, match=
"Invalid MCP profile"):
149 for tool
in TOOL_MANIFEST:
150 decision = policy.check(tool)
151 assert decision.allowed, f
"{tool} should be allowed in default profile"
155 decision = policy.check(
"evil_tool")
156 assert decision.allowed
is False
157 assert decision.decision ==
"denied"
158 assert decision.policy_rule ==
"unknown_tool"
162 decision = policy.check(
"oct_lint")
163 assert decision.reason !=
""
173 for tool
in TOOL_MANIFEST:
174 decision = policy.check(tool)
175 assert decision.allowed
is False
176 assert decision.policy_rule ==
"safe_mode"
180 decision = policy.check(
"oct_lint")
181 assert "safe mode" in decision.reason.lower()
191 for tool
in TOOL_MANIFEST:
192 decision = policy.check(tool)
193 assert decision.allowed
is False
194 assert decision.policy_rule ==
"airgapped_profile"
198 decision = policy.check(
"unknown_tool")
199 assert decision.allowed
is False
203 decision = policy.check(
"oct_lint")
204 assert "airgapped" in decision.reason.lower()
214 for tool
in TOOL_MANIFEST:
215 spec = TOOL_MANIFEST[tool]
216 decision = policy.check(tool)
217 if not spec.destructive:
218 assert decision.allowed, f
"{tool} should be allowed in strict (non-destructive)"
221 """Phase 5B write tools (destructive=True) are all denied in strict profile."""
223 for tool
in _PHASE_5B_TOOLS:
224 decision = policy.check(tool)
225 assert decision.allowed
is False, f
"{tool} should be denied in strict"
226 assert "destructive" in decision.policy_rule
235 d =
PolicyDecision(allowed=
True, decision=
"allowed", policy_rule=
"x", reason=
"ok")
236 assert d.allowed
is True
239 d =
PolicyDecision(allowed=
False, decision=
"denied", policy_rule=
"safe_mode", reason=
"blocked")
240 assert d.allowed
is False
test_airgapped_unknown_tool_also_denied(self)
test_airgapped_denies_all_tools(self)
test_airgapped_reason_mentions_profile(self)
test_airgapped_profile(self)
test_default_profile(self)
test_strict_profile(self)
test_invalid_profile_raises(self)
test_unknown_tool_denied(self)
test_decision_has_reason(self)
test_all_manifest_tools_allowed(self)
test_safe_mode_denies_all_tools(self)
test_safe_mode_reason_mentions_safe_mode(self)
test_strict_denies_all_write_tools(self)
test_strict_allows_non_destructive_tools(self)
test_allowed_true_means_proceed(self)
test_allowed_false_means_blocked(self)