Files
derp/tests/test_username.py
user 7184c43b08 fix: resolve test_username.py import for plugins/ directory
Load plugins.username via importlib.util.spec_from_file_location
since plugins/ is not a Python package on sys.path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 06:17:11 +01:00

267 lines
8.5 KiB
Python

"""Tests for the username enumeration plugin."""
import importlib.util
import sys
from pathlib import Path
# plugins/ is not a Python package -- load the module from file path
_spec = importlib.util.spec_from_file_location(
"plugins.username", Path(__file__).resolve().parent.parent / "plugins" / "username.py",
)
_mod = importlib.util.module_from_spec(_spec)
sys.modules[_spec.name] = _mod
_spec.loader.exec_module(_mod)
from plugins.username import ( # noqa: E402
_BY_CATEGORY,
_BY_NAME,
_SERVICES,
_USERNAME_RE,
_Service,
classify,
classify_body,
classify_json,
classify_status,
format_list,
format_single,
format_summary,
)
class TestUsernameRegex:
def test_valid_simple(self):
assert _USERNAME_RE.match("john")
def test_valid_with_dots(self):
assert _USERNAME_RE.match("john.doe")
def test_valid_with_hyphens(self):
assert _USERNAME_RE.match("john-doe")
def test_valid_with_underscores(self):
assert _USERNAME_RE.match("john_doe")
def test_valid_numeric(self):
assert _USERNAME_RE.match("user123")
def test_valid_max_length(self):
assert _USERNAME_RE.match("a" * 39)
def test_invalid_too_long(self):
assert _USERNAME_RE.match("a" * 40) is None
def test_invalid_empty(self):
assert _USERNAME_RE.match("") is None
def test_invalid_spaces(self):
assert _USERNAME_RE.match("john doe") is None
def test_invalid_special_chars(self):
assert _USERNAME_RE.match("john@doe") is None
assert _USERNAME_RE.match("john!") is None
assert _USERNAME_RE.match("user#1") is None
def test_invalid_slash(self):
assert _USERNAME_RE.match("user/name") is None
class TestServiceRegistry:
def test_no_duplicate_names(self):
names = [s.name for s in _SERVICES]
assert len(names) == len(set(names))
def test_all_have_required_fields(self):
for svc in _SERVICES:
assert svc.name, "service must have a name"
assert svc.url, "service must have a url"
assert "{user}" in svc.url, f"{svc.name}: url must contain {{user}}"
assert svc.method in ("status", "json", "body"), (
f"{svc.name}: invalid method '{svc.method}'"
)
assert svc.category in ("dev", "social", "media", "other"), (
f"{svc.name}: invalid category '{svc.category}'"
)
def test_minimum_service_count(self):
assert len(_SERVICES) >= 20
def test_lookup_table_matches(self):
assert len(_BY_NAME) == len(_SERVICES)
def test_categories_cover_all(self):
total = sum(len(svcs) for svcs in _BY_CATEGORY.values())
assert total == len(_SERVICES)
def test_all_four_categories_present(self):
for cat in ("dev", "social", "media", "other"):
assert cat in _BY_CATEGORY, f"missing category: {cat}"
assert len(_BY_CATEGORY[cat]) > 0
class TestClassifyStatus:
def test_found(self):
assert classify_status(200) == "found"
def test_not_found(self):
assert classify_status(404) == "not_found"
def test_error_zero(self):
assert classify_status(0) == "error"
def test_error_redirect(self):
assert classify_status(302) == "error"
def test_error_server(self):
assert classify_status(500) == "error"
class TestClassifyJson:
def _svc(self, name: str) -> _Service:
return _Service(name=name, url="https://example.com/{user}", method="json", category="dev")
def test_github_found(self):
body = '{"login": "john", "id": 123}'
assert classify_json(self._svc("GitHub"), 200, body) == "found"
def test_github_not_found(self):
assert classify_json(self._svc("GitHub"), 404, "") == "not_found"
def test_gitlab_found(self):
body = '[{"id": 1, "username": "john"}]'
assert classify_json(self._svc("GitLab"), 200, body) == "found"
def test_gitlab_not_found(self):
assert classify_json(self._svc("GitLab"), 200, "[]") == "not_found"
def test_docker_hub_found(self):
body = '{"id": "abc123", "username": "john"}'
assert classify_json(self._svc("Docker Hub"), 200, body) == "found"
def test_docker_hub_not_found(self):
assert classify_json(self._svc("Docker Hub"), 404, "") == "not_found"
def test_keybase_found(self):
body = '{"them": [{"id": "abc"}]}'
assert classify_json(self._svc("Keybase"), 200, body) == "found"
def test_keybase_not_found(self):
body = '{"them": []}'
assert classify_json(self._svc("Keybase"), 200, body) == "not_found"
def test_devto_found(self):
body = '{"username": "john"}'
assert classify_json(self._svc("Dev.to"), 200, body) == "found"
def test_devto_not_found(self):
assert classify_json(self._svc("Dev.to"), 404, "") == "not_found"
def test_reddit_found(self):
body = '{"kind": "t2", "data": {"name": "john"}}'
assert classify_json(self._svc("Reddit"), 200, body) == "found"
def test_reddit_not_found(self):
body = '{"error": 404}'
assert classify_json(self._svc("Reddit"), 200, body) == "not_found"
def test_gravatar_found(self):
body = '{"entry": [{"id": "123"}]}'
assert classify_json(self._svc("Gravatar"), 200, body) == "found"
def test_gravatar_not_found(self):
assert classify_json(self._svc("Gravatar"), 404, "") == "not_found"
def test_error_on_bad_json(self):
assert classify_json(self._svc("GitHub"), 200, "not json") == "error"
def test_error_on_empty_body(self):
assert classify_json(self._svc("GitHub"), 200, "") == "error"
def test_error_on_zero_status(self):
assert classify_json(self._svc("GitHub"), 0, "") == "error"
class TestClassifyBody:
def test_telegram_found(self):
body = '<div class="tgme_page_extra">@john</div>'
assert classify_body(200, body) == "found"
def test_telegram_not_found(self):
body = '<div>If you have <strong>Telegram</strong></div>'
assert classify_body(200, body) == "not_found"
def test_error_on_zero_status(self):
assert classify_body(0, "") == "error"
class TestClassifyRouter:
def test_routes_to_status(self):
svc = _Service("Test", "https://example.com/{user}", "status", "other")
assert classify(svc, 200, "") == "found"
assert classify(svc, 404, "") == "not_found"
def test_routes_to_json(self):
svc = _Service("GitHub", "https://api.github.com/users/{user}", "json", "dev")
assert classify(svc, 200, '{"login": "x"}') == "found"
def test_routes_to_body(self):
svc = _Service("Telegram", "https://t.me/{user}", "body", "social")
assert classify(svc, 200, "tgme_page_extra") == "found"
class TestFormatSummary:
def test_all_found(self):
results = [("GitHub", "found"), ("GitLab", "found")]
lines = format_summary("john", results)
assert "2 found" in lines[0]
assert "0 not found" in lines[0]
assert "Found: GitHub, GitLab" in lines[1]
def test_mixed_results(self):
results = [
("GitHub", "found"),
("Reddit", "not_found"),
("Twitch", "error"),
]
lines = format_summary("john", results)
assert "1 found" in lines[0]
assert "1 not found" in lines[0]
assert "1 errors" in lines[0]
def test_none_found(self):
results = [("GitHub", "not_found"), ("Reddit", "not_found")]
lines = format_summary("john", results)
assert "0 found" in lines[0]
assert len(lines) == 1 # No "Found:" line
class TestFormatSingle:
def test_found_with_url(self):
svc = _BY_NAME["github"]
line = format_single(svc, "john", "found")
assert "found" in line
assert "https://github.com/john" in line
def test_not_found(self):
svc = _BY_NAME["github"]
line = format_single(svc, "john", "not_found")
assert "not_found" in line
assert "https://" not in line
def test_error(self):
svc = _BY_NAME["github"]
line = format_single(svc, "john", "error")
assert "error" in line
class TestFormatList:
def test_has_all_categories(self):
lines = format_list()
labels = [line.split(":")[0] for line in lines]
assert "Dev" in labels
assert "Social" in labels
assert "Media" in labels
assert "Other" in labels
def test_line_count(self):
assert len(format_list()) == 4