""" Tests for Proxy Configuration API Router Covers: GET/POST /api/proxy/config, POST /api/proxy/test """ from pathlib import Path from unittest.mock import AsyncMock, MagicMock, patch import pytest from fastapi import FastAPI from fastapi.testclient import TestClient from api_utils.routers.proxy import ( ProxyConfig, ProxyTestRequest, ProxyTestResult, _load_config, _save_config, router, ) @pytest.fixture def app() -> FastAPI: """Create test FastAPI app with proxy router.""" app = FastAPI() app.include_router(router) return app @pytest.fixture def client(app: FastAPI) -> TestClient: """Create test client.""" return TestClient(app) @pytest.fixture def mock_config_file(tmp_path: Path) -> Path: """Create a temporary config file path.""" return tmp_path / "proxy_config.json" class TestProxyConfig: """Tests for proxy configuration endpoints.""" def test_proxy_config_model_validation(self) -> None: """Test ProxyConfig validation.""" config = ProxyConfig(enabled=True, address="http://127.0.0.1:7890") assert config.enabled is True assert config.address == "http://127.0.0.1:7890" def test_proxy_config_invalid_address(self) -> None: """Test ProxyConfig rejects invalid addresses.""" with pytest.raises(ValueError, match="http://"): ProxyConfig(enabled=True, address="invalid") def test_proxy_config_empty_address_allowed(self) -> None: """Test ProxyConfig allows empty address.""" config = ProxyConfig(enabled=False, address="") assert config.address == "" def test_get_proxy_config(self, client: TestClient) -> None: """Test GET /api/proxy/config returns config.""" with patch( "api_utils.routers.proxy._load_config", return_value=ProxyConfig(enabled=True, address="http://test:8080"), ): response = client.get("/api/proxy/config") assert response.status_code == 200 data = response.json() assert data["enabled"] is True assert data["address"] == "http://test:8080" def test_update_proxy_config(self, client: TestClient) -> None: """Test POST /api/proxy/config updates config.""" with patch("api_utils.routers.proxy._save_config") as mock_save: response = client.post( "/api/proxy/config", json={"enabled": True, "address": "http://new:8080"}, ) assert response.status_code == 200 data = response.json() assert data["success"] is True assert data["config"]["enabled"] is True mock_save.assert_called_once() def test_update_proxy_config_validation_error(self, client: TestClient) -> None: """Test POST /api/proxy/config rejects invalid data.""" response = client.post( "/api/proxy/config", json={"enabled": True, "address": "invalid"}, ) assert response.status_code == 422 # Validation error class TestProxyConnectivityTest: """Tests for proxy connectivity test endpoint.""" def test_proxy_test_request_model(self) -> None: """Test ProxyTestRequest model.""" req = ProxyTestRequest(address="http://proxy:8080") assert req.address == "http://proxy:8080" assert req.test_url == "http://httpbin.org/get" def test_proxy_test_result_model(self) -> None: """Test ProxyTestResult model.""" result = ProxyTestResult(success=True, message="OK", latency_ms=123.45) assert result.success is True assert result.latency_ms == 123.45 def test_proxy_test_empty_address(self, client: TestClient) -> None: """Test POST /api/proxy/test with empty address returns error.""" response = client.post("/api/proxy/test", json={"address": ""}) assert response.status_code == 400 data = response.json() assert data["success"] is False @pytest.mark.asyncio async def test_proxy_test_success(self, client: TestClient) -> None: """Test POST /api/proxy/test with successful connection.""" mock_response = MagicMock() mock_response.status_code = 200 with patch("httpx.AsyncClient") as mock_client: mock_instance = AsyncMock() mock_instance.get.return_value = mock_response mock_instance.__aenter__.return_value = mock_instance mock_instance.__aexit__.return_value = None mock_client.return_value = mock_instance response = client.post( "/api/proxy/test", json={"address": "http://proxy:8080", "test_url": "http://test.com"}, ) # Note: Due to async context, this test may need adjustment # The actual response depends on httpx behavior assert response.status_code in (200, 500) # Either success or error class TestConfigPersistence: """Tests for config file operations.""" def test_load_config_default(self, tmp_path: Path) -> None: """Test _load_config returns default when file missing.""" with patch( "api_utils.routers.proxy._PROXY_CONFIG_FILE", tmp_path / "nonexistent.json" ): config = _load_config() assert config.enabled is False assert config.address == "http://127.0.0.1:7890" def test_save_and_load_config(self, tmp_path: Path) -> None: """Test config can be saved and loaded.""" config_file = tmp_path / "proxy_config.json" with patch("api_utils.routers.proxy._PROXY_CONFIG_FILE", config_file): original = ProxyConfig(enabled=True, address="http://saved:9999") _save_config(original) loaded = _load_config() assert loaded.enabled is True assert loaded.address == "http://saved:9999"