Files
cursor-flasher/tests/test_hook.py
cottongin 5b71b2275b Restructure config for per-mode style/sound and fix pulse dismiss
Major changes:
- Add StyleConfig dataclass with independent color, width, opacity,
  duration, pulse_speed, sound, and volume per mode (running/completed)
- Replace flat flash_*/sound_*/play_on config with running: and
  completed: YAML sections
- Replace CGEventTap (silently fails in forked daemon) with
  CGEventSourceSecondsSinceLastEventType polling for reliable
  input-based pulse dismissal when Cursor is already frontmost
- Update overlay, sound, and daemon to pass StyleConfig per call
- Rewrite tests for new config shape and dismiss mechanism

Made-with: Cursor
2026-03-10 07:01:52 -04:00

106 lines
2.9 KiB
Python

"""Tests for the hook notification script."""
import json
import os
import socket
import tempfile
import threading
import pytest
def _short_sock_path():
"""Create a short socket path that fits macOS's 104-char limit."""
fd, path = tempfile.mkstemp(suffix=".sock", dir="/tmp")
os.close(fd)
os.unlink(path)
return path
def _run_hook_main(stdin_data: str, socket_path: str):
"""Run the hook's main() with patched stdin and socket path."""
import io
import sys
from unittest.mock import patch
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "hooks"))
import notify
with patch.object(notify, "SOCKET_PATH", socket_path), \
patch("sys.stdin", io.StringIO(stdin_data)):
notify.main()
class TestHookNotify:
def test_sends_message_to_socket(self):
sock_path = _short_sock_path()
received = []
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
server.bind(sock_path)
server.listen(1)
def accept():
conn, _ = server.accept()
data = conn.recv(4096)
received.append(json.loads(data))
conn.close()
t = threading.Thread(target=accept)
t.start()
try:
hook_input = json.dumps({
"workspace_roots": ["/Users/me/project"],
"hook_event_name": "preToolUse",
"tool_name": "Shell",
})
_run_hook_main(hook_input, sock_path)
t.join(timeout=2)
finally:
server.close()
if os.path.exists(sock_path):
os.unlink(sock_path)
assert len(received) == 1
assert received[0]["workspace"] == "/Users/me/project"
assert received[0]["event"] == "preToolUse"
assert received[0]["tool"] == "Shell"
def test_handles_missing_socket_gracefully(self):
hook_input = json.dumps({
"workspace_roots": ["/Users/me/project"],
"hook_event_name": "stop",
})
_run_hook_main(hook_input, "/tmp/nonexistent.sock")
def test_handles_empty_workspace_roots(self):
sock_path = _short_sock_path()
received = []
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
server.bind(sock_path)
server.listen(1)
def accept():
conn, _ = server.accept()
data = conn.recv(4096)
received.append(json.loads(data))
conn.close()
t = threading.Thread(target=accept)
t.start()
try:
hook_input = json.dumps({
"workspace_roots": [],
"hook_event_name": "stop",
})
_run_hook_main(hook_input, sock_path)
t.join(timeout=2)
finally:
server.close()
if os.path.exists(sock_path):
os.unlink(sock_path)
assert received[0]["workspace"] == ""