mirror of
https://github.com/trustedsec/hate_crack.git
synced 2026-07-01 10:35:08 -07:00
Merge pull request #95 from trustedsec/feat/rule-tools
feat: add rule file management submenu (#93)
This commit is contained in:
@@ -643,6 +643,8 @@ All tests use mocked API calls, so they can run without connectivity to a Hashvi
|
||||
|
||||
(80) Wordlist Tools
|
||||
|
||||
(81) Rule File Tools
|
||||
|
||||
(90) Download rules from Hashmob.net
|
||||
(91) Analyze Hashcat Rules
|
||||
(92) Download wordlists from Hashmob.net
|
||||
@@ -855,6 +857,15 @@ A submenu of wordlist preprocessing utilities using hashcat-utils binaries. All
|
||||
|
||||
All binaries are in `hate_crack/hashcat-utils/bin/`.
|
||||
|
||||
#### Rule File Tools (option 81)
|
||||
Preprocesses hashcat rule files using `cleanup-rules.bin` and `rules_optimize.bin` from hashcat-utils.
|
||||
|
||||
* **Clean** - removes invalid syntax and duplicate rules using `cleanup-rules.bin`. Useful after combining rule files or downloading rules from external sources.
|
||||
* **Optimize** - consolidates redundant operations using `rules_optimize.bin`. Reduces rule file size and improves cracking speed.
|
||||
* **Clean and optimize** - runs both operations in sequence via a temporary file, then writes the final result.
|
||||
|
||||
All three operations read from an input file and write to a separate output file (original is never modified).
|
||||
|
||||
#### Download Rules from Hashmob.net
|
||||
Downloads the latest rule files from Hashmob.net's rule repository. These rules are curated and optimized for password cracking and can be used with the Quick Crack and Loopback Attack modes.
|
||||
|
||||
|
||||
@@ -93,6 +93,7 @@ def get_main_menu_options():
|
||||
"21": _attacks.generate_rules_crack,
|
||||
"22": _attacks.combipow_crack,
|
||||
"80": _attacks.wordlist_tools_submenu,
|
||||
"81": _attacks.rule_tools_submenu,
|
||||
"90": download_hashmob_rules,
|
||||
"91": weakpass_wordlist_menu,
|
||||
"92": download_hashmob_wordlists,
|
||||
|
||||
@@ -837,6 +837,118 @@ def combinator_submenu(ctx: Any) -> None:
|
||||
thorough_combinator(ctx)
|
||||
|
||||
|
||||
def _rule_select_file(ctx: Any, prompt: str = "Rule file: ") -> str:
|
||||
"""Prompt for a rule file path with tab-autocomplete."""
|
||||
import glob as _glob
|
||||
|
||||
def rule_completer(text: str, state: int) -> str | None:
|
||||
base = ctx.rulesDirectory
|
||||
if not text:
|
||||
pattern = os.path.join(base, "*.rule")
|
||||
else:
|
||||
text = os.path.expanduser(text)
|
||||
if text.startswith(("/", "./", "../", "~")):
|
||||
pattern = text + "*"
|
||||
else:
|
||||
pattern = os.path.join(base, text + "*")
|
||||
matches = _glob.glob(pattern)
|
||||
try:
|
||||
return matches[state]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
_configure_readline(rule_completer)
|
||||
return input(prompt).strip()
|
||||
|
||||
|
||||
def rule_cleanup_handler(ctx: Any) -> None:
|
||||
"""Clean a rule file using cleanup-rules.bin."""
|
||||
print("\nClean rule file - removes invalid and duplicate rules.")
|
||||
print("Reads an input rule file and writes cleaned rules to an output file.\n")
|
||||
infile = _rule_select_file(ctx, "Input rule file (tab to autocomplete): ")
|
||||
if not infile or not os.path.isfile(infile):
|
||||
print(f"[!] File not found: {infile}")
|
||||
return
|
||||
outfile = input("Output file path: ").strip()
|
||||
if not outfile:
|
||||
print("[!] Output path required.")
|
||||
return
|
||||
print(f"\nCleaning {infile} -> {outfile}")
|
||||
if ctx.rules_cleanup(infile, outfile):
|
||||
print("[+] Done.")
|
||||
else:
|
||||
print("[!] Cleanup failed.")
|
||||
|
||||
|
||||
def rule_optimize_handler(ctx: Any) -> None:
|
||||
"""Optimize a rule file using rules_optimize.bin."""
|
||||
print("\nOptimize rule file - consolidates redundant operations.")
|
||||
infile = _rule_select_file(ctx, "Input rule file: ")
|
||||
if not infile or not os.path.isfile(infile):
|
||||
print(f"[!] File not found: {infile}")
|
||||
return
|
||||
outfile = input("Output file path: ").strip()
|
||||
if not outfile:
|
||||
print("[!] Output path required.")
|
||||
return
|
||||
print(f"\nOptimizing {infile} -> {outfile}")
|
||||
if ctx.rules_optimize(infile, outfile):
|
||||
print("[+] Done.")
|
||||
else:
|
||||
print("[!] Optimize failed.")
|
||||
|
||||
|
||||
def rule_cleanup_and_optimize_handler(ctx: Any) -> None:
|
||||
"""Clean then optimize a rule file."""
|
||||
import tempfile
|
||||
|
||||
print("\nClean and optimize rule file (both operations in sequence).")
|
||||
infile = _rule_select_file(ctx, "Input rule file: ")
|
||||
if not infile or not os.path.isfile(infile):
|
||||
print(f"[!] File not found: {infile}")
|
||||
return
|
||||
outfile = input("Output file path: ").strip()
|
||||
if not outfile:
|
||||
print("[!] Output path required.")
|
||||
return
|
||||
with tempfile.NamedTemporaryFile(suffix=".rule", delete=False) as tmp:
|
||||
tmp_path = tmp.name
|
||||
try:
|
||||
print(f"\nStep 1/2: Cleaning {infile}...")
|
||||
if not ctx.rules_cleanup(infile, tmp_path):
|
||||
print("[!] Cleanup failed.")
|
||||
return
|
||||
print(f"Step 2/2: Optimizing -> {outfile}...")
|
||||
if ctx.rules_optimize(tmp_path, outfile):
|
||||
print("[+] Done.")
|
||||
else:
|
||||
print("[!] Optimize failed.")
|
||||
finally:
|
||||
if os.path.exists(tmp_path):
|
||||
os.unlink(tmp_path)
|
||||
|
||||
|
||||
def rule_tools_submenu(ctx: Any) -> None:
|
||||
from hate_crack.menu import interactive_menu
|
||||
|
||||
items = [
|
||||
("1", "Clean rule file (remove invalid/duplicate rules)"),
|
||||
("2", "Optimize rule file (consolidate redundant operations)"),
|
||||
("3", "Clean and optimize rule file (both)"),
|
||||
("99", "Back to Main Menu"),
|
||||
]
|
||||
while True:
|
||||
choice = interactive_menu(items, title="\nRule File Tools:")
|
||||
if choice is None or choice == "99":
|
||||
break
|
||||
elif choice == "1":
|
||||
rule_cleanup_handler(ctx)
|
||||
elif choice == "2":
|
||||
rule_optimize_handler(ctx)
|
||||
elif choice == "3":
|
||||
rule_cleanup_and_optimize_handler(ctx)
|
||||
|
||||
|
||||
def wordlist_filter_length(ctx: Any) -> None:
|
||||
"""Prompt for paths and lengths, then filter wordlist by word length."""
|
||||
infile = input("\n[*] Enter path to input wordlist: ").strip()
|
||||
|
||||
@@ -3738,6 +3738,26 @@ def wordlist_tools_submenu():
|
||||
return _attacks.wordlist_tools_submenu(_attack_ctx())
|
||||
|
||||
|
||||
def rules_cleanup(infile: str, outfile: str) -> bool:
|
||||
"""Clean a rule file using cleanup-rules.bin. Returns True on success."""
|
||||
cleanup_path = os.path.join(hate_path, "hashcat-utils", "bin", "cleanup-rules.bin")
|
||||
with open(infile, "rb") as fin, open(outfile, "wb") as fout:
|
||||
result = subprocess.run([cleanup_path], stdin=fin, stdout=fout)
|
||||
return result.returncode == 0
|
||||
|
||||
|
||||
def rules_optimize(infile: str, outfile: str) -> bool:
|
||||
"""Optimize a rule file using rules_optimize.bin. Returns True on success."""
|
||||
optimize_path = os.path.join(hate_path, "hashcat-utils", "bin", "rules_optimize.bin")
|
||||
with open(infile, "rb") as fin, open(outfile, "wb") as fout:
|
||||
result = subprocess.run([optimize_path], stdin=fin, stdout=fout)
|
||||
return result.returncode == 0
|
||||
|
||||
|
||||
def rule_tools_submenu():
|
||||
return _attacks.rule_tools_submenu(_attack_ctx())
|
||||
|
||||
|
||||
# convert hex words for recycling
|
||||
def convert_hex(working_file):
|
||||
processed_words = []
|
||||
@@ -3971,6 +3991,7 @@ def get_main_menu_items():
|
||||
("21", "Random Rules Attack"),
|
||||
("22", "Combipow Passphrase Attack"),
|
||||
("80", "Wordlist Tools"),
|
||||
("81", "Rule File Tools"),
|
||||
("90", "Download rules from Hashmob.net"),
|
||||
("91", "Analyze Hashcat Rules"),
|
||||
("92", "Download wordlists from Hashmob.net"),
|
||||
@@ -4013,6 +4034,7 @@ def get_main_menu_options():
|
||||
"21": generate_rules_crack,
|
||||
"22": combipow_crack,
|
||||
"80": wordlist_tools_submenu,
|
||||
"81": rule_tools_submenu,
|
||||
"90": lambda: download_hashmob_rules(rules_dir=rulesDirectory),
|
||||
"91": analyze_rules,
|
||||
"92": download_hashmob_wordlists,
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from hate_crack.attacks import (
|
||||
rule_cleanup_and_optimize_handler,
|
||||
rule_cleanup_handler,
|
||||
rule_optimize_handler,
|
||||
rule_tools_submenu,
|
||||
)
|
||||
|
||||
|
||||
def _make_ctx():
|
||||
ctx = MagicMock()
|
||||
ctx.rules_cleanup.return_value = True
|
||||
ctx.rules_optimize.return_value = True
|
||||
ctx.rulesDirectory = "/tmp/rules"
|
||||
return ctx
|
||||
|
||||
|
||||
class TestRuleCleanupHandler:
|
||||
def test_calls_rules_cleanup_with_correct_paths(self, tmp_path):
|
||||
ctx = _make_ctx()
|
||||
infile = tmp_path / "test.rule"
|
||||
infile.write_text("l\nu\n")
|
||||
outfile = tmp_path / "clean.rule"
|
||||
with patch("builtins.input", side_effect=[str(infile), str(outfile)]):
|
||||
rule_cleanup_handler(ctx)
|
||||
ctx.rules_cleanup.assert_called_once_with(str(infile), str(outfile))
|
||||
|
||||
def test_rejects_nonexistent_infile(self, tmp_path):
|
||||
ctx = _make_ctx()
|
||||
with patch("builtins.input", return_value="/nonexistent.rule"):
|
||||
rule_cleanup_handler(ctx)
|
||||
ctx.rules_cleanup.assert_not_called()
|
||||
|
||||
def test_rejects_empty_outfile(self, tmp_path):
|
||||
ctx = _make_ctx()
|
||||
infile = tmp_path / "test.rule"
|
||||
infile.write_text("l\n")
|
||||
with patch("builtins.input", side_effect=[str(infile), ""]):
|
||||
rule_cleanup_handler(ctx)
|
||||
ctx.rules_cleanup.assert_not_called()
|
||||
|
||||
def test_prints_done_on_success(self, tmp_path, capsys):
|
||||
ctx = _make_ctx()
|
||||
infile = tmp_path / "test.rule"
|
||||
infile.write_text("l\n")
|
||||
outfile = tmp_path / "clean.rule"
|
||||
with patch("builtins.input", side_effect=[str(infile), str(outfile)]):
|
||||
rule_cleanup_handler(ctx)
|
||||
assert "[+] Done." in capsys.readouterr().out
|
||||
|
||||
def test_prints_failure_on_error(self, tmp_path, capsys):
|
||||
ctx = _make_ctx()
|
||||
ctx.rules_cleanup.return_value = False
|
||||
infile = tmp_path / "test.rule"
|
||||
infile.write_text("l\n")
|
||||
outfile = tmp_path / "clean.rule"
|
||||
with patch("builtins.input", side_effect=[str(infile), str(outfile)]):
|
||||
rule_cleanup_handler(ctx)
|
||||
assert "[!] Cleanup failed." in capsys.readouterr().out
|
||||
|
||||
|
||||
class TestRuleOptimizeHandler:
|
||||
def test_calls_rules_optimize_with_correct_paths(self, tmp_path):
|
||||
ctx = _make_ctx()
|
||||
infile = tmp_path / "test.rule"
|
||||
infile.write_text("l\nu\n")
|
||||
outfile = tmp_path / "optimized.rule"
|
||||
with patch("builtins.input", side_effect=[str(infile), str(outfile)]):
|
||||
rule_optimize_handler(ctx)
|
||||
ctx.rules_optimize.assert_called_once_with(str(infile), str(outfile))
|
||||
|
||||
def test_rejects_nonexistent_infile(self, tmp_path):
|
||||
ctx = _make_ctx()
|
||||
with patch("builtins.input", return_value="/nonexistent.rule"):
|
||||
rule_optimize_handler(ctx)
|
||||
ctx.rules_optimize.assert_not_called()
|
||||
|
||||
def test_rejects_empty_outfile(self, tmp_path):
|
||||
ctx = _make_ctx()
|
||||
infile = tmp_path / "test.rule"
|
||||
infile.write_text("l\n")
|
||||
with patch("builtins.input", side_effect=[str(infile), ""]):
|
||||
rule_optimize_handler(ctx)
|
||||
ctx.rules_optimize.assert_not_called()
|
||||
|
||||
def test_prints_done_on_success(self, tmp_path, capsys):
|
||||
ctx = _make_ctx()
|
||||
infile = tmp_path / "test.rule"
|
||||
infile.write_text("l\n")
|
||||
outfile = tmp_path / "optimized.rule"
|
||||
with patch("builtins.input", side_effect=[str(infile), str(outfile)]):
|
||||
rule_optimize_handler(ctx)
|
||||
assert "[+] Done." in capsys.readouterr().out
|
||||
|
||||
def test_prints_failure_on_error(self, tmp_path, capsys):
|
||||
ctx = _make_ctx()
|
||||
ctx.rules_optimize.return_value = False
|
||||
infile = tmp_path / "test.rule"
|
||||
infile.write_text("l\n")
|
||||
outfile = tmp_path / "optimized.rule"
|
||||
with patch("builtins.input", side_effect=[str(infile), str(outfile)]):
|
||||
rule_optimize_handler(ctx)
|
||||
assert "[!] Optimize failed." in capsys.readouterr().out
|
||||
|
||||
|
||||
class TestRuleCleanupAndOptimize:
|
||||
def test_calls_cleanup_then_optimize(self, tmp_path):
|
||||
ctx = _make_ctx()
|
||||
infile = tmp_path / "test.rule"
|
||||
infile.write_text("l\nu\n")
|
||||
outfile = tmp_path / "final.rule"
|
||||
with patch("builtins.input", side_effect=[str(infile), str(outfile)]):
|
||||
rule_cleanup_and_optimize_handler(ctx)
|
||||
ctx.rules_cleanup.assert_called_once()
|
||||
ctx.rules_optimize.assert_called_once()
|
||||
|
||||
def test_stops_if_cleanup_fails(self, tmp_path):
|
||||
ctx = _make_ctx()
|
||||
ctx.rules_cleanup.return_value = False
|
||||
infile = tmp_path / "test.rule"
|
||||
infile.write_text("l\n")
|
||||
outfile = tmp_path / "out.rule"
|
||||
with patch("builtins.input", side_effect=[str(infile), str(outfile)]):
|
||||
rule_cleanup_and_optimize_handler(ctx)
|
||||
ctx.rules_optimize.assert_not_called()
|
||||
|
||||
def test_rejects_nonexistent_infile(self, tmp_path):
|
||||
ctx = _make_ctx()
|
||||
with patch("builtins.input", return_value="/nonexistent.rule"):
|
||||
rule_cleanup_and_optimize_handler(ctx)
|
||||
ctx.rules_cleanup.assert_not_called()
|
||||
|
||||
def test_rejects_empty_outfile(self, tmp_path):
|
||||
ctx = _make_ctx()
|
||||
infile = tmp_path / "test.rule"
|
||||
infile.write_text("l\n")
|
||||
with patch("builtins.input", side_effect=[str(infile), ""]):
|
||||
rule_cleanup_and_optimize_handler(ctx)
|
||||
ctx.rules_cleanup.assert_not_called()
|
||||
|
||||
def test_temp_file_cleaned_up_on_success(self, tmp_path):
|
||||
ctx = _make_ctx()
|
||||
captured_tmp = []
|
||||
|
||||
def capture_cleanup(infile, tmpfile):
|
||||
captured_tmp.append(tmpfile)
|
||||
return True
|
||||
|
||||
ctx.rules_cleanup.side_effect = capture_cleanup
|
||||
infile = tmp_path / "test.rule"
|
||||
infile.write_text("l\n")
|
||||
outfile = tmp_path / "final.rule"
|
||||
with patch("builtins.input", side_effect=[str(infile), str(outfile)]):
|
||||
rule_cleanup_and_optimize_handler(ctx)
|
||||
assert captured_tmp, "rules_cleanup should have been called"
|
||||
assert not os.path.exists(captured_tmp[0]), "temp file should be cleaned up"
|
||||
|
||||
def test_temp_file_cleaned_up_on_cleanup_failure(self, tmp_path):
|
||||
ctx = _make_ctx()
|
||||
captured_tmp = []
|
||||
|
||||
def capture_cleanup(infile, tmpfile):
|
||||
captured_tmp.append(tmpfile)
|
||||
return False
|
||||
|
||||
ctx.rules_cleanup.side_effect = capture_cleanup
|
||||
infile = tmp_path / "test.rule"
|
||||
infile.write_text("l\n")
|
||||
outfile = tmp_path / "out.rule"
|
||||
with patch("builtins.input", side_effect=[str(infile), str(outfile)]):
|
||||
rule_cleanup_and_optimize_handler(ctx)
|
||||
if captured_tmp:
|
||||
assert not os.path.exists(captured_tmp[0]), "temp file should be cleaned up"
|
||||
|
||||
def test_prints_done_on_full_success(self, tmp_path, capsys):
|
||||
ctx = _make_ctx()
|
||||
infile = tmp_path / "test.rule"
|
||||
infile.write_text("l\n")
|
||||
outfile = tmp_path / "final.rule"
|
||||
with patch("builtins.input", side_effect=[str(infile), str(outfile)]):
|
||||
rule_cleanup_and_optimize_handler(ctx)
|
||||
assert "[+] Done." in capsys.readouterr().out
|
||||
|
||||
|
||||
class TestRuleToolsSubmenu:
|
||||
def test_dispatches_to_cleanup(self):
|
||||
ctx = _make_ctx()
|
||||
with (
|
||||
patch("hate_crack.attacks.rule_cleanup_handler") as mock_fn,
|
||||
patch(
|
||||
"hate_crack.menu.interactive_menu", side_effect=["1", "99"]
|
||||
),
|
||||
):
|
||||
rule_tools_submenu(ctx)
|
||||
mock_fn.assert_called_once_with(ctx)
|
||||
|
||||
def test_dispatches_to_optimize(self):
|
||||
ctx = _make_ctx()
|
||||
with (
|
||||
patch("hate_crack.attacks.rule_optimize_handler") as mock_fn,
|
||||
patch(
|
||||
"hate_crack.menu.interactive_menu", side_effect=["2", "99"]
|
||||
),
|
||||
):
|
||||
rule_tools_submenu(ctx)
|
||||
mock_fn.assert_called_once_with(ctx)
|
||||
|
||||
def test_dispatches_to_cleanup_and_optimize(self):
|
||||
ctx = _make_ctx()
|
||||
with (
|
||||
patch("hate_crack.attacks.rule_cleanup_and_optimize_handler") as mock_fn,
|
||||
patch(
|
||||
"hate_crack.menu.interactive_menu", side_effect=["3", "99"]
|
||||
),
|
||||
):
|
||||
rule_tools_submenu(ctx)
|
||||
mock_fn.assert_called_once_with(ctx)
|
||||
|
||||
def test_exits_on_99(self):
|
||||
ctx = _make_ctx()
|
||||
with patch("hate_crack.menu.interactive_menu", return_value="99"):
|
||||
rule_tools_submenu(ctx)
|
||||
|
||||
def test_exits_on_none(self):
|
||||
ctx = _make_ctx()
|
||||
with patch("hate_crack.menu.interactive_menu", return_value=None):
|
||||
rule_tools_submenu(ctx)
|
||||
|
||||
def test_loops_until_exit(self):
|
||||
ctx = _make_ctx()
|
||||
with (
|
||||
patch("hate_crack.attacks.rule_cleanup_handler") as mock_fn,
|
||||
patch(
|
||||
"hate_crack.menu.interactive_menu",
|
||||
side_effect=["1", "1", "99"],
|
||||
),
|
||||
):
|
||||
rule_tools_submenu(ctx)
|
||||
assert mock_fn.call_count == 2
|
||||
@@ -0,0 +1,116 @@
|
||||
"""Tests for rules_cleanup and rules_optimize subprocess wrappers in main.py."""
|
||||
import subprocess
|
||||
from unittest.mock import MagicMock, mock_open, patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def _load_main():
|
||||
import importlib
|
||||
import os
|
||||
import sys
|
||||
|
||||
os.environ.setdefault("HATE_CRACK_SKIP_INIT", "1")
|
||||
if "hate_crack.main" in sys.modules:
|
||||
return sys.modules["hate_crack.main"]
|
||||
return importlib.import_module("hate_crack.main")
|
||||
|
||||
|
||||
class TestRulesCleanupWrapper:
|
||||
def test_runs_cleanup_binary_with_file_io(self, tmp_path):
|
||||
main = _load_main()
|
||||
infile = tmp_path / "input.rule"
|
||||
infile.write_text("l\nu\n")
|
||||
outfile = tmp_path / "output.rule"
|
||||
|
||||
fake_result = MagicMock()
|
||||
fake_result.returncode = 0
|
||||
|
||||
with patch("subprocess.run", return_value=fake_result) as mock_run:
|
||||
result = main.rules_cleanup(str(infile), str(outfile))
|
||||
|
||||
assert result is True
|
||||
mock_run.assert_called_once()
|
||||
call_args = mock_run.call_args
|
||||
cmd = call_args[0][0]
|
||||
assert cmd[0].endswith("cleanup-rules.bin")
|
||||
|
||||
def test_returns_false_on_nonzero_exit(self, tmp_path):
|
||||
main = _load_main()
|
||||
infile = tmp_path / "input.rule"
|
||||
infile.write_text("l\n")
|
||||
outfile = tmp_path / "output.rule"
|
||||
|
||||
fake_result = MagicMock()
|
||||
fake_result.returncode = 1
|
||||
|
||||
with patch("subprocess.run", return_value=fake_result):
|
||||
result = main.rules_cleanup(str(infile), str(outfile))
|
||||
|
||||
assert result is False
|
||||
|
||||
def test_binary_path_uses_hate_path(self, tmp_path):
|
||||
main = _load_main()
|
||||
infile = tmp_path / "input.rule"
|
||||
infile.write_text("l\n")
|
||||
outfile = tmp_path / "output.rule"
|
||||
|
||||
fake_result = MagicMock()
|
||||
fake_result.returncode = 0
|
||||
|
||||
with patch("subprocess.run", return_value=fake_result) as mock_run:
|
||||
main.rules_cleanup(str(infile), str(outfile))
|
||||
|
||||
cmd = mock_run.call_args[0][0]
|
||||
expected_suffix = "hashcat-utils/bin/cleanup-rules.bin"
|
||||
assert cmd[0].endswith(expected_suffix), f"Expected path ending with {expected_suffix}, got {cmd[0]}"
|
||||
|
||||
|
||||
class TestRulesOptimizeWrapper:
|
||||
def test_runs_optimize_binary_with_file_io(self, tmp_path):
|
||||
main = _load_main()
|
||||
infile = tmp_path / "input.rule"
|
||||
infile.write_text("l\nu\n")
|
||||
outfile = tmp_path / "output.rule"
|
||||
|
||||
fake_result = MagicMock()
|
||||
fake_result.returncode = 0
|
||||
|
||||
with patch("subprocess.run", return_value=fake_result) as mock_run:
|
||||
result = main.rules_optimize(str(infile), str(outfile))
|
||||
|
||||
assert result is True
|
||||
mock_run.assert_called_once()
|
||||
call_args = mock_run.call_args
|
||||
cmd = call_args[0][0]
|
||||
assert cmd[0].endswith("rules_optimize.bin")
|
||||
|
||||
def test_returns_false_on_nonzero_exit(self, tmp_path):
|
||||
main = _load_main()
|
||||
infile = tmp_path / "input.rule"
|
||||
infile.write_text("l\n")
|
||||
outfile = tmp_path / "output.rule"
|
||||
|
||||
fake_result = MagicMock()
|
||||
fake_result.returncode = 1
|
||||
|
||||
with patch("subprocess.run", return_value=fake_result):
|
||||
result = main.rules_optimize(str(infile), str(outfile))
|
||||
|
||||
assert result is False
|
||||
|
||||
def test_binary_path_uses_hate_path(self, tmp_path):
|
||||
main = _load_main()
|
||||
infile = tmp_path / "input.rule"
|
||||
infile.write_text("l\n")
|
||||
outfile = tmp_path / "output.rule"
|
||||
|
||||
fake_result = MagicMock()
|
||||
fake_result.returncode = 0
|
||||
|
||||
with patch("subprocess.run", return_value=fake_result) as mock_run:
|
||||
main.rules_optimize(str(infile), str(outfile))
|
||||
|
||||
cmd = mock_run.call_args[0][0]
|
||||
expected_suffix = "hashcat-utils/bin/rules_optimize.bin"
|
||||
assert cmd[0].endswith(expected_suffix), f"Expected path ending with {expected_suffix}, got {cmd[0]}"
|
||||
@@ -31,6 +31,7 @@ MENU_OPTION_TEST_CASES = [
|
||||
("21", CLI_MODULE._attacks, "generate_rules_crack", "random-rules"),
|
||||
("22", CLI_MODULE._attacks, "combipow_crack", "combipow"),
|
||||
("80", CLI_MODULE._attacks, "wordlist_tools_submenu", "wordlist-tools"),
|
||||
("81", CLI_MODULE._attacks, "rule_tools_submenu", "rule-tools"),
|
||||
("90", CLI_MODULE, "download_hashmob_rules", "hashmob-rules"),
|
||||
("91", CLI_MODULE, "weakpass_wordlist_menu", "weakpass-menu"),
|
||||
("92", CLI_MODULE, "download_hashmob_wordlists", "hashmob-wordlists"),
|
||||
|
||||
Reference in New Issue
Block a user