Files
derp/tests/test_archive.py
user e3bb793574 feat: add canary, tcping, archive, resolve plugins
canary: generate realistic fake credentials (token/aws/basic) for
planting as canary tripwires. Per-channel state persistence.

tcping: TCP connect latency probe through SOCKS5 proxy with
min/avg/max reporting. Proxy-compatible alternative to traceroute.

archive: save URLs to Wayback Machine via Save Page Now API,
routed through SOCKS5 proxy.

resolve: bulk DNS resolution (up to 10 hosts) via TCP DNS through
SOCKS5 proxy with concurrent asyncio.gather.

83 new tests (1010 total), docs updated.
2026-02-20 19:38:10 +01:00

151 lines
4.8 KiB
Python

"""Tests for the Wayback Machine archive plugin."""
import asyncio
import importlib.util
import sys
import urllib.error
from pathlib import Path
from unittest.mock import MagicMock, patch
from derp.irc import Message
# plugins/ is not a Python package -- load the module from file path
_spec = importlib.util.spec_from_file_location(
"plugins.archive", Path(__file__).resolve().parent.parent / "plugins" / "archive.py",
)
_mod = importlib.util.module_from_spec(_spec)
sys.modules[_spec.name] = _mod
_spec.loader.exec_module(_mod)
from plugins.archive import _save_page, cmd_archive # noqa: E402
# -- Helpers -----------------------------------------------------------------
class _FakeBot:
def __init__(self):
self.replied: list[str] = []
async def reply(self, message, text: str) -> None:
self.replied.append(text)
def _msg(text: str) -> Message:
return Message(
raw="", prefix="alice!~alice@host", nick="alice",
command="PRIVMSG", params=["#test", text], tags={},
)
# -- _save_page --------------------------------------------------------------
class TestSavePage:
def test_content_location_header(self):
resp = MagicMock()
resp.headers = {"Content-Location": "/web/20260220/https://example.com"}
resp.read.return_value = b""
resp.geturl.return_value = "https://web.archive.org/save/https://example.com"
with patch.object(_mod, "_urlopen", return_value=resp):
result = _save_page("https://example.com")
assert "url" in result
assert "/web/20260220" in result["url"]
def test_final_url_redirect(self):
resp = MagicMock()
resp.headers = {}
resp.read.return_value = b""
resp.geturl.return_value = "https://web.archive.org/web/20260220/https://example.com"
with patch.object(_mod, "_urlopen", return_value=resp):
result = _save_page("https://example.com")
assert "url" in result
assert "/web/20260220" in result["url"]
def test_fallback_url(self):
resp = MagicMock()
resp.headers = {}
resp.read.return_value = b""
resp.geturl.return_value = "https://web.archive.org/save/ok"
with patch.object(_mod, "_urlopen", return_value=resp):
result = _save_page("https://example.com")
assert "url" in result
assert "/web/*/" in result["url"]
def test_rate_limit(self):
exc = urllib.error.HTTPError(
"url", 429, "Too Many Requests", {}, None,
)
with patch.object(_mod, "_urlopen", side_effect=exc):
result = _save_page("https://example.com")
assert "error" in result
assert "rate limit" in result["error"]
def test_origin_unreachable(self):
exc = urllib.error.HTTPError(
"url", 523, "Origin Unreachable", {}, None,
)
with patch.object(_mod, "_urlopen", side_effect=exc):
result = _save_page("https://example.com")
assert "error" in result
assert "unreachable" in result["error"]
def test_generic_http_error(self):
exc = urllib.error.HTTPError(
"url", 500, "Server Error", {}, None,
)
with patch.object(_mod, "_urlopen", side_effect=exc):
result = _save_page("https://example.com")
assert "error" in result
assert "500" in result["error"]
def test_timeout(self):
with patch.object(_mod, "_urlopen", side_effect=TimeoutError("timed out")):
result = _save_page("https://example.com")
assert "error" in result
assert "timeout" in result["error"]
# -- Command handler ---------------------------------------------------------
class TestCmdArchive:
def test_no_args(self):
bot = _FakeBot()
asyncio.run(cmd_archive(bot, _msg("!archive")))
assert "Usage" in bot.replied[0]
def test_no_scheme(self):
bot = _FakeBot()
asyncio.run(cmd_archive(bot, _msg("!archive example.com")))
assert "http://" in bot.replied[0]
def test_success(self):
bot = _FakeBot()
result = {"url": "https://web.archive.org/web/20260220/https://example.com"}
with patch.object(_mod, "_save_page", return_value=result):
asyncio.run(cmd_archive(bot, _msg("!archive https://example.com")))
assert len(bot.replied) == 2
assert "Archiving" in bot.replied[0]
assert "Archived:" in bot.replied[1]
assert "/web/20260220" in bot.replied[1]
def test_error(self):
bot = _FakeBot()
result = {"error": "rate limited -- try again later"}
with patch.object(_mod, "_save_page", return_value=result):
asyncio.run(cmd_archive(bot, _msg("!archive https://example.com")))
assert len(bot.replied) == 2
assert "failed" in bot.replied[1].lower()
assert "rate limit" in bot.replied[1]