158 lines
6.5 KiB
Python
158 lines
6.5 KiB
Python
"""test_server_resource_block.py — юниты для блокировки тяжёлых ресурсов (perf).
|
||
|
||
Проверяет:
|
||
1. _parse_bool — разбор BROWSER_BLOCK_RESOURCES в bool;
|
||
2. _apply_resource_block навешивает route когда флаг включён и НЕ навешивает когда
|
||
выключен (no-op);
|
||
3. сам route-handler аборт'ит image/media/font и continue'ит document/script/xhr;
|
||
4. сбой abort() деградирует в continue_() (fallback), не роняя навигацию.
|
||
|
||
camoufox/Playwright НЕ поднимается: route + request мокаются заглушками. Хендлер
|
||
вызывается напрямую через захваченный _apply_resource_block-callback.
|
||
|
||
Запуск (из tradein-mvp/browser/)::
|
||
|
||
python -m pytest test_server_resource_block.py -q
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import asyncio
|
||
import importlib.util
|
||
from pathlib import Path
|
||
from typing import Any
|
||
|
||
import pytest
|
||
|
||
# server.py — не пакет (отдельный сервис без __init__/pyproject). Грузим по пути.
|
||
_SERVER_PATH = Path(__file__).resolve().parent / "server.py"
|
||
_spec = importlib.util.spec_from_file_location("tradein_browser_server", _SERVER_PATH)
|
||
assert _spec is not None and _spec.loader is not None
|
||
server = importlib.util.module_from_spec(_spec)
|
||
_spec.loader.exec_module(server)
|
||
|
||
|
||
# ── _parse_bool ──────────────────────────────────────────────────────────────────
|
||
|
||
|
||
def test_parse_bool_truthy() -> None:
|
||
for raw in ("true", "True", "1", "yes", "on", " TRUE "):
|
||
assert server._parse_bool(raw, False) is True, raw
|
||
|
||
|
||
def test_parse_bool_falsy() -> None:
|
||
for raw in ("false", "0", "no", "off", "garbage", ""):
|
||
assert server._parse_bool(raw, True) is False, raw
|
||
|
||
|
||
def test_parse_bool_none_uses_default() -> None:
|
||
assert server._parse_bool(None, True) is True
|
||
assert server._parse_bool(None, False) is False
|
||
|
||
|
||
def test_default_block_resources_on() -> None:
|
||
"""Дефолт: блокировка включена, типы — image/media/font."""
|
||
assert server._BLOCK_RESOURCES is True
|
||
assert server._BLOCKED_TYPES == frozenset({"image", "media", "font"})
|
||
|
||
|
||
# ── route-interception заглушки ──────────────────────────────────────────────────
|
||
|
||
|
||
class _FakeRequest:
|
||
def __init__(self, resource_type: str) -> None:
|
||
self.resource_type = resource_type
|
||
|
||
|
||
class _FakeRoute:
|
||
"""Поддельный Playwright Route: считает abort/continue, мокает request."""
|
||
|
||
def __init__(self, resource_type: str, fail_abort: bool = False) -> None:
|
||
self.request = _FakeRequest(resource_type)
|
||
self.aborted = 0
|
||
self.continued = 0
|
||
self._fail_abort = fail_abort
|
||
|
||
async def abort(self) -> None:
|
||
if self._fail_abort:
|
||
raise RuntimeError("route already handled")
|
||
self.aborted += 1
|
||
|
||
async def continue_(self) -> None:
|
||
self.continued += 1
|
||
|
||
|
||
class _FakePage:
|
||
"""Поддельная page: захватывает callback из page.route(pattern, handler)."""
|
||
|
||
def __init__(self) -> None:
|
||
self.route_pattern: str | None = None
|
||
self.route_handler: Any = None
|
||
|
||
async def route(self, pattern: str, handler: Any) -> None:
|
||
self.route_pattern = pattern
|
||
self.route_handler = handler
|
||
|
||
|
||
def _capture_handler(monkeypatch: pytest.MonkeyPatch, *, enabled: bool, blocked: set[str]) -> Any:
|
||
"""Включает флаг/типы, прогоняет _apply_resource_block, возвращает page (с handler)."""
|
||
monkeypatch.setattr(server, "_BLOCK_RESOURCES", enabled)
|
||
monkeypatch.setattr(server, "_BLOCKED_TYPES", frozenset(blocked))
|
||
page = _FakePage()
|
||
asyncio.run(server._apply_resource_block(page))
|
||
return page
|
||
|
||
|
||
def test_apply_resource_block_noop_when_disabled(monkeypatch: pytest.MonkeyPatch) -> None:
|
||
"""BROWSER_BLOCK_RESOURCES=false → route НЕ навешивается."""
|
||
page = _capture_handler(monkeypatch, enabled=False, blocked={"image"})
|
||
assert page.route_handler is None
|
||
assert page.route_pattern is None
|
||
|
||
|
||
def test_apply_resource_block_registers_route_when_enabled(
|
||
monkeypatch: pytest.MonkeyPatch,
|
||
) -> None:
|
||
"""Флаг включён → route на '**/*' с хендлером."""
|
||
page = _capture_handler(monkeypatch, enabled=True, blocked={"image", "media", "font"})
|
||
assert page.route_pattern == "**/*"
|
||
assert callable(page.route_handler)
|
||
|
||
|
||
def test_handler_aborts_image(monkeypatch: pytest.MonkeyPatch) -> None:
|
||
"""image (в _BLOCKED_TYPES) → abort, без continue."""
|
||
page = _capture_handler(monkeypatch, enabled=True, blocked={"image", "media", "font"})
|
||
route = _FakeRoute("image")
|
||
asyncio.run(page.route_handler(route))
|
||
assert route.aborted == 1
|
||
assert route.continued == 0
|
||
|
||
|
||
def test_handler_aborts_media_and_font(monkeypatch: pytest.MonkeyPatch) -> None:
|
||
page = _capture_handler(monkeypatch, enabled=True, blocked={"image", "media", "font"})
|
||
for rtype in ("media", "font"):
|
||
route = _FakeRoute(rtype)
|
||
asyncio.run(page.route_handler(route))
|
||
assert route.aborted == 1, rtype
|
||
assert route.continued == 0, rtype
|
||
|
||
|
||
def test_handler_continues_document_and_script(monkeypatch: pytest.MonkeyPatch) -> None:
|
||
"""document/script/stylesheet/xhr/fetch — НЕ блокируются (нужны для гидрации/stealth)."""
|
||
page = _capture_handler(monkeypatch, enabled=True, blocked={"image", "media", "font"})
|
||
for rtype in ("document", "script", "stylesheet", "xhr", "fetch"):
|
||
route = _FakeRoute(rtype)
|
||
asyncio.run(page.route_handler(route))
|
||
assert route.aborted == 0, rtype
|
||
assert route.continued == 1, rtype
|
||
|
||
|
||
def test_handler_falls_back_to_continue_on_abort_error(
|
||
monkeypatch: pytest.MonkeyPatch,
|
||
) -> None:
|
||
"""abort() кинул (route уже обработан) → fallback continue_(), навигация не падает."""
|
||
page = _capture_handler(monkeypatch, enabled=True, blocked={"image"})
|
||
route = _FakeRoute("image", fail_abort=True)
|
||
# Не должно бросить наружу.
|
||
asyncio.run(page.route_handler(route))
|
||
assert route.continued == 1, "после сбоя abort должен быть fallback-continue"
|