mirror of
https://github.com/trustedsec/hate_crack.git
synced 2026-04-28 12:03:11 -07:00
feat(notify): add per-crack UI toggle with global-OFF guard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4108,6 +4108,29 @@ def toggle_notifications():
|
||||
)
|
||||
|
||||
|
||||
def toggle_per_crack_notifications():
|
||||
"""Runtime toggle for ``notify_per_crack_enabled`` with a UI-level guard.
|
||||
|
||||
Per-crack notifications require global notifications to be ON in order
|
||||
to fire (see ``notify.start_tailer``). Turning per-crack ON while the
|
||||
global switch is OFF is silently ineffective, which surprises users —
|
||||
so we refuse the transition and point them at the global toggle.
|
||||
|
||||
Turning per-crack OFF is always allowed, regardless of the global
|
||||
state, so users can clean up an inconsistent config without friction.
|
||||
"""
|
||||
settings = _notify.get_settings()
|
||||
if not settings.per_crack_enabled and not settings.enabled:
|
||||
print(
|
||||
"\n[!] Global Pushover notifications are OFF. Enable option 1 "
|
||||
"(Toggle Pushover Notifications) first."
|
||||
)
|
||||
return
|
||||
new_state = _notify.toggle_per_crack_enabled()
|
||||
label = "ON" if new_state else "OFF"
|
||||
print(f"\nPer-crack notifications are now {label}.")
|
||||
|
||||
|
||||
def test_pushover_notification():
|
||||
"""Send a canned test notification so the user can verify Pushover works.
|
||||
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
"""Unit tests for the toggle_per_crack_enabled runtime toggle."""
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from hate_crack import notify as _notify
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
||||
_CLI_SPEC = importlib.util.spec_from_file_location(
|
||||
"hate_crack_cli_percrack", PROJECT_ROOT / "hate_crack.py"
|
||||
)
|
||||
CLI_MODULE = importlib.util.module_from_spec(_CLI_SPEC)
|
||||
_CLI_SPEC.loader.exec_module(CLI_MODULE)
|
||||
|
||||
|
||||
def _init_with(tmp_path: Path, **overrides) -> Path:
|
||||
"""Seed a config file with defaults + overrides and init the notify module."""
|
||||
@@ -65,3 +73,64 @@ class TestTogglePerCrackEnabled:
|
||||
assert data["notify_per_crack_enabled"] is True
|
||||
finally:
|
||||
_notify.clear_state_for_tests()
|
||||
|
||||
|
||||
class TestTogglePerCrackNotificationsUI:
|
||||
def _seed_settings(self, monkeypatch, *, enabled: bool, per_crack: bool):
|
||||
from hate_crack.notify.settings import NotifySettings
|
||||
|
||||
settings = NotifySettings(enabled=enabled, per_crack_enabled=per_crack)
|
||||
monkeypatch.setattr(
|
||||
CLI_MODULE._notify, "get_settings", lambda: settings
|
||||
)
|
||||
return settings
|
||||
|
||||
def test_guard_refuses_on_when_global_off(self, monkeypatch, capsys):
|
||||
self._seed_settings(monkeypatch, enabled=False, per_crack=False)
|
||||
called = {"n": 0}
|
||||
|
||||
def _fake_toggle() -> bool:
|
||||
called["n"] += 1
|
||||
return True
|
||||
|
||||
monkeypatch.setattr(
|
||||
CLI_MODULE._notify, "toggle_per_crack_enabled", _fake_toggle
|
||||
)
|
||||
CLI_MODULE.toggle_per_crack_notifications()
|
||||
captured = capsys.readouterr().out
|
||||
assert "Global Pushover notifications are OFF" in captured
|
||||
assert called["n"] == 0
|
||||
|
||||
def test_flips_on_when_global_on(self, monkeypatch, capsys):
|
||||
self._seed_settings(monkeypatch, enabled=True, per_crack=False)
|
||||
called = {"n": 0}
|
||||
|
||||
def _fake_toggle() -> bool:
|
||||
called["n"] += 1
|
||||
return True
|
||||
|
||||
monkeypatch.setattr(
|
||||
CLI_MODULE._notify, "toggle_per_crack_enabled", _fake_toggle
|
||||
)
|
||||
CLI_MODULE.toggle_per_crack_notifications()
|
||||
captured = capsys.readouterr().out
|
||||
assert "Per-crack notifications are now ON" in captured
|
||||
assert called["n"] == 1
|
||||
|
||||
def test_off_to_off_is_allowed_even_if_global_off(self, monkeypatch, capsys):
|
||||
# Turning OFF must always succeed, even with global OFF, so a user
|
||||
# can clean up an inconsistent (per_crack=True, enabled=False) config.
|
||||
self._seed_settings(monkeypatch, enabled=False, per_crack=True)
|
||||
called = {"n": 0}
|
||||
|
||||
def _fake_toggle() -> bool:
|
||||
called["n"] += 1
|
||||
return False
|
||||
|
||||
monkeypatch.setattr(
|
||||
CLI_MODULE._notify, "toggle_per_crack_enabled", _fake_toggle
|
||||
)
|
||||
CLI_MODULE.toggle_per_crack_notifications()
|
||||
captured = capsys.readouterr().out
|
||||
assert "Per-crack notifications are now OFF" in captured
|
||||
assert called["n"] == 1
|
||||
|
||||
Reference in New Issue
Block a user