Spaces:
Running
Running
Download tests/test_web_main.py from Mega-Evolutions/Auto-Rename: direct link, hf CLI and curl.
- Browser
- Download file 8.52 kB
-
https://huggingface.co/spaces/Mega-Evolutions/Auto-Rename/resolve/main/tests/test_web_main.py
- Command line
-
hf download hf://spaces/Mega-Evolutions/Auto-Rename/tests/test_web_main.py
-
curl -L -o test_web_main.py https://huggingface.co/spaces/Mega-Evolutions/Auto-Rename/resolve/main/tests/test_web_main.py
8.52 kB
| import asyncio | |
| import dataclasses | |
| import logging | |
| import socket | |
| from types import SimpleNamespace | |
| import aiohttp | |
| import pytest | |
| from aiohttp.test_utils import TestClient, TestServer | |
| from telethon import functions | |
| from telethon.crypto import AuthKey | |
| import bot.main as bot_main | |
| from bot import __version__ | |
| from bot.config import load_config | |
| from bot.db import Database | |
| from bot.errorlog import ChannelLogHandler | |
| from bot.web import make_app, start_web | |
| from .tgfakes import FakeClient | |
| def free_port() -> int: | |
| with socket.socket() as s: | |
| s.bind(("127.0.0.1", 0)) | |
| return s.getsockname()[1] | |
| # --------------------------------------------------------------------------- | |
| # health server | |
| # --------------------------------------------------------------------------- | |
| async def test_health_endpoints_answer_get_and_head(): | |
| async with TestClient(TestServer(make_app())) as client: | |
| r = await client.get("/") | |
| assert r.status == 200 and "running" in await r.text() | |
| assert (await client.head("/")).status == 200 # cron pingers often use HEAD | |
| r = await client.get("/health") | |
| body = await r.json() | |
| assert r.status == 200 and body["status"] == "ok" and body["version"] == __version__ | |
| assert body["uptime_seconds"] >= 0 | |
| assert (await client.get("/nope")).status == 404 | |
| async def test_start_web_listens_on_the_given_port(): | |
| port = free_port() | |
| runner = await start_web(port, host="127.0.0.1") | |
| try: | |
| async with aiohttp.ClientSession() as http: | |
| async with http.get(f"http://127.0.0.1:{port}/health") as r: | |
| assert r.status == 200 | |
| finally: | |
| await runner.cleanup() | |
| # --------------------------------------------------------------------------- | |
| # start-up helpers | |
| # --------------------------------------------------------------------------- | |
| def test_clean_temp_dir_removes_only_job_leftovers(tmp_path): | |
| root = tmp_path / "work" | |
| (root / "job1").mkdir(parents=True) | |
| (root / "job1" / "movie.mkv").write_bytes(b"1") | |
| (root / "tracks5_6").mkdir() | |
| (root / "tracks5_6" / "probe.mkv").write_bytes(b"2") | |
| (root / "other").mkdir() | |
| (root / "keep.txt").write_text("k") | |
| bot_main.clean_temp_dir(str(root)) | |
| assert sorted(p.name for p in root.iterdir()) == ["keep.txt", "other"] | |
| def test_clean_temp_dir_creates_a_missing_directory(tmp_path): | |
| bot_main.clean_temp_dir(str(tmp_path / "a" / "b")) | |
| assert (tmp_path / "a" / "b").is_dir() | |
| def test_main_exits_with_status_2_when_configuration_is_missing(monkeypatch, capsys): | |
| for key in ("API_ID", "API_HASH", "BOT_TOKEN", "OWNER_ID"): | |
| monkeypatch.delenv(key, raising=False) | |
| with pytest.raises(SystemExit) as exc: | |
| bot_main.main() | |
| assert exc.value.code == 2 | |
| assert "Missing required environment variables" in capsys.readouterr().err | |
| class FakeTG: | |
| created: list = [] | |
| def __init__(self, session, api_id, api_hash, **kw): | |
| self.session, self.api_id, self.api_hash, self.kw = session, api_id, api_hash, kw | |
| self.had_key = bool(session.auth_key) | |
| self.started_with = None | |
| FakeTG.created.append(self) | |
| async def start(self, bot_token=None): | |
| self.started_with = bot_token | |
| if not self.session.auth_key: # a fresh login creates an auth key | |
| self.session.set_dc(2, "149.154.167.51", 443) | |
| self.session.auth_key = AuthKey(b"\x07" * 256) | |
| return self | |
| async def login(tmp_path, monkeypatch): | |
| FakeTG.created = [] | |
| monkeypatch.setattr("telethon.TelegramClient", FakeTG) | |
| cfg = load_config({"API_ID": "11", "API_HASH": "hash", "BOT_TOKEN": "1234:abc", "OWNER_ID": "1"}) | |
| db = Database(f"sqlite+aiosqlite:///{tmp_path}/login.db") | |
| await db.init() | |
| yield SimpleNamespace(cfg=cfg, db=db) | |
| await db.close() | |
| async def test_first_login_saves_the_session_and_later_starts_reuse_it(login): | |
| first = await bot_main.build_client(login.cfg, login.db) | |
| assert not first.had_key and first.started_with == "1234:abc" and (first.api_id, first.api_hash) == (11, "hash") | |
| assert first.kw["connection_retries"] == -1 and first.kw["auto_reconnect"] is True | |
| assert await login.db.kv_get("session") and await login.db.kv_get("session_bot") == "1234" | |
| second = await bot_main.build_client(login.cfg, login.db) # e.g. after the Space restarted | |
| assert second.had_key and second.session.dc_id == 2 | |
| async def test_a_saved_session_of_another_bot_is_not_reused(login): | |
| await bot_main.build_client(login.cfg, login.db) | |
| other = dataclasses.replace(login.cfg, bot_token="9999:zzz") | |
| client = await bot_main.build_client(other, login.db) | |
| assert not client.had_key and await login.db.kv_get("session_bot") == "9999" | |
| async def test_session_string_setting_wins_over_the_database(login): | |
| saved = (await bot_main.build_client(login.cfg, login.db)).session.save() | |
| await login.db.kv_delete("session") | |
| await login.db.kv_delete("session_bot") | |
| cfg = dataclasses.replace(login.cfg, session_string=saved) | |
| assert (await bot_main.build_client(cfg, login.db)).had_key | |
| # --------------------------------------------------------------------------- | |
| # whole start-up sequence with a fake Telegram client | |
| # --------------------------------------------------------------------------- | |
| async def test_amain_wires_everything_and_exits_nonzero_if_telegram_disconnects(tmp_path, monkeypatch): | |
| port = free_port() | |
| env = { | |
| "API_ID": "1", "API_HASH": "h", "BOT_TOKEN": "1:t", "OWNER_ID": "1000001", "LOG_CHANNEL": "-1001111111111", | |
| "DATABASE_URL": f"sqlite+aiosqlite:///{tmp_path}/main.db", "TEMP_DIR": str(tmp_path / "work"), | |
| "PORT": str(port), "FAST_TRANSFER": "0", | |
| } | |
| for key, value in env.items(): | |
| monkeypatch.setenv(key, value) | |
| (tmp_path / "work" / "job9").mkdir(parents=True) # leftover of a "crashed" run | |
| gate = asyncio.Event() | |
| client = FakeClient() | |
| client.disconnected = False | |
| async def get_me(): | |
| return SimpleNamespace(username="rename_bot") | |
| async def run_until_disconnected(): | |
| await gate.wait() | |
| async def disconnect(): | |
| client.disconnected = True | |
| client.get_me, client.run_until_disconnected, client.disconnect = get_me, run_until_disconnected, disconnect | |
| async def fake_build_client(cfg, db): | |
| return client | |
| monkeypatch.setattr(bot_main, "build_client", fake_build_client) | |
| task = asyncio.ensure_future(bot_main.amain()) | |
| try: | |
| for _ in range(200): # wait for the "started" notice: everything is wired by then | |
| if any("Bot started" in text for _chat, text, _kw in client.sent): | |
| break | |
| await asyncio.sleep(0.05) | |
| else: | |
| pytest.fail("the bot never finished starting") | |
| async with aiohttp.ClientSession() as http: | |
| async with http.get(f"http://127.0.0.1:{port}/health") as r: | |
| assert r.status == 200 | |
| assert len(client.handlers) > 10 | |
| assert any(isinstance(r, functions.bots.SetBotCommandsRequest) for r in client.requests) | |
| assert not (tmp_path / "work" / "job9").exists() | |
| assert client.sent[0][0] == -1001111111111 | |
| started = client.sent[0][1] # the notice also says what the speed depends on (here: parallel transfer is off) | |
| assert "Bot started" in started and "⚡ cryptg ✅" in started and "parallel off ⚠️" in started | |
| assert any(isinstance(h, ChannelLogHandler) for h in logging.getLogger().handlers) | |
| logging.getLogger("bot.something").error("an error somewhere in the bot") # errors go to the log channel as well | |
| for _ in range(200): | |
| if any("an error somewhere in the bot" in text for _chat, text, _kw in client.sent): | |
| break | |
| await asyncio.sleep(0.05) | |
| else: | |
| pytest.fail("the error never reached the log channel") | |
| gate.set() # Telegram connection lost -> exit non-zero so the container restarts | |
| assert await asyncio.wait_for(task, 10) == 1 | |
| finally: | |
| gate.set() | |
| if not task.done(): | |
| task.cancel() | |
| assert client.disconnected | |
| assert not any(isinstance(h, ChannelLogHandler) for h in logging.getLogger().handlers) # detached again on the way out | |
| with pytest.raises(aiohttp.ClientError): | |
| async with aiohttp.ClientSession() as http: | |
| async with http.get(f"http://127.0.0.1:{port}/health"): | |
| pass | |