Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions tests/integration/test_audit_primitive_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,7 @@ def no_read(*args: object, **kwargs: object) -> str:
assert second.findings_by_file["second"][0].file == "second"


@pytest.mark.lifecycle_smoke
@pytest.mark.parametrize("target", ["cursor", "windsurf"])
@pytest.mark.parametrize("nested", [False, True])
def test_preserved_nested_command_layout_is_visible_non_prompt(
Expand All @@ -702,14 +703,16 @@ def test_preserved_nested_command_layout_is_visible_non_prompt(
marker = project / "HOOK_MUST_NOT_RUN"
command = {"type": "command", "command": f"touch {marker}{_BIDI}"}
entry = {"matcher": "*", "hooks": [command]} if nested else command
payload = json.dumps({"version": 1, "hooks": {"PreToolUse": [entry]}})
event = "preToolUse" if target == "cursor" else "PreToolUse"
payload = json.dumps({"version": 1, "hooks": {event: [entry]}})
path.write_text(payload, encoding="utf-8")
result = scan_project_result(project, targets=(profile,))
if target == "cursor" and nested:
# Cursor hooks are native v1 flat handlers; nested layouts are reported, not scanned.
assert [entry.diagnostic for entry in result.incomplete] == [
"native hook event requires flat handlers"
assert [(entry.pointer, entry.diagnostic) for entry in result.incomplete] == [
("/hooks/preToolUse/0", "native hook event requires flat handlers")
]
assert [entry.status for entry in result.inventory] == ["incomplete"]
else:
assert result.incomplete == ()
assert [entry.status for entry in result.inventory] == ["not-applicable"]
Expand Down
22 changes: 4 additions & 18 deletions tests/integration/test_hook_root_source_drift_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,20 +127,7 @@ def _user_owned_entry(target: str) -> dict:
}


def _is_user_owned(entry: object) -> bool:
if not isinstance(entry, dict):
return False
if entry.get("command") == "echo user-owned":
return True
handlers = entry.get("hooks")
return (
isinstance(handlers, list)
and bool(handlers)
and isinstance(handlers[0], dict)
and handlers[0].get("command") == "echo user-owned"
)


@pytest.mark.lifecycle_smoke
@pytest.mark.parametrize("target, settings_rel, sidecar_rel, event_key", _HARNESS_CASES)
def test_root_hook_source_drift_heals_on_reinstall(
tmp_path: Path,
Expand Down Expand Up @@ -203,9 +190,8 @@ def test_root_hook_source_drift_heals_on_reinstall(
# Append a user-owned entry directly to the settings file (never in the sidecar).
settings_path = project / settings_rel
settings_data = json.loads(settings_path.read_text(encoding="utf-8"))
settings_data.setdefault("hooks", {}).setdefault(event_key, []).append(
_user_owned_entry(target)
)
user_entry = _user_owned_entry(target)
settings_data.setdefault("hooks", {}).setdefault(event_key, []).append(user_entry)
settings_path.write_text(json.dumps(settings_data), encoding="utf-8")

# Second install: must heal the stale marker without touching the user-owned entry.
Expand All @@ -219,7 +205,7 @@ def test_root_hook_source_drift_heals_on_reinstall(

settings_data = json.loads(settings_path.read_text(encoding="utf-8"))
entries = settings_data.get("hooks", {}).get(event_key, [])
user_owned = [e for e in entries if _is_user_owned(e)]
user_owned = [e for e in entries if e == user_entry]
assert len(user_owned) == 1, (
f"User-owned hook entry must survive healing for {target}; entries={entries}"
)
Expand Down
Loading