mirror of
https://github.com/trustedsec/hate_crack.git
synced 2026-07-28 14:47:22 -07:00
Merge branch 'feat/test-pushover-menu' into feat/pushover-notifications
Adds menu option 84 (Send Test Pushover Notification) so users can verify their Pushover credentials without running an attack. Ignores the global notify_enabled toggle by design (prints a note when OFF).
This commit is contained in:
@@ -95,6 +95,7 @@ def get_main_menu_options():
|
||||
"80": _attacks.wordlist_tools_submenu,
|
||||
"81": _attacks.rule_tools_submenu,
|
||||
"83": toggle_notifications,
|
||||
"84": test_pushover_notification,
|
||||
"90": download_hashmob_rules,
|
||||
"91": weakpass_wordlist_menu,
|
||||
"92": download_hashmob_wordlists,
|
||||
|
||||
@@ -4108,6 +4108,39 @@ def toggle_notifications():
|
||||
)
|
||||
|
||||
|
||||
def test_pushover_notification():
|
||||
"""Send a canned test notification so the user can verify Pushover works.
|
||||
|
||||
Ignores the global ``notify_enabled`` toggle on purpose: the point of the
|
||||
test is to confirm the wire is live, independent of whether attacks are
|
||||
currently wired to notify. When the global toggle is OFF we still send
|
||||
but print a note so the user is not surprised later.
|
||||
"""
|
||||
settings = _notify.get_settings()
|
||||
token = settings.pushover_token
|
||||
user = settings.pushover_user
|
||||
if not token or not user:
|
||||
print(
|
||||
"\n[!] Pushover credentials missing. Set notify_pushover_token "
|
||||
"and notify_pushover_user in config.json."
|
||||
)
|
||||
return
|
||||
|
||||
if not settings.enabled:
|
||||
print("\n(notifications are globally OFF, but sending test anyway)")
|
||||
|
||||
title = "hate_crack: test notification"
|
||||
message = (
|
||||
"This is a test notification from hate_crack. "
|
||||
"If you see this, Pushover is wired up correctly."
|
||||
)
|
||||
ok = _notify._send_pushover(token, user, title, message)
|
||||
if ok:
|
||||
print("[+] Test Pushover notification sent. Check your device.")
|
||||
else:
|
||||
print("[!] Test Pushover notification failed. See log output for details.")
|
||||
|
||||
|
||||
def get_main_menu_items():
|
||||
"""Return ordered (key, label) pairs for the main menu."""
|
||||
items = [
|
||||
@@ -4136,6 +4169,7 @@ def get_main_menu_items():
|
||||
"83",
|
||||
f"Toggle Pushover Notifications [{'ON' if _notify.get_settings().enabled else 'OFF'}]",
|
||||
),
|
||||
("84", "Send Test Pushover Notification"),
|
||||
("90", "Download rules from Hashmob.net"),
|
||||
("91", "Analyze Hashcat Rules"),
|
||||
("92", "Download wordlists from Hashmob.net"),
|
||||
@@ -4180,6 +4214,7 @@ def get_main_menu_options():
|
||||
"80": wordlist_tools_submenu,
|
||||
"81": rule_tools_submenu,
|
||||
"83": toggle_notifications,
|
||||
"84": test_pushover_notification,
|
||||
"90": lambda: download_hashmob_rules(rules_dir=rulesDirectory),
|
||||
"91": analyze_rules,
|
||||
"92": download_hashmob_wordlists,
|
||||
|
||||
@@ -33,6 +33,7 @@ MENU_OPTION_TEST_CASES = [
|
||||
("80", CLI_MODULE._attacks, "wordlist_tools_submenu", "wordlist-tools"),
|
||||
("81", CLI_MODULE._attacks, "rule_tools_submenu", "rule-tools"),
|
||||
("83", CLI_MODULE, "toggle_notifications", "toggle-notifications"),
|
||||
("84", CLI_MODULE, "test_pushover_notification", "test-pushover"),
|
||||
("90", CLI_MODULE, "download_hashmob_rules", "hashmob-rules"),
|
||||
("91", CLI_MODULE, "weakpass_wordlist_menu", "weakpass-menu"),
|
||||
("92", CLI_MODULE, "download_hashmob_wordlists", "hashmob-wordlists"),
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
"""Unit tests for main.test_pushover_notification().
|
||||
|
||||
These tests patch ``hate_crack.main._notify`` directly rather than
|
||||
``hate_crack.notify._send_pushover``. The latter is fragile because
|
||||
``tests/test_random_rules_attack.py`` purges ``hate_crack.*`` from
|
||||
``sys.modules`` and re-imports, which leaves ``main._notify`` pointing
|
||||
at a different module object than the one a top-level ``patch`` would
|
||||
touch. Patching the attribute on ``main`` itself is robust to that.
|
||||
|
||||
We use ``patch.object(hc_main, "_notify")`` rather than
|
||||
``patch("hate_crack.main._notify")`` so the patch target is the exact
|
||||
module object whose function we invoke. A string target would re-resolve
|
||||
``hate_crack.main`` through ``sys.modules``, which — again thanks to the
|
||||
purge in ``test_random_rules_attack.py`` — can be a different object from
|
||||
the ``hc_main`` reference bound at test-module import time.
|
||||
"""
|
||||
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
|
||||
from hate_crack import main as hc_main
|
||||
|
||||
|
||||
def _settings(
|
||||
*, enabled: bool = True, token: str = "tok", user: str = "usr"
|
||||
) -> SimpleNamespace:
|
||||
"""Minimal stand-in for ``NotifySettings`` — we only read three fields."""
|
||||
return SimpleNamespace(enabled=enabled, pushover_token=token, pushover_user=user)
|
||||
|
||||
|
||||
class TestTestPushoverNotification:
|
||||
def test_success_prints_confirmation_and_sends(self, capsys):
|
||||
with patch.object(hc_main, "_notify") as mock_notify:
|
||||
mock_notify.get_settings.return_value = _settings(enabled=True)
|
||||
mock_notify._send_pushover.return_value = True
|
||||
hc_main.test_pushover_notification()
|
||||
mock_notify._send_pushover.assert_called_once()
|
||||
args = mock_notify._send_pushover.call_args.args
|
||||
assert args[0] == "tok"
|
||||
assert args[1] == "usr"
|
||||
assert args[2] == "hate_crack: test notification"
|
||||
assert "test notification from hate_crack" in args[3]
|
||||
out = capsys.readouterr().out
|
||||
assert "[+] Test Pushover notification sent" in out
|
||||
|
||||
def test_failure_prints_failure_line(self, capsys):
|
||||
with patch.object(hc_main, "_notify") as mock_notify:
|
||||
mock_notify.get_settings.return_value = _settings(enabled=True)
|
||||
mock_notify._send_pushover.return_value = False
|
||||
hc_main.test_pushover_notification()
|
||||
out = capsys.readouterr().out
|
||||
assert "[!] Test Pushover notification failed" in out
|
||||
|
||||
def test_missing_token_skips_send_and_warns(self, capsys):
|
||||
with patch.object(hc_main, "_notify") as mock_notify:
|
||||
mock_notify.get_settings.return_value = _settings(enabled=True, token="")
|
||||
hc_main.test_pushover_notification()
|
||||
mock_notify._send_pushover.assert_not_called()
|
||||
out = capsys.readouterr().out
|
||||
assert "[!] Pushover credentials missing" in out
|
||||
assert "notify_pushover_token" in out
|
||||
|
||||
def test_missing_user_skips_send_and_warns(self, capsys):
|
||||
with patch.object(hc_main, "_notify") as mock_notify:
|
||||
mock_notify.get_settings.return_value = _settings(enabled=True, user="")
|
||||
hc_main.test_pushover_notification()
|
||||
mock_notify._send_pushover.assert_not_called()
|
||||
out = capsys.readouterr().out
|
||||
assert "[!] Pushover credentials missing" in out
|
||||
|
||||
def test_globally_off_still_sends_with_note(self, capsys):
|
||||
with patch.object(hc_main, "_notify") as mock_notify:
|
||||
mock_notify.get_settings.return_value = _settings(enabled=False)
|
||||
mock_notify._send_pushover.return_value = True
|
||||
hc_main.test_pushover_notification()
|
||||
mock_notify._send_pushover.assert_called_once()
|
||||
out = capsys.readouterr().out
|
||||
assert "notifications are globally OFF" in out
|
||||
assert "[+] Test Pushover notification sent" in out
|
||||
Reference in New Issue
Block a user