Files
robotex/tests/unit/test_schemas.py
T
protokeyandClaude Opus 5.5 0ae5b0c83f test: юнит-тесты и сквозные тесты с Chrome (#21)
- tests/unit (make test, без браузера, ~5 с): схемы и валидация, /v1/solve с подменённым
  движком (коды ошибок, слоты, сессии, UA и cookies профиля, маскирование секретов в логах),
  действия, оркестратор антибота, профиль и отпечаток
- tests/browser (make test-browser, в Docker): example.com, UA один раз и замена cookies
  на httpbin, применение и стабильность отпечатка, прохождение Cloudflare
- requirements-dev.txt, make sync-dev; Makefile вызывает pip через python -m pip
  (у перенесённого .venv битый shebang bin/pip)
- fix: в CloudflareSolver вызов bounding_box() у неожиданной корутины — ветка без iframe
  всегда падала с AttributeError

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 18:10:21 +04:00

76 lines
2.4 KiB
Python

import pytest
from pydantic import ValidationError
from api.schemas import ActionRequest, SolveRequest, WAIT_FOR_DEFAULT_TIMEOUT
def solve(**kwargs) -> SolveRequest:
return SolveRequest(**{"url": "https://example.com", "actions": [], **kwargs})
@pytest.mark.parametrize("action, error", [
({"type": "nope"}, "unknown action type"),
({"type": "wait_for"}, "requires: selector"),
({"type": "fill", "selector": "#q"}, "requires: value"),
({"type": "fill", "selector": "#q", "value": ""}, "requires: value"),
({"type": "click"}, "requires: selector"),
({"type": "delay"}, "requires: value"),
({"type": "delay", "value": "abc"}, "integer"),
({"type": "delay", "value": "601"}, "between 0 and 600"),
({"type": "wait_for", "selector": "a", "timeout": 0}, "between 1 and 7200"),
({"type": "wait_for", "selector": "a", "timeout": 7201}, "between 1 and 7200"),
({"type": "captcha", "selector": "#c", "value": "datadome"}, "captcha value must be one of"),
])
def test_invalid_action(action, error):
with pytest.raises(ValidationError, match=error):
ActionRequest(**action)
@pytest.mark.parametrize("action", [
{"type": "wait_for", "selector": "a", "timeout": 10},
{"type": "fill", "selector": "#q", "value": "x"},
{"type": "delay", "value": "0"},
{"type": "pass_challenge"},
{"type": "captcha", "selector": "#c", "value": "hcaptcha"},
{"type": "inner_html", "selector": "h1"},
])
def test_valid_action(action):
assert ActionRequest(**action).type == action["type"]
def test_wait_for_default_timeout():
assert ActionRequest(type="wait_for", selector="a").timeout == WAIT_FOR_DEFAULT_TIMEOUT
@pytest.mark.parametrize("proxy", [
"http://host:8080",
"https://user:pass@host:443",
"socks5://10.0.0.1:1080",
])
def test_valid_proxy(proxy):
assert solve(proxy=proxy).proxy == proxy
@pytest.mark.parametrize("proxy, error", [
("host:8080", "scheme"),
("ftp://host:21", "scheme"),
("http://host", "host:port"),
("http://:8080", "host:port"),
("http://host:99999", "invalid proxy url"),
])
def test_invalid_proxy(proxy, error):
with pytest.raises(ValidationError, match=error):
solve(proxy=proxy)
def test_proxy_optional():
assert solve().proxy is None
assert solve(proxy="").proxy is None
def test_captcha_action_requires_key():
action = {"type": "captcha", "selector": "#c", "value": "recaptcha"}
with pytest.raises(ValidationError, match="requires request field 'captcha'"):
solve(actions=[action])
assert solve(actions=[action], captcha="key").captcha == "key"