Files
telegram-sticker-downloader/tests/conftest.py
T
2026-07-27 15:16:01 +02:00

143 lines
3.7 KiB
Python

"""Test doubles for the Telegram bot and the CDN fetcher."""
from __future__ import annotations
import asyncio
from dataclasses import dataclass, field
from typing import Any
import pytest
from telegram.error import BadRequest
from sticker_downloader.config import DownloadConfig
@dataclass
class FakeFile:
file_path: str
@dataclass
class FakeThumbnail:
file_id: str
@dataclass
class FakeSticker:
file_id: str
file_unique_id: str = "uniq"
emoji: str | None = "😀"
is_animated: bool = False
is_video: bool = False
thumbnail: FakeThumbnail | None = None
width: int = 512
height: int = 512
type: str = "regular"
@dataclass
class FakeStickerSet:
name: str
title: str = "A Pack"
sticker_type: str = "regular"
stickers: list[FakeSticker] = field(default_factory=list)
thumbnail: FakeThumbnail | None = None
class FakeBot:
"""Implements just the two coroutines the downloader calls."""
def __init__(
self,
sets: dict[str, FakeStickerSet] | None = None,
*,
extensions: dict[str, str] | None = None,
) -> None:
self._sets = sets or {}
#: file_id -> extension served by the fake CDN URL.
self._extensions = extensions or {}
self.get_sticker_set_calls: list[str] = []
self.get_file_calls: list[str] = []
async def get_sticker_set(self, name: str) -> FakeStickerSet:
self.get_sticker_set_calls.append(name)
try:
return self._sets[name]
except KeyError:
raise BadRequest("Stickerset_invalid") from None
async def get_file(self, file_id: str) -> FakeFile:
self.get_file_calls.append(file_id)
extension = self._extensions.get(file_id, "webp")
return FakeFile(file_path=f"https://cdn.example/file/{file_id}.{extension}")
class FakeFetcher:
"""Returns deterministic bytes and can be told to fail for some URLs."""
def __init__(
self,
*,
payload: bytes = b"sticker-bytes",
failures: dict[str, Exception] | None = None,
) -> None:
self._payload = payload
self._failures = failures or {}
self.urls: list[str] = []
self.max_concurrent = 0
self._in_flight = 0
async def fetch(self, url: str) -> bytes:
self.urls.append(url)
self._in_flight += 1
self.max_concurrent = max(self.max_concurrent, self._in_flight)
try:
await asyncio.sleep(0)
for needle, error in self._failures.items():
if needle in url:
raise error
return self._payload
finally:
self._in_flight -= 1
async def no_sleep(_delay: float) -> None:
"""Drop-in for ``asyncio.sleep`` so retry tests run instantly."""
return None
def make_sticker(index: int, **overrides: Any) -> FakeSticker:
"""A static sticker with a PNG thumbnail, unless overridden."""
defaults: dict[str, Any] = {
"file_id": f"file{index}",
"file_unique_id": f"uniq{index}",
"thumbnail": FakeThumbnail(file_id=f"thumb{index}"),
}
defaults.update(overrides)
return FakeSticker(**defaults)
@pytest.fixture
def pack() -> FakeStickerSet:
return FakeStickerSet(
name="TestPack",
title="Test Pack",
stickers=[make_sticker(1), make_sticker(2)],
thumbnail=FakeThumbnail(file_id="packthumb"),
)
@pytest.fixture
def bot(pack: FakeStickerSet) -> FakeBot:
return FakeBot({pack.name: pack})
@pytest.fixture
def fetcher() -> FakeFetcher:
return FakeFetcher()
@pytest.fixture
def config(tmp_path) -> DownloadConfig:
return DownloadConfig(output_dir=tmp_path / "downloads")