gendesign/tradein-mvp/browser/test_server_resource_block.py
bot-backend 1533baad97
All checks were successful
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI / changes (pull_request) Successful in 6s
perf(browser): block image/media/font resources in camoufox fetch (~10x smaller pages, faster via proxy)
2026-06-18 18:16:53 +03:00

158 lines
6.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""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"