mirror of
https://github.com/feder-cr/invisible_playwright.git
synced 2026-06-07 08:35:12 +02:00
Covers every input partition: None/empty/direct, SOCKS4/5/default, HTTP/HTTPS passthrough, case-insensitive scheme detection, malformed inputs, mutation contract, and edge cases (IPv6 brackets, whitespace, non-numeric ports). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
266 lines
10 KiB
Python
266 lines
10 KiB
Python
"""Unit tests for `invisible_playwright._proxy.configure_proxy`.
|
|
|
|
Decision-table coverage of every input partition: None/empty/direct,
|
|
SOCKS4/5/default, HTTP/HTTPS, case variants, malformed, mutation contract.
|
|
"""
|
|
import pytest
|
|
|
|
from invisible_playwright._proxy import configure_proxy
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# CP1-CP7: no-op cases — return None, do NOT mutate prefs
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_cp1_none_proxy_returns_none():
|
|
prefs = {}
|
|
assert configure_proxy(None, prefs) is None
|
|
assert prefs == {}
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_cp2_empty_dict_returns_none():
|
|
prefs = {}
|
|
assert configure_proxy({}, prefs) is None
|
|
assert prefs == {}
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_cp3_empty_server_returns_none():
|
|
prefs = {}
|
|
assert configure_proxy({"server": ""}, prefs) is None
|
|
assert prefs == {}
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_cp4_whitespace_server_returns_none():
|
|
prefs = {}
|
|
assert configure_proxy({"server": " "}, prefs) is None
|
|
assert prefs == {}
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_cp5_direct_scheme_returns_none():
|
|
prefs = {}
|
|
assert configure_proxy({"server": "direct://"}, prefs) is None
|
|
assert prefs == {}
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_cp6_direct_scheme_uppercase_returns_none():
|
|
prefs = {}
|
|
assert configure_proxy({"server": "DIRECT://"}, prefs) is None
|
|
assert prefs == {}
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_cp7_direct_scheme_mixed_case_returns_none():
|
|
prefs = {}
|
|
assert configure_proxy({"server": "DiReCt://"}, prefs) is None
|
|
assert prefs == {}
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# CP8-CP9: HTTP/HTTPS — passthrough (return proxy unchanged, no mutation)
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_cp8_http_proxy_passthrough():
|
|
prefs = {}
|
|
proxy = {"server": "http://proxy:8080"}
|
|
result = configure_proxy(proxy, prefs)
|
|
assert result == proxy
|
|
# No SOCKS-related mutations.
|
|
assert "network.proxy.type" not in prefs
|
|
assert "network.proxy.socks" not in prefs
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_cp9_https_proxy_passthrough():
|
|
prefs = {}
|
|
proxy = {"server": "https://proxy:8080"}
|
|
result = configure_proxy(proxy, prefs)
|
|
assert result == proxy
|
|
assert "network.proxy.type" not in prefs
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_cp8b_http_with_username_password_passthrough():
|
|
"""HTTP proxies preserve username/password for Playwright to consume."""
|
|
prefs = {}
|
|
proxy = {"server": "http://proxy:8080", "username": "user", "password": "pw"}
|
|
result = configure_proxy(proxy, prefs)
|
|
assert result == proxy
|
|
assert "network.proxy.type" not in prefs
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# CP10-CP13: SOCKS — mutate prefs, return None
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_cp10_socks5_with_credentials():
|
|
prefs = {}
|
|
proxy = {
|
|
"server": "socks5://host:1080",
|
|
"username": "u",
|
|
"password": "p",
|
|
}
|
|
result = configure_proxy(proxy, prefs)
|
|
assert result is None
|
|
assert prefs["network.proxy.type"] == 1
|
|
assert prefs["network.proxy.socks"] == "host"
|
|
assert prefs["network.proxy.socks_port"] == 1080
|
|
assert prefs["network.proxy.socks_version"] == 5
|
|
assert prefs["network.proxy.socks_username"] == "u"
|
|
assert prefs["network.proxy.socks_password"] == "p"
|
|
assert prefs["network.proxy.socks_remote_dns"] is True
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_cp11_socks4_sets_version_4():
|
|
prefs = {}
|
|
configure_proxy({"server": "socks4://host:1080"}, prefs)
|
|
assert prefs["network.proxy.socks_version"] == 4
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_cp12_bare_socks_defaults_to_v5():
|
|
prefs = {}
|
|
configure_proxy({"server": "socks://host:1080"}, prefs)
|
|
assert prefs["network.proxy.socks_version"] == 5
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_cp13_socks_scheme_is_case_insensitive():
|
|
prefs = {}
|
|
proxy = {"server": "SOCKS5://HOST:1080"}
|
|
result = configure_proxy(proxy, prefs)
|
|
assert result is None
|
|
assert prefs["network.proxy.type"] == 1
|
|
# Host preserves case (only the scheme is case-folded).
|
|
assert prefs["network.proxy.socks"] == "HOST"
|
|
assert prefs["network.proxy.socks_version"] == 5
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# CP14-CP15: edge SOCKS inputs
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_cp14_socks_without_port_dropped_silently():
|
|
prefs = {}
|
|
result = configure_proxy({"server": "socks5://hostonly"}, prefs)
|
|
assert result is None
|
|
# Malformed input drops silently — no mutations.
|
|
assert "network.proxy.type" not in prefs
|
|
assert "network.proxy.socks" not in prefs
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_cp15_socks_without_credentials_uses_empty_strings():
|
|
prefs = {}
|
|
configure_proxy({"server": "socks5://host:1080"}, prefs)
|
|
assert prefs["network.proxy.socks_username"] == ""
|
|
assert prefs["network.proxy.socks_password"] == ""
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_cp15b_socks_with_none_credentials_uses_empty_strings():
|
|
"""`proxy.get("username")` returning None should resolve to ""."""
|
|
prefs = {}
|
|
configure_proxy(
|
|
{"server": "socks5://host:1080", "username": None, "password": None},
|
|
prefs,
|
|
)
|
|
assert prefs["network.proxy.socks_username"] == ""
|
|
assert prefs["network.proxy.socks_password"] == ""
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# CP16: mutation contract — prefs dict mutated in-place
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_cp16_prefs_mutated_in_place():
|
|
"""Caller's prefs dict receives the SOCKS keys directly (not a copy)."""
|
|
prefs = {"existing.pref": "kept"}
|
|
sentinel = prefs
|
|
configure_proxy({"server": "socks5://host:1080"}, prefs)
|
|
# Same object identity — mutated, not replaced.
|
|
assert prefs is sentinel
|
|
# Existing pref preserved.
|
|
assert prefs["existing.pref"] == "kept"
|
|
# SOCKS keys added.
|
|
assert "network.proxy.type" in prefs
|
|
assert "network.proxy.socks" in prefs
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# CP17: boundary — IPv6-style host preserved via rsplit
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_cp17_ipv6_bracketed_host_preserved_via_rsplit():
|
|
"""rsplit(':', 1) keeps brackets intact for `[::1]:1080`-style hosts."""
|
|
prefs = {}
|
|
configure_proxy({"server": "socks5://[::1]:1080"}, prefs)
|
|
assert prefs["network.proxy.socks"] == "[::1]"
|
|
assert prefs["network.proxy.socks_port"] == 1080
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# Recheck additions — branches discovered while re-reading _proxy.py
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_socks_with_surrounding_whitespace_in_server_stripped():
|
|
"""The implementation strips whitespace before scheme checks."""
|
|
prefs = {}
|
|
result = configure_proxy({"server": " socks5://host:1080 "}, prefs)
|
|
assert result is None
|
|
assert prefs["network.proxy.socks"] == "host"
|
|
assert prefs["network.proxy.socks_port"] == 1080
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_server_key_missing_returns_none():
|
|
"""No 'server' key → treated as empty → no-op."""
|
|
prefs = {}
|
|
result = configure_proxy({"username": "u"}, prefs)
|
|
assert result is None
|
|
assert prefs == {}
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_server_key_none_returns_none():
|
|
"""`server: None` is normalized to "" by the implementation."""
|
|
prefs = {}
|
|
result = configure_proxy({"server": None}, prefs)
|
|
assert result is None
|
|
assert prefs == {}
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_socks_port_coerced_to_int():
|
|
"""Port string is parsed via int() — not a numeric string."""
|
|
prefs = {}
|
|
configure_proxy({"server": "socks5://host:443"}, prefs)
|
|
assert prefs["network.proxy.socks_port"] == 443
|
|
assert isinstance(prefs["network.proxy.socks_port"], int)
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_socks_non_numeric_port_raises_value_error():
|
|
"""Non-numeric port is a programmer error — int() raises."""
|
|
prefs = {}
|
|
with pytest.raises(ValueError):
|
|
configure_proxy({"server": "socks5://host:notaport"}, prefs)
|