fixed uv tool install

This commit is contained in:
Justin Bollinger
2026-01-30 16:39:03 -05:00
parent 4899da197e
commit 175ddfca4c
8 changed files with 2397 additions and 19 deletions
+2
View File
@@ -5,3 +5,5 @@ __pycache__/
hate_crack/__pycache__/
tests/__pycache__/
uv.lock
build/
hate_crack.egg-info/
-19
View File
@@ -1,19 +0,0 @@
PREFIX ?= /usr/local
BINDIR ?= $(PREFIX)/bin
SCRIPT ?= hate_crack
WRAPPER_PATH := $(BINDIR)/$(SCRIPT)
PROJECT_ROOT := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
ENTRYPOINT := $(PROJECT_ROOT)/hate_crack/hate_crack.py
UV ?= uv
.PHONY: install clean
install:
@mkdir -p $(BINDIR)
@printf '%s\n' "#!/bin/sh" "" "$(UV) run $(ENTRYPOINT) \"\$$@\"" > $(WRAPPER_PATH)
@chmod +x $(WRAPPER_PATH)
@echo "Installed $(WRAPPER_PATH)"
clean:
@rm -f $(WRAPPER_PATH)
@echo "Removed $(WRAPPER_PATH)"
+12
View File
@@ -0,0 +1,12 @@
#!/usr/bin/env python3
import sys
from .hate_crack import cli_main
def main() -> int:
return cli_main() or 0
if __name__ == "__main__":
sys.exit(main())
+2220
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -8,6 +8,11 @@ def _resolve_root():
def _load_root_module():
try:
from . import _root # type: ignore
return _root
except Exception:
pass
root_path = _resolve_root()
if not os.path.isfile(root_path):
raise FileNotFoundError(f"Root hate_crack.py not found at {root_path}")
+10
View File
@@ -1,3 +1,7 @@
[build-system]
requires = ["setuptools>=69"]
build-backend = "setuptools.build_meta"
[project]
name = "hate-crack"
version = "2.0"
@@ -10,3 +14,9 @@ dependencies = [
"beautifulsoup4>=4.12.0",
"ruff>=0.9.4",
]
[project.scripts]
hate_crack = "hate_crack.__main__:main"
[tool.setuptools.packages.find]
include = ["hate_crack*"]
+95
View File
@@ -0,0 +1,95 @@
import os
import sys
import types
import pytest
from unittest import mock
import hate_crack.api as api
@pytest.fixture
def fake_file(tmp_path):
return tmp_path / "testfile.txt"
@pytest.fixture
def patch_dependencies(tmp_path):
# Patch sanitize_filename to just return the filename
patch1 = mock.patch("hate_crack.api.sanitize_filename", side_effect=lambda x: x)
# Patch get_hcat_wordlists_dir to return tmp_path
patch2 = mock.patch("hate_crack.api.get_hcat_wordlists_dir", return_value=str(tmp_path))
# Patch extract_with_7z to just return True
patch3 = mock.patch("hate_crack.api.extract_with_7z", return_value=True)
# Patch os.replace to do nothing
patch4 = mock.patch("os.replace")
# Patch os.makedirs to do nothing
patch5 = mock.patch("os.makedirs")
patches = [patch1, patch2, patch3, patch4, patch5]
for p in patches:
p.start()
yield
for p in patches:
p.stop()
def make_mock_response(content=b"abc", total=3, status_code=200, endswith='.txt'):
mock_resp = mock.MagicMock()
mock_resp.__enter__.return_value = mock_resp
mock_resp.__exit__.return_value = False
mock_resp.iter_content = lambda chunk_size: [content]
mock_resp.headers = {'content-length': str(total)}
mock_resp.status_code = status_code
mock_resp.raise_for_status = mock.Mock()
return mock_resp
def test_download_success(tmp_path, patch_dependencies):
file_name = "wordlist.txt"
out_path = str(tmp_path / file_name)
mock_resp = make_mock_response(content=b"abc", total=3)
with mock.patch("hate_crack.api.requests.get", return_value=mock_resp):
with mock.patch("builtins.open", mock.mock_open()) as m_open:
result = api.download_official_wordlist(file_name, out_path)
assert result is True
m_open.assert_called() # File was opened for writing
def test_download_7z_triggers_extract(tmp_path, patch_dependencies):
file_name = "archive.7z"
out_path = str(tmp_path / file_name)
mock_resp = make_mock_response(content=b"abc", total=3)
with mock.patch("hate_crack.api.requests.get", return_value=mock_resp):
with mock.patch("builtins.open", mock.mock_open()):
with mock.patch("hate_crack.api.extract_with_7z") as m_extract:
m_extract.return_value = True
result = api.download_official_wordlist(file_name, out_path)
assert result is True
m_extract.assert_called_once()
def test_download_keyboard_interrupt(tmp_path, patch_dependencies):
file_name = "wordlist.txt"
out_path = str(tmp_path / file_name)
# Simulate KeyboardInterrupt in requests.get context manager
def raise_keyboard_interrupt(*a, **kw):
raise KeyboardInterrupt()
with mock.patch("hate_crack.api.requests.get", side_effect=raise_keyboard_interrupt):
result = api.download_official_wordlist(file_name, out_path)
assert result is False
def test_download_exception(tmp_path, patch_dependencies):
file_name = "wordlist.txt"
out_path = str(tmp_path / file_name)
# Simulate generic Exception in requests.get context manager
def raise_exception(*a, **kw):
raise Exception("fail")
with mock.patch("hate_crack.api.requests.get", side_effect=raise_exception):
result = api.download_official_wordlist(file_name, out_path)
assert result is False
def test_progress_bar_prints(tmp_path, patch_dependencies, capsys):
file_name = "wordlist.txt"
out_path = str(tmp_path / file_name)
# Simulate a file with a known size
content = b"x" * 8192
mock_resp = make_mock_response(content=content, total=8192)
with mock.patch("hate_crack.api.requests.get", return_value=mock_resp):
with mock.patch("builtins.open", mock.mock_open()):
result = api.download_official_wordlist(file_name, out_path)
assert result is True
captured = capsys.readouterr()
assert "Downloaded" in captured.out or "Downloaded" in captured.err
+53
View File
@@ -0,0 +1,53 @@
import importlib.util
from pathlib import Path
import pytest
PROJECT_ROOT = Path(__file__).resolve().parents[1]
CLI_SPEC = importlib.util.spec_from_file_location("hate_crack_cli", PROJECT_ROOT / "hate_crack.py")
CLI_MODULE = importlib.util.module_from_spec(CLI_SPEC)
CLI_SPEC.loader.exec_module(CLI_MODULE)
MENU_OPTION_TEST_CASES = [
("1", CLI_MODULE._attacks, "quick_crack", "quick-crack"),
("2", CLI_MODULE._attacks, "extensive_crack", "extensive-crack"),
("3", CLI_MODULE._attacks, "brute_force_crack", "brute-force"),
("4", CLI_MODULE._attacks, "top_mask_crack", "top-mask"),
("5", CLI_MODULE._attacks, "fingerprint_crack", "fingerprint"),
("6", CLI_MODULE._attacks, "combinator_crack", "combinator"),
("7", CLI_MODULE._attacks, "hybrid_crack", "hybrid"),
("8", CLI_MODULE._attacks, "pathwell_crack", "pathwell"),
("9", CLI_MODULE._attacks, "prince_attack", "prince"),
("10", CLI_MODULE._attacks, "yolo_combination", "yolo"),
("11", CLI_MODULE._attacks, "middle_combinator", "middle"),
("12", CLI_MODULE._attacks, "thorough_combinator", "thorough"),
("13", CLI_MODULE._attacks, "bandrel_method", "bandrel"),
("91", CLI_MODULE, "download_hashmob_rules", "hashmob-rules"),
("92", CLI_MODULE, "weakpass_wordlist_menu", "weakpass-menu"),
("93", CLI_MODULE, "download_hashmob_wordlists", "hashmob-wordlists"),
("94", CLI_MODULE, "weakpass_wordlist_menu", "weakpass-menu-secondary"),
("95", CLI_MODULE, "hashview_api", "hashview"),
("96", CLI_MODULE, "pipal", "pipal"),
("97", CLI_MODULE, "export_excel", "export-excel"),
("98", CLI_MODULE, "show_results", "show-results"),
("99", CLI_MODULE, "show_readme", "show-readme"),
("100", CLI_MODULE, "quit_hc", "quit"),
]
@pytest.mark.parametrize(
("option_key", "target_module", "target_attr", "expected_prefix"),
MENU_OPTION_TEST_CASES,
)
def test_main_menu_option_returns_expected(monkeypatch, option_key, target_module, target_attr, expected_prefix):
sentinel = f"{expected_prefix}-{option_key}"
monkeypatch.setattr(
target_module,
target_attr,
lambda *args, **kwargs: sentinel,
)
options = CLI_MODULE.get_main_menu_options()
assert option_key in options, f"Menu option {option_key} must exist"
handler = options[option_key]
assert handler() == sentinel