Rename to stealergram, add pyproject.toml, purge em-dashes

- Rename project to stealergram throughout
- Add pyproject.toml (replaces requirements.txt split, folds pytest.ini)
- Replace all em-dashes with hyphens across all source files

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-19 10:06:30 -04:00
parent 4c104cddd2
commit 741e6bb0d3
46 changed files with 244 additions and 191 deletions

View File

@@ -7,7 +7,7 @@ os.environ.setdefault("API_HASH", "dummy_hash_for_tests")
os.environ.setdefault("BOT_TOKEN", "0:dummy_bot_token")
os.environ.setdefault("NOTIFY_CHAT_ID", "99999")
# Web frontend test defaults set once here so all web test files see the same values.
# Web frontend test defaults - set once here so all web test files see the same values.
os.environ.setdefault("WEB_SECRET_KEY", "test-secret-key-for-pytest")
os.environ.setdefault("WEB_ADMIN_USER", "superadmin")
os.environ.setdefault("WEB_ADMIN_PASS", "superpass")
@@ -17,8 +17,8 @@ import config
import utils.scorer as scorer
# Two test keywords:
# @testcorp\.com employee email domain (triggers CRITICAL)
# testcorp\.com plain domain match (triggers LOW baseline)
# @testcorp\.com - employee email domain (triggers CRITICAL)
# testcorp\.com - plain domain match (triggers LOW baseline)
TEST_KEYWORDS = [r"@testcorp\.com", r"testcorp\.com"]
@@ -29,7 +29,7 @@ def patched_keywords(monkeypatch):
scorer's module-level globals so scoring logic uses known test patterns.
scorer.py now reads _config.TARGET_KEYWORDS at call time via `import config as _config`,
so patching config.TARGET_KEYWORDS is sufficient no direct scorer patch needed.
so patching config.TARGET_KEYWORDS is sufficient - no direct scorer patch needed.
"""
monkeypatch.setattr(config, "TARGET_KEYWORDS", TEST_KEYWORDS)
monkeypatch.setattr(scorer, "EMPLOYEE_DOMAINS", scorer._build_employee_domains())

View File

@@ -1,5 +1,5 @@
"""
Tests for utils/cache.py file-ID deduplication cache.
Tests for utils/cache.py - file-ID deduplication cache.
Each test gets an isolated cache file via the `isolated_cache` fixture
so tests never touch data/cache.json.

View File

@@ -1,5 +1,5 @@
"""
Tests for utils/database.py SQLite persistence layer.
Tests for utils/database.py - SQLite persistence layer.
Each test gets an isolated in-memory-equivalent DB via the `isolated_db`
fixture so tests never touch data/hits.db.
@@ -112,7 +112,7 @@ def test_by_severity_returns_correct_severity():
def test_by_severity_excludes_duplicates():
"""seen_before=1 rows must be invisible to by_severity they are stored for stats only."""
"""seen_before=1 rows must be invisible to by_severity - they are stored for stats only."""
hit = make_hit(severity=HIGH, url="intranet.testcorp.com")
db_module.insert_hits([hit], source="c", filename="f.txt", seen_before=True)
assert db_module.by_severity(HIGH) == []

View File

@@ -1,5 +1,5 @@
"""
Tests for tui/events.py subscribe/unsubscribe broadcast, signal_channel_changed.
Tests for tui/events.py - subscribe/unsubscribe broadcast, signal_channel_changed.
"""
import queue

View File

@@ -1,5 +1,5 @@
"""
Tests for core/processor.py archive extraction and line-by-line search.
Tests for core/processor.py - archive extraction and line-by-line search.
No Telegram deps, no async. Tests create real archive fixtures in tmp_path
so process_file's cleanup guarantee can be verified against actual disk state.
@@ -60,7 +60,7 @@ class TestSearchFile:
assert search_file(f, patterns) == ["testcorp.com|user|pass"]
def test_handles_encoding_errors_gracefully(self, tmp_path, patterns):
"""Combo files are often messy invalid bytes must not crash the search."""
"""Combo files are often messy - invalid bytes must not crash the search."""
f = tmp_path / "combo.txt"
f.write_bytes(
b"testcorp.com|user1|pass\n"
@@ -81,7 +81,7 @@ class TestSearchFile:
assert len(hits) == 2
# ─── process_file plain .txt ────────────────────────────────────────────────
# ─── process_file - plain .txt ────────────────────────────────────────────────
class TestProcessFilePlainText:
def test_returns_hits(self, tmp_path, patterns):
@@ -104,7 +104,7 @@ class TestProcessFilePlainText:
assert not f.exists()
# ─── process_file .zip extraction ──────────────────────────────────────────
# ─── process_file - .zip extraction ──────────────────────────────────────────
class TestProcessFileZip:
def _make_zip(self, tmp_path: Path, content: str, filename="content.txt") -> Path:
@@ -155,7 +155,7 @@ class TestProcessFileZip:
assert len(hits) == 2
# ─── process_file nested archives ──────────────────────────────────────────
# ─── process_file - nested archives ──────────────────────────────────────────
class TestProcessFileNested:
def test_nested_zip_is_recursed(self, tmp_path, patterns):
@@ -177,7 +177,7 @@ class TestProcessFileNested:
assert not (tmp_path / "outer").exists()
# ─── process_file password-protected .7z ───────────────────────────────────
# ─── process_file - password-protected .7z ───────────────────────────────────
class TestProcessFile7zPassword:
def test_unlocks_with_correct_password(self, tmp_path, patterns, monkeypatch):
@@ -218,6 +218,6 @@ class TestProcessFile7zPassword:
z.write(txt, "content.txt")
txt.unlink()
# No hits archive could not be opened
# No hits - archive could not be opened
hits = process_file(szf, patterns)
assert hits == []

View File

@@ -1,10 +1,10 @@
"""
Tests for utils/scorer.py severity scoring and ULP line parsing.
Tests for utils/scorer.py - severity scoring and ULP line parsing.
All tests use the `patched_keywords` fixture (see conftest.py) which
replaces TARGET_KEYWORDS with two entries:
@testcorp.com employee email domain (CRITICAL trigger)
testcorp.com plain domain match (LOW baseline)
@testcorp.com - employee email domain (CRITICAL trigger)
testcorp.com - plain domain match (LOW baseline)
"""
import pytest
@@ -50,7 +50,7 @@ class TestULPParsingRealWorld:
@pytest.mark.parametrize("line,exp_url,exp_user,exp_pass", [
# ── Protocol + port + path, colon separator ──────────────────────────
# Port is digits followed by '/' must be consumed as part of the URL.
# Port is digits followed by '/' - must be consumed as part of the URL.
(
"http://portal.fakehosp.example.com:88/:55512309-1:hunter2",
"http://portal.fakehosp.example.com:88/", "55512309-1", "hunter2",
@@ -91,7 +91,7 @@ class TestULPParsingRealWorld:
"jdoe@fakehosp.example.com", "Passw0rd!",
),
# ── Pipe separator (unambiguous port stays in URL) ──────────────────
# ── Pipe separator (unambiguous - port stays in URL) ──────────────────
(
"http://portal.fakehosp.example.com:88/|22.987.654-3|florida88",
"http://portal.fakehosp.example.com:88/", "22.987.654-3", "florida88",
@@ -113,7 +113,7 @@ class TestULPParsingRealWorld:
"portal.fakehosp.example.com:88/", "22.987.654-3", "florida88",
),
# ── No protocol, no port plain colon separators ────────────────────
# ── No protocol, no port - plain colon separators ────────────────────
(
"booking.fakehosp.example.com:66778899-7:correcthorse",
"booking.fakehosp.example.com", "66778899-7", "correcthorse",
@@ -234,7 +234,7 @@ class TestWeakPasswordFlags:
assert any("Common password" in r for r in hit.reasons)
def test_weak_password_does_not_escalate_severity(self, patched_keywords):
"""Weak password flags are informational they must not change severity."""
"""Weak password flags are informational - they must not change severity."""
hit = score_hit("testcorp.com|user|abc")
assert hit.severity == LOW

View File

@@ -1,5 +1,5 @@
"""
Tests for web/auth.py JWT token lifecycle, bcrypt helpers.
Tests for web/auth.py - JWT token lifecycle, bcrypt helpers.
"""
import pytest

View File

@@ -1,5 +1,5 @@
"""
Tests for web/db.py user store and refresh token management.
Tests for web/db.py - user store and refresh token management.
"""
import pytest