mirror of
https://github.com/trustedsec/hate_crack.git
synced 2026-04-28 12:03:11 -07:00
feat(notify): add Notifications submenu dispatcher
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3881,6 +3881,31 @@ def rule_tools_submenu():
|
||||
return _attacks.rule_tools_submenu(_attack_ctx())
|
||||
|
||||
|
||||
def notifications_submenu():
|
||||
"""Submenu for all Pushover notification controls (main-menu option 82)."""
|
||||
from hate_crack.menu import interactive_menu
|
||||
|
||||
while True:
|
||||
settings = _notify.get_settings()
|
||||
global_label = "ON" if settings.enabled else "OFF"
|
||||
per_crack_label = "ON" if settings.per_crack_enabled else "OFF"
|
||||
items = [
|
||||
("1", f"Toggle Pushover Notifications [{global_label}]"),
|
||||
("2", f"Toggle Per-Crack Notifications [{per_crack_label}]"),
|
||||
("3", "Send Test Pushover Notification"),
|
||||
("99", "Back to Main Menu"),
|
||||
]
|
||||
choice = interactive_menu(items, title="\nNotifications:")
|
||||
if choice is None or choice == "99":
|
||||
break
|
||||
if choice == "1":
|
||||
toggle_notifications()
|
||||
elif choice == "2":
|
||||
toggle_per_crack_notifications()
|
||||
elif choice == "3":
|
||||
test_pushover_notification()
|
||||
|
||||
|
||||
# convert hex words for recycling
|
||||
def convert_hex(working_file):
|
||||
processed_words = []
|
||||
|
||||
108
tests/test_notifications_submenu.py
Normal file
108
tests/test_notifications_submenu.py
Normal file
@@ -0,0 +1,108 @@
|
||||
"""Unit tests for the Notifications submenu dispatcher (main-menu option 82).
|
||||
|
||||
Patching note: ``notifications_submenu`` is defined in ``hate_crack/main.py``
|
||||
and resolves ``toggle_notifications`` / ``toggle_per_crack_notifications`` /
|
||||
``test_pushover_notification`` against ``hate_crack.main``'s own globals.
|
||||
We therefore patch that module directly — patching the ``hate_crack.py``
|
||||
proxy would have no effect on the submenu's internal dispatch.
|
||||
"""
|
||||
import hate_crack.main as _main_mod
|
||||
import hate_crack.menu as _menu_mod
|
||||
from hate_crack.notify.settings import NotifySettings
|
||||
|
||||
|
||||
def _stub_action_handlers(monkeypatch, calls):
|
||||
monkeypatch.setattr(
|
||||
_main_mod, "toggle_notifications", lambda: calls.append("toggle")
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
_main_mod,
|
||||
"toggle_per_crack_notifications",
|
||||
lambda: calls.append("toggle_pc"),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
_main_mod,
|
||||
"test_pushover_notification",
|
||||
lambda: calls.append("test"),
|
||||
)
|
||||
|
||||
|
||||
def _queue_menu_choices(monkeypatch, choices):
|
||||
"""Queue ``choices`` as sequential return values from ``interactive_menu``.
|
||||
|
||||
Always appends a final ``"99"`` so the loop exits even if the caller
|
||||
forgot — this mirrors how the real user ends a submenu.
|
||||
"""
|
||||
iterator = iter(list(choices) + ["99"])
|
||||
|
||||
def _fake_menu(items, title=""):
|
||||
return next(iterator)
|
||||
|
||||
monkeypatch.setattr(_menu_mod, "interactive_menu", _fake_menu)
|
||||
|
||||
|
||||
class TestNotificationsSubmenu:
|
||||
def test_choice_1_dispatches_toggle_notifications(self, monkeypatch):
|
||||
calls = []
|
||||
_stub_action_handlers(monkeypatch, calls)
|
||||
_queue_menu_choices(monkeypatch, ["1"])
|
||||
_main_mod.notifications_submenu()
|
||||
assert calls == ["toggle"]
|
||||
|
||||
def test_choice_2_dispatches_toggle_per_crack(self, monkeypatch):
|
||||
calls = []
|
||||
_stub_action_handlers(monkeypatch, calls)
|
||||
_queue_menu_choices(monkeypatch, ["2"])
|
||||
_main_mod.notifications_submenu()
|
||||
assert calls == ["toggle_pc"]
|
||||
|
||||
def test_choice_3_dispatches_test_pushover(self, monkeypatch):
|
||||
calls = []
|
||||
_stub_action_handlers(monkeypatch, calls)
|
||||
_queue_menu_choices(monkeypatch, ["3"])
|
||||
_main_mod.notifications_submenu()
|
||||
assert calls == ["test"]
|
||||
|
||||
def test_choice_99_exits_without_dispatch(self, monkeypatch):
|
||||
calls = []
|
||||
_stub_action_handlers(monkeypatch, calls)
|
||||
|
||||
def _only_99(items, title=""):
|
||||
return "99"
|
||||
|
||||
monkeypatch.setattr(_menu_mod, "interactive_menu", _only_99)
|
||||
_main_mod.notifications_submenu()
|
||||
assert calls == []
|
||||
|
||||
def test_none_choice_exits_without_dispatch(self, monkeypatch):
|
||||
calls = []
|
||||
_stub_action_handlers(monkeypatch, calls)
|
||||
|
||||
def _returns_none(items, title=""):
|
||||
return None
|
||||
|
||||
monkeypatch.setattr(_menu_mod, "interactive_menu", _returns_none)
|
||||
_main_mod.notifications_submenu()
|
||||
assert calls == []
|
||||
|
||||
def test_submenu_labels_reflect_live_settings(self, monkeypatch):
|
||||
captured_items = {}
|
||||
|
||||
def _capture(items, title=""):
|
||||
captured_items["items"] = items
|
||||
captured_items["title"] = title
|
||||
return "99"
|
||||
|
||||
monkeypatch.setattr(_menu_mod, "interactive_menu", _capture)
|
||||
monkeypatch.setattr(
|
||||
_main_mod._notify,
|
||||
"get_settings",
|
||||
lambda: NotifySettings(enabled=True, per_crack_enabled=False),
|
||||
)
|
||||
_main_mod.notifications_submenu()
|
||||
labels = {k: v for k, v in captured_items["items"]}
|
||||
assert "ON" in labels["1"]
|
||||
assert "OFF" in labels["2"]
|
||||
assert labels["3"] == "Send Test Pushover Notification"
|
||||
assert labels["99"] == "Back to Main Menu"
|
||||
assert "Notifications" in captured_items["title"]
|
||||
Reference in New Issue
Block a user