Files
derp/tests/test_flaskpaste.py
user aebe1589d2 feat: add URL shortening to subscription announcements
Bot.shorten_url() method delegates to flaskpaste plugin when loaded.
RSS, YouTube, and pastemoni announcements auto-shorten links.
Includes test_flaskpaste.py (9 cases) and FakeBot updates in 3 test files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 17:35:03 +01:00

213 lines
6.3 KiB
Python

"""Tests for the FlaskPaste plugin and Bot.shorten_url method."""
import asyncio
import importlib.util
import sys
from pathlib import Path
from unittest.mock import 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.flaskpaste",
Path(__file__).resolve().parent.parent / "plugins" / "flaskpaste.py",
)
_mod = importlib.util.module_from_spec(_spec)
sys.modules[_spec.name] = _mod
_spec.loader.exec_module(_mod)
from plugins.flaskpaste import ( # noqa: E402
cmd_paste,
cmd_shorten,
create_paste,
shorten_url,
)
# -- Helpers -----------------------------------------------------------------
class _FakeState:
"""In-memory stand-in for bot.state."""
def __init__(self):
self._store: dict[str, dict[str, str]] = {}
def get(self, plugin: str, key: str, default: str | None = None) -> str | None:
return self._store.get(plugin, {}).get(key, default)
def set(self, plugin: str, key: str, value: str) -> None:
self._store.setdefault(plugin, {})[key] = value
def delete(self, plugin: str, key: str) -> bool:
try:
del self._store[plugin][key]
return True
except KeyError:
return False
def keys(self, plugin: str) -> list[str]:
return sorted(self._store.get(plugin, {}).keys())
class _FakeRegistry:
"""Minimal registry stand-in."""
def __init__(self):
self._modules: dict = {}
class _FakeBot:
"""Minimal bot stand-in that captures sent/replied messages."""
def __init__(self, *, admin: bool = False):
self.sent: list[tuple[str, str]] = []
self.replied: list[str] = []
self.state = _FakeState()
self.registry = _FakeRegistry()
self._admin = admin
self.config: dict = {}
async def send(self, target: str, text: str) -> None:
self.sent.append((target, text))
async def reply(self, message, text: str) -> None:
self.replied.append(text)
def _is_admin(self, message) -> bool:
return self._admin
def _msg(text: str, nick: str = "alice", target: str = "#test") -> Message:
"""Create a channel PRIVMSG."""
return Message(
raw="", prefix=f"{nick}!~{nick}@host", nick=nick,
command="PRIVMSG", params=[target, text], tags={},
)
# ---------------------------------------------------------------------------
# TestCmdShorten
# ---------------------------------------------------------------------------
class TestCmdShorten:
def test_missing_url(self):
bot = _FakeBot()
asyncio.run(cmd_shorten(bot, _msg("!shorten")))
assert "Usage:" in bot.replied[0]
def test_invalid_scheme(self):
bot = _FakeBot()
asyncio.run(cmd_shorten(bot, _msg("!shorten ftp://example.com")))
assert "http://" in bot.replied[0]
def test_success(self):
bot = _FakeBot()
async def inner():
with patch.object(
_mod, "_shorten_url",
return_value="https://paste.mymx.me/s/abc123",
):
await cmd_shorten(bot, _msg("!shorten https://example.com/long"))
assert "paste.mymx.me/s/abc123" in bot.replied[0]
asyncio.run(inner())
def test_failure(self):
bot = _FakeBot()
async def inner():
with patch.object(
_mod, "_shorten_url",
side_effect=ConnectionError("down"),
):
await cmd_shorten(bot, _msg("!shorten https://example.com/long"))
assert "shorten failed" in bot.replied[0]
asyncio.run(inner())
# ---------------------------------------------------------------------------
# TestCmdPaste
# ---------------------------------------------------------------------------
class TestCmdPaste:
def test_missing_text(self):
bot = _FakeBot()
asyncio.run(cmd_paste(bot, _msg("!paste")))
assert "Usage:" in bot.replied[0]
def test_success(self):
bot = _FakeBot()
async def inner():
with patch.object(
_mod, "_create_paste",
return_value="https://paste.mymx.me/xyz789",
):
await cmd_paste(bot, _msg("!paste hello world"))
assert "paste.mymx.me/xyz789" in bot.replied[0]
asyncio.run(inner())
def test_failure(self):
bot = _FakeBot()
async def inner():
with patch.object(
_mod, "_create_paste",
side_effect=ConnectionError("down"),
):
await cmd_paste(bot, _msg("!paste hello world"))
assert "paste failed" in bot.replied[0]
asyncio.run(inner())
# ---------------------------------------------------------------------------
# TestShortenUrlHelper
# ---------------------------------------------------------------------------
class TestShortenUrlHelper:
def test_returns_short_url(self):
bot = _FakeBot()
with patch.object(
_mod, "_shorten_url",
return_value="https://paste.mymx.me/s/short",
):
result = shorten_url(bot, "https://example.com/long")
assert result == "https://paste.mymx.me/s/short"
def test_returns_original_on_error(self):
bot = _FakeBot()
with patch.object(
_mod, "_shorten_url",
side_effect=ConnectionError("fail"),
):
result = shorten_url(bot, "https://example.com/long")
assert result == "https://example.com/long"
# ---------------------------------------------------------------------------
# TestCreatePasteHelper
# ---------------------------------------------------------------------------
class TestCreatePasteHelper:
def test_returns_paste_url(self):
bot = _FakeBot()
with patch.object(
_mod, "_create_paste",
return_value="https://paste.mymx.me/abc",
):
result = create_paste(bot, "hello")
assert result == "https://paste.mymx.me/abc"
def test_returns_none_on_error(self):
bot = _FakeBot()
with patch.object(
_mod, "_create_paste",
side_effect=ConnectionError("fail"),
):
result = create_paste(bot, "hello")
assert result is None