mirror of
https://github.com/trustedsec/hate_crack.git
synced 2026-04-28 12:03:11 -07:00
Replace input() with ctx.select_file_with_autocomplete() for all file and directory path prompts in the 7 wordlist tools submenu functions. Non-path prompts (lengths, masks, offsets, mode selection) remain as plain input() calls. Update tests to set ctx.select_file_with_autocomplete.side_effect for file path values and leave builtins.input patches only for non-path inputs.
309 lines
13 KiB
Python
309 lines
13 KiB
Python
import os
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from hate_crack.attacks import (
|
|
wordlist_cut_substring,
|
|
wordlist_filter_charclass_exclude,
|
|
wordlist_filter_charclass_include,
|
|
wordlist_filter_length,
|
|
wordlist_shard,
|
|
wordlist_split_by_length,
|
|
wordlist_subtract_words,
|
|
wordlist_tools_submenu,
|
|
)
|
|
|
|
|
|
def _make_ctx():
|
|
ctx = MagicMock()
|
|
ctx.wordlist_filter_len.return_value = True
|
|
ctx.wordlist_filter_req_include.return_value = True
|
|
ctx.wordlist_filter_req_exclude.return_value = True
|
|
ctx.wordlist_cutb.return_value = True
|
|
ctx.wordlist_splitlen.return_value = True
|
|
ctx.wordlist_subtract.return_value = True
|
|
ctx.wordlist_subtract_single.return_value = True
|
|
ctx.wordlist_gate.return_value = True
|
|
return ctx
|
|
|
|
|
|
class TestWordlistFilterLength:
|
|
def test_calls_wordlist_filter_len(self, tmp_path):
|
|
ctx = _make_ctx()
|
|
infile = tmp_path / "in.txt"
|
|
infile.write_text("test\n")
|
|
outfile = tmp_path / "out.txt"
|
|
ctx.select_file_with_autocomplete.side_effect = [str(infile), str(outfile)]
|
|
with patch("builtins.input", side_effect=["4", "8"]):
|
|
wordlist_filter_length(ctx)
|
|
ctx.wordlist_filter_len.assert_called_once_with(str(infile), str(outfile), 4, 8)
|
|
|
|
def test_rejects_nonexistent_infile(self, tmp_path):
|
|
ctx = _make_ctx()
|
|
ctx.select_file_with_autocomplete.return_value = "/nonexistent/file.txt"
|
|
wordlist_filter_length(ctx)
|
|
ctx.wordlist_filter_len.assert_not_called()
|
|
|
|
def test_rejects_empty_outfile(self, tmp_path):
|
|
ctx = _make_ctx()
|
|
infile = tmp_path / "in.txt"
|
|
infile.write_text("test\n")
|
|
ctx.select_file_with_autocomplete.side_effect = [str(infile), ""]
|
|
wordlist_filter_length(ctx)
|
|
ctx.wordlist_filter_len.assert_not_called()
|
|
|
|
def test_prints_success_message(self, tmp_path, capsys):
|
|
ctx = _make_ctx()
|
|
infile = tmp_path / "in.txt"
|
|
infile.write_text("test\n")
|
|
outfile = tmp_path / "out.txt"
|
|
ctx.select_file_with_autocomplete.side_effect = [str(infile), str(outfile)]
|
|
with patch("builtins.input", side_effect=["4", "8"]):
|
|
wordlist_filter_length(ctx)
|
|
out = capsys.readouterr().out
|
|
assert "success" in out.lower() or "wrote" in out.lower() or str(outfile) in out
|
|
|
|
def test_prints_failure_message(self, tmp_path, capsys):
|
|
ctx = _make_ctx()
|
|
ctx.wordlist_filter_len.return_value = False
|
|
infile = tmp_path / "in.txt"
|
|
infile.write_text("test\n")
|
|
outfile = tmp_path / "out.txt"
|
|
ctx.select_file_with_autocomplete.side_effect = [str(infile), str(outfile)]
|
|
with patch("builtins.input", side_effect=["4", "8"]):
|
|
wordlist_filter_length(ctx)
|
|
out = capsys.readouterr().out
|
|
assert "fail" in out.lower() or "error" in out.lower() or "!" in out
|
|
|
|
|
|
class TestWordlistFilterCharclassInclude:
|
|
def test_calls_wordlist_filter_req_include(self, tmp_path):
|
|
ctx = _make_ctx()
|
|
infile = tmp_path / "in.txt"
|
|
infile.write_text("test\n")
|
|
outfile = tmp_path / "out.txt"
|
|
ctx.select_file_with_autocomplete.side_effect = [str(infile), str(outfile)]
|
|
with patch("builtins.input", side_effect=["7"]):
|
|
wordlist_filter_charclass_include(ctx)
|
|
ctx.wordlist_filter_req_include.assert_called_once_with(str(infile), str(outfile), 7)
|
|
|
|
def test_rejects_nonexistent_infile(self, tmp_path):
|
|
ctx = _make_ctx()
|
|
ctx.select_file_with_autocomplete.return_value = "/nonexistent/file.txt"
|
|
wordlist_filter_charclass_include(ctx)
|
|
ctx.wordlist_filter_req_include.assert_not_called()
|
|
|
|
|
|
class TestWordlistFilterCharclassExclude:
|
|
def test_calls_wordlist_filter_req_exclude(self, tmp_path):
|
|
ctx = _make_ctx()
|
|
infile = tmp_path / "in.txt"
|
|
infile.write_text("test\n")
|
|
outfile = tmp_path / "out.txt"
|
|
ctx.select_file_with_autocomplete.side_effect = [str(infile), str(outfile)]
|
|
with patch("builtins.input", side_effect=["8"]):
|
|
wordlist_filter_charclass_exclude(ctx)
|
|
ctx.wordlist_filter_req_exclude.assert_called_once_with(str(infile), str(outfile), 8)
|
|
|
|
def test_rejects_nonexistent_infile(self, tmp_path):
|
|
ctx = _make_ctx()
|
|
ctx.select_file_with_autocomplete.return_value = "/nonexistent/file.txt"
|
|
wordlist_filter_charclass_exclude(ctx)
|
|
ctx.wordlist_filter_req_exclude.assert_not_called()
|
|
|
|
|
|
class TestWordlistCutSubstring:
|
|
def test_calls_wordlist_cutb_with_length(self, tmp_path):
|
|
ctx = _make_ctx()
|
|
infile = tmp_path / "in.txt"
|
|
infile.write_text("test\n")
|
|
outfile = tmp_path / "out.txt"
|
|
ctx.select_file_with_autocomplete.side_effect = [str(infile), str(outfile)]
|
|
with patch("builtins.input", side_effect=["2", "4"]):
|
|
wordlist_cut_substring(ctx)
|
|
ctx.wordlist_cutb.assert_called_once_with(str(infile), str(outfile), 2, 4)
|
|
|
|
def test_calls_wordlist_cutb_without_length(self, tmp_path):
|
|
ctx = _make_ctx()
|
|
infile = tmp_path / "in.txt"
|
|
infile.write_text("test\n")
|
|
outfile = tmp_path / "out.txt"
|
|
ctx.select_file_with_autocomplete.side_effect = [str(infile), str(outfile)]
|
|
with patch("builtins.input", side_effect=["2", ""]):
|
|
wordlist_cut_substring(ctx)
|
|
ctx.wordlist_cutb.assert_called_once_with(str(infile), str(outfile), 2, None)
|
|
|
|
def test_rejects_nonexistent_infile(self, tmp_path):
|
|
ctx = _make_ctx()
|
|
ctx.select_file_with_autocomplete.return_value = "/nonexistent/file.txt"
|
|
wordlist_cut_substring(ctx)
|
|
ctx.wordlist_cutb.assert_not_called()
|
|
|
|
|
|
class TestWordlistSplitByLength:
|
|
def test_calls_wordlist_splitlen(self, tmp_path):
|
|
ctx = _make_ctx()
|
|
infile = tmp_path / "in.txt"
|
|
infile.write_text("test\n")
|
|
outdir = tmp_path / "split"
|
|
ctx.select_file_with_autocomplete.side_effect = [str(infile), str(outdir)]
|
|
wordlist_split_by_length(ctx)
|
|
ctx.wordlist_splitlen.assert_called_once_with(str(infile), str(outdir))
|
|
|
|
def test_creates_outdir_if_missing(self, tmp_path):
|
|
ctx = _make_ctx()
|
|
infile = tmp_path / "in.txt"
|
|
infile.write_text("test\n")
|
|
outdir = tmp_path / "split" / "nested"
|
|
ctx.select_file_with_autocomplete.side_effect = [str(infile), str(outdir)]
|
|
wordlist_split_by_length(ctx)
|
|
assert outdir.exists()
|
|
|
|
def test_rejects_nonexistent_infile(self, tmp_path):
|
|
ctx = _make_ctx()
|
|
ctx.select_file_with_autocomplete.return_value = "/nonexistent/file.txt"
|
|
wordlist_split_by_length(ctx)
|
|
ctx.wordlist_splitlen.assert_not_called()
|
|
|
|
|
|
class TestWordlistSubtractWords:
|
|
def test_single_remove_calls_wordlist_subtract_single(self, tmp_path):
|
|
ctx = _make_ctx()
|
|
infile = tmp_path / "in.txt"
|
|
infile.write_text("word1\n")
|
|
removefile = tmp_path / "remove.txt"
|
|
removefile.write_text("word1\n")
|
|
outfile = tmp_path / "out.txt"
|
|
ctx.select_file_with_autocomplete.side_effect = [str(infile), str(removefile), str(outfile)]
|
|
with patch("builtins.input", side_effect=["1"]):
|
|
wordlist_subtract_words(ctx)
|
|
ctx.wordlist_subtract_single.assert_called_once_with(str(infile), str(removefile), str(outfile))
|
|
|
|
def test_multi_remove_calls_wordlist_subtract(self, tmp_path):
|
|
ctx = _make_ctx()
|
|
infile = tmp_path / "in.txt"
|
|
infile.write_text("word1\nword2\n")
|
|
removefile1 = tmp_path / "remove1.txt"
|
|
removefile1.write_text("word1\n")
|
|
removefile2 = tmp_path / "remove2.txt"
|
|
removefile2.write_text("word2\n")
|
|
outfile = tmp_path / "out.txt"
|
|
ctx.select_file_with_autocomplete.side_effect = [
|
|
str(infile),
|
|
str(outfile),
|
|
f"{removefile1},{removefile2}",
|
|
]
|
|
with patch("builtins.input", side_effect=["2"]):
|
|
wordlist_subtract_words(ctx)
|
|
ctx.wordlist_subtract.assert_called_once_with(
|
|
str(infile), str(outfile), str(removefile1), str(removefile2)
|
|
)
|
|
|
|
def test_single_remove_rejects_nonexistent_infile(self, tmp_path):
|
|
ctx = _make_ctx()
|
|
ctx.select_file_with_autocomplete.return_value = "/nonexistent.txt"
|
|
with patch("builtins.input", side_effect=["1"]):
|
|
wordlist_subtract_words(ctx)
|
|
ctx.wordlist_subtract_single.assert_not_called()
|
|
|
|
|
|
class TestWordlistShard:
|
|
def test_calls_wordlist_gate_with_correct_args(self, tmp_path):
|
|
ctx = _make_ctx()
|
|
infile = tmp_path / "in.txt"
|
|
infile.write_text("word1\nword2\nword3\n")
|
|
outfile = tmp_path / "shard.txt"
|
|
ctx.select_file_with_autocomplete.side_effect = [str(infile), str(outfile)]
|
|
with patch("builtins.input", side_effect=["3", "0"]):
|
|
wordlist_shard(ctx)
|
|
ctx.wordlist_gate.assert_called_once_with(str(infile), str(outfile), 3, 0)
|
|
|
|
def test_rejects_offset_gte_mod(self, tmp_path):
|
|
ctx = _make_ctx()
|
|
infile = tmp_path / "in.txt"
|
|
infile.write_text("word1\n")
|
|
outfile = tmp_path / "shard.txt"
|
|
ctx.select_file_with_autocomplete.side_effect = [str(infile), str(outfile)]
|
|
with patch("builtins.input", side_effect=["3", "3"]):
|
|
wordlist_shard(ctx)
|
|
ctx.wordlist_gate.assert_not_called()
|
|
|
|
def test_rejects_nonexistent_infile(self, tmp_path):
|
|
ctx = _make_ctx()
|
|
ctx.select_file_with_autocomplete.return_value = "/nonexistent/file.txt"
|
|
wordlist_shard(ctx)
|
|
ctx.wordlist_gate.assert_not_called()
|
|
|
|
def test_rejects_mod_less_than_2(self, tmp_path):
|
|
ctx = _make_ctx()
|
|
infile = tmp_path / "in.txt"
|
|
infile.write_text("word1\n")
|
|
outfile = tmp_path / "shard.txt"
|
|
ctx.select_file_with_autocomplete.side_effect = [str(infile), str(outfile)]
|
|
with patch("builtins.input", side_effect=["1", "0"]):
|
|
wordlist_shard(ctx)
|
|
ctx.wordlist_gate.assert_not_called()
|
|
|
|
|
|
class TestWordlistToolsSubmenu:
|
|
def test_submenu_dispatches_to_filter_length(self):
|
|
ctx = _make_ctx()
|
|
with patch("hate_crack.attacks.wordlist_filter_length") as mock_fn, \
|
|
patch("hate_crack.attacks.interactive_menu", side_effect=["1", "99"]):
|
|
wordlist_tools_submenu(ctx)
|
|
mock_fn.assert_called_once_with(ctx)
|
|
|
|
def test_submenu_dispatches_to_filter_charclass_include(self):
|
|
ctx = _make_ctx()
|
|
with patch("hate_crack.attacks.wordlist_filter_charclass_include") as mock_fn, \
|
|
patch("hate_crack.attacks.interactive_menu", side_effect=["2", "99"]):
|
|
wordlist_tools_submenu(ctx)
|
|
mock_fn.assert_called_once_with(ctx)
|
|
|
|
def test_submenu_dispatches_to_filter_charclass_exclude(self):
|
|
ctx = _make_ctx()
|
|
with patch("hate_crack.attacks.wordlist_filter_charclass_exclude") as mock_fn, \
|
|
patch("hate_crack.attacks.interactive_menu", side_effect=["3", "99"]):
|
|
wordlist_tools_submenu(ctx)
|
|
mock_fn.assert_called_once_with(ctx)
|
|
|
|
def test_submenu_dispatches_to_cut_substring(self):
|
|
ctx = _make_ctx()
|
|
with patch("hate_crack.attacks.wordlist_cut_substring") as mock_fn, \
|
|
patch("hate_crack.attacks.interactive_menu", side_effect=["4", "99"]):
|
|
wordlist_tools_submenu(ctx)
|
|
mock_fn.assert_called_once_with(ctx)
|
|
|
|
def test_submenu_dispatches_to_split_by_length(self):
|
|
ctx = _make_ctx()
|
|
with patch("hate_crack.attacks.wordlist_split_by_length") as mock_fn, \
|
|
patch("hate_crack.attacks.interactive_menu", side_effect=["5", "99"]):
|
|
wordlist_tools_submenu(ctx)
|
|
mock_fn.assert_called_once_with(ctx)
|
|
|
|
def test_submenu_dispatches_to_subtract_words(self):
|
|
ctx = _make_ctx()
|
|
with patch("hate_crack.attacks.wordlist_subtract_words") as mock_fn, \
|
|
patch("hate_crack.attacks.interactive_menu", side_effect=["6", "99"]):
|
|
wordlist_tools_submenu(ctx)
|
|
mock_fn.assert_called_once_with(ctx)
|
|
|
|
def test_submenu_dispatches_to_shard(self):
|
|
ctx = _make_ctx()
|
|
with patch("hate_crack.attacks.wordlist_shard") as mock_fn, \
|
|
patch("hate_crack.attacks.interactive_menu", side_effect=["7", "99"]):
|
|
wordlist_tools_submenu(ctx)
|
|
mock_fn.assert_called_once_with(ctx)
|
|
|
|
def test_submenu_exits_on_99(self):
|
|
ctx = _make_ctx()
|
|
with patch("hate_crack.attacks.interactive_menu", return_value="99"):
|
|
wordlist_tools_submenu(ctx)
|
|
|
|
def test_submenu_exits_on_none(self):
|
|
ctx = _make_ctx()
|
|
with patch("hate_crack.attacks.interactive_menu", return_value=None):
|
|
wordlist_tools_submenu(ctx)
|