Files
FESADev/tests/test_hook_validation_scope.py
T
2026-09-05 15:14:21 +09:00

143 lines
4.1 KiB
Python

import io
import json
from scripts.hooks import pre_tool_use, stop_validation
def edit_payload(root, path, *, session_id="session-1", turn_id="turn-1"):
return {
"session_id": session_id,
"turn_id": turn_id,
"cwd": str(root),
"tool_name": "apply_patch",
"tool_input": {
"patch": (
"*** Begin Patch\n"
f"*** Update File: {path}\n"
"@@\n"
"-old\n"
"+new\n"
"*** End Patch\n"
)
},
}
def stop_payload(root, *, session_id="session-1", turn_id="turn-1"):
return {
"session_id": session_id,
"turn_id": turn_id,
"cwd": str(root),
"hook_event_name": "Stop",
"stop_hook_active": False,
}
def run_pre_tool_use(monkeypatch, payload):
monkeypatch.setattr(
pre_tool_use.sys,
"stdin",
io.StringIO(json.dumps(payload)),
)
assert pre_tool_use.main() == 0
def run_stop(monkeypatch, payload, root, validation_calls):
monkeypatch.setattr(
stop_validation.sys,
"stdin",
io.StringIO(json.dumps(payload)),
)
monkeypatch.setattr(
stop_validation,
"_project_root",
lambda *args, **kwargs: root,
)
def capture_validation(*args, **kwargs):
validation_calls.append(root)
return True, ""
monkeypatch.setattr(stop_validation, "run_validations", capture_validation)
assert stop_validation.main() == 0
def test_stop_skips_validation_after_markdown_only_edit(tmp_path, monkeypatch):
run_pre_tool_use(monkeypatch, edit_payload(tmp_path, "docs/guide.md"))
validation_calls = []
run_stop(monkeypatch, stop_payload(tmp_path), tmp_path, validation_calls)
assert validation_calls == []
def test_stop_validates_after_non_document_edit(tmp_path, monkeypatch):
run_pre_tool_use(monkeypatch, edit_payload(tmp_path, "scripts/tool.py"))
validation_calls = []
run_stop(monkeypatch, stop_payload(tmp_path), tmp_path, validation_calls)
assert validation_calls == [tmp_path]
def test_stop_validates_after_mixed_document_and_code_edits(tmp_path, monkeypatch):
run_pre_tool_use(monkeypatch, edit_payload(tmp_path, "scripts/tool.py"))
run_pre_tool_use(monkeypatch, edit_payload(tmp_path, "docs/guide.md"))
validation_calls = []
run_stop(monkeypatch, stop_payload(tmp_path), tmp_path, validation_calls)
assert validation_calls == [tmp_path]
def test_stop_validates_when_turn_scope_is_missing(tmp_path, monkeypatch):
validation_calls = []
run_stop(monkeypatch, stop_payload(tmp_path), tmp_path, validation_calls)
assert validation_calls == [tmp_path]
def test_document_scope_is_isolated_by_turn(tmp_path, monkeypatch):
run_pre_tool_use(
monkeypatch,
edit_payload(tmp_path, "docs/guide.md", turn_id="document-turn"),
)
validation_calls = []
run_stop(
monkeypatch,
stop_payload(tmp_path, turn_id="different-turn"),
tmp_path,
validation_calls,
)
assert validation_calls == [tmp_path]
def test_stop_validates_when_turn_scope_is_corrupt(tmp_path, monkeypatch):
run_pre_tool_use(monkeypatch, edit_payload(tmp_path, "docs/guide.md"))
state_files = list((tmp_path / ".harness" / "hook-state").glob("*.json"))
assert len(state_files) == 1
state_files[0].write_text("not json", encoding="utf-8")
validation_calls = []
run_stop(monkeypatch, stop_payload(tmp_path), tmp_path, validation_calls)
assert validation_calls == [tmp_path]
def test_later_document_edit_does_not_repair_malformed_scope(
tmp_path, monkeypatch
):
payload = edit_payload(tmp_path, "docs/guide.md")
run_pre_tool_use(monkeypatch, payload)
state_files = list((tmp_path / ".harness" / "hook-state").glob("*.json"))
assert len(state_files) == 1
state_files[0].write_text('{"version": 1}', encoding="utf-8")
run_pre_tool_use(monkeypatch, payload)
validation_calls = []
run_stop(monkeypatch, stop_payload(tmp_path), tmp_path, validation_calls)
assert validation_calls == [tmp_path]