feat(08-03): create CrawlerConfig for proxy and rate limiting
- Add CrawlerConfig with ProxyConfig, delay, and retry settings - Support HTTP_PROXY/HTTPS_PROXY environment variable parsing - Add proxy_required validation with clear error message - Add get_crawler_config singleton for configuration access Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
"""Tests for Crawler Proxy Configuration"""
|
||||
|
||||
import os
|
||||
import pytest
|
||||
from unittest.mock import patch
|
||||
|
||||
from kb_workflow.crawler.config import (
|
||||
CrawlerConfig,
|
||||
ProxyConfig,
|
||||
get_crawler_config,
|
||||
reset_crawler_config,
|
||||
)
|
||||
|
||||
|
||||
class TestProxyConfig:
|
||||
"""Tests for ProxyConfig model."""
|
||||
|
||||
def test_parse_proxy_from_https_env(self):
|
||||
"""Parse proxy from HTTPS_PROXY environment variable."""
|
||||
reset_crawler_config()
|
||||
with patch.dict(os.environ, {"HTTPS_PROXY": "http://127.0.0.1:7890"}, clear=False):
|
||||
os.environ.pop("HTTP_PROXY", None)
|
||||
config = CrawlerConfig.from_env()
|
||||
assert config.proxy is not None
|
||||
assert config.proxy.server == "http://127.0.0.1:7890"
|
||||
|
||||
def test_parse_proxy_from_http_env(self):
|
||||
"""Parse proxy from HTTP_PROXY (HTTPS_PROXY takes priority)."""
|
||||
reset_crawler_config()
|
||||
with patch.dict(os.environ, {
|
||||
"HTTP_PROXY": "http://127.0.0.1:8080",
|
||||
"HTTPS_PROXY": "http://127.0.0.1:7890"
|
||||
}, clear=False):
|
||||
config = CrawlerConfig.from_env()
|
||||
assert config.proxy.server == "http://127.0.0.1:7890"
|
||||
|
||||
def test_parse_proxy_with_auth(self):
|
||||
"""Parse proxy with authentication credentials."""
|
||||
reset_crawler_config()
|
||||
with patch.dict(os.environ, {
|
||||
"HTTPS_PROXY": "http://user:pass@proxy.example.com:8080"
|
||||
}, clear=False):
|
||||
os.environ.pop("HTTP_PROXY", None)
|
||||
config = CrawlerConfig.from_env()
|
||||
assert config.proxy.username == "user"
|
||||
assert config.proxy.password == "pass"
|
||||
assert config.proxy.server == "http://proxy.example.com:8080"
|
||||
|
||||
def test_no_proxy_configured(self):
|
||||
"""Handle case when no proxy is configured."""
|
||||
reset_crawler_config()
|
||||
# Save current values
|
||||
old_http = os.environ.get("HTTP_PROXY")
|
||||
old_https = os.environ.get("HTTPS_PROXY")
|
||||
|
||||
try:
|
||||
os.environ.pop("HTTP_PROXY", None)
|
||||
os.environ.pop("HTTPS_PROXY", None)
|
||||
config = CrawlerConfig.from_env()
|
||||
assert config.proxy is None
|
||||
finally:
|
||||
# Restore
|
||||
if old_http:
|
||||
os.environ["HTTP_PROXY"] = old_http
|
||||
if old_https:
|
||||
os.environ["HTTPS_PROXY"] = old_https
|
||||
|
||||
def test_proxy_required_validation_fails(self):
|
||||
"""Raise ValueError when proxy required but not configured."""
|
||||
config = CrawlerConfig(proxy=None, proxy_required=True)
|
||||
with pytest.raises(ValueError, match="Proxy configuration missing"):
|
||||
config.validate_proxy()
|
||||
|
||||
def test_proxy_required_validation_passes(self):
|
||||
"""Pass validation when proxy is configured and required."""
|
||||
config = CrawlerConfig(
|
||||
proxy=ProxyConfig(server="http://127.0.0.1:7890"),
|
||||
proxy_required=True
|
||||
)
|
||||
# Should not raise exception
|
||||
config.validate_proxy()
|
||||
|
||||
def test_proxy_not_required(self):
|
||||
"""Pass validation when proxy is not required."""
|
||||
config = CrawlerConfig(proxy=None, proxy_required=False)
|
||||
# Should not raise exception
|
||||
config.validate_proxy()
|
||||
|
||||
def test_to_playwright_format(self):
|
||||
"""Convert to Playwright proxy format."""
|
||||
proxy = ProxyConfig(
|
||||
server="http://127.0.0.1:7890",
|
||||
username="user",
|
||||
password="pass"
|
||||
)
|
||||
result = proxy.to_playwright_format()
|
||||
assert result == {
|
||||
"server": "http://127.0.0.1:7890",
|
||||
"username": "user",
|
||||
"password": "pass"
|
||||
}
|
||||
|
||||
def test_to_playwright_format_no_auth(self):
|
||||
"""Convert to Playwright format without authentication."""
|
||||
proxy = ProxyConfig(server="http://127.0.0.1:7890")
|
||||
result = proxy.to_playwright_format()
|
||||
assert result == {"server": "http://127.0.0.1:7890"}
|
||||
|
||||
def test_get_crawler_config_singleton(self):
|
||||
"""Test that get_crawler_config returns cached instance."""
|
||||
reset_crawler_config()
|
||||
with patch.dict(os.environ, {"HTTPS_PROXY": "http://127.0.0.1:7890"}, clear=False):
|
||||
os.environ.pop("HTTP_PROXY", None)
|
||||
config1 = get_crawler_config()
|
||||
config2 = get_crawler_config()
|
||||
assert config1 is config2
|
||||
|
||||
def test_default_values(self):
|
||||
"""Test default configuration values."""
|
||||
config = CrawlerConfig(proxy=None, proxy_required=False)
|
||||
assert config.min_delay == 1.0
|
||||
assert config.max_delay == 3.0
|
||||
assert config.max_retries == 3
|
||||
assert config.retry_min_wait == 2.0
|
||||
assert config.retry_max_wait == 10.0
|
||||
Reference in New Issue
Block a user