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": "typing", "selector": "#q"}, "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"), ({"type": "captcha", "selector": "img", "value": "image"}, "requires: target"), ({"type": "captcha", "selector": "#c", "value": "hcaptcha", "target": "#in"}, "only for action 'captcha'"), ({"type": "typing", "selector": "#q", "value": "x", "numeric": 1}, "only for action 'captcha'"), ({"type": "captcha", "selector": "img", "value": "image", "target": "#in", "numeric": 5}, "numeric"), ({"type": "captcha", "selector": "img", "value": "image", "target": "#in", "max_len": 21}, "max_len"), ({"type": "captcha", "selector": "img", "value": "image", "target": "#in", "min_len": 6, "max_len": 4}, "min_len must not exceed max_len"), ]) 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": "typing", "selector": "#q", "value": "Привет"}, {"type": "inner_text", "selector": "h1"}, {"type": "delay", "value": "0"}, {"type": "pass_challenge"}, {"type": "captcha", "selector": "#c", "value": "hcaptcha"}, {"type": "captcha", "selector": "img", "value": "image", "target": "#in"}, {"type": "captcha", "selector": "img", "value": "image", "target": "#in", "min_len": 4, "max_len": 6, "numeric": 1, "lang": "en", "case_sensitive": True}, {"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_user_agent_rejected(): with pytest.raises(ValidationError, match="user_agent is not supported"): solve(user_agent="Mozilla/5.0") 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"