""" Tests unitarios del endpoint /visit con mocks para no depender de Camoufox. Importamos app.main dentro del contexto del patch para asegurar que todos los módulos se carguen con AsyncCamoufox mockeado. """ from contextlib import contextmanager from unittest.mock import AsyncMock, patch @contextmanager def mocked_client(mock_response=None): """Context manager que crea un TestClient con AsyncCamoufox mockeado. Importa app.main dentro del contexto del patch.""" if mock_response is None: mock_response = {"html": "Example", "screenshot": None} with patch("app.use_cases.camoufox.wrapper.AsyncCamoufox") as MockClass: mock_browser = AsyncMock() mock_page = AsyncMock() mock_page.content = AsyncMock(return_value=mock_response["html"]) mock_page.screenshot = AsyncMock( return_value=b"fake_screenshot_bytes" if mock_response.get("screenshot") else None ) mock_page.set_extra_http_headers = AsyncMock() mock_page.goto = AsyncMock() mock_page.wait_for_load_state = AsyncMock() mock_page.set_default_navigation_timeout = AsyncMock() mock_browser.new_page = AsyncMock(return_value=mock_page) MockClass.return_value.__aenter__ = AsyncMock(return_value=mock_browser) MockClass.return_value.__aexit__ = AsyncMock(return_value=False) # Importar app DENTRO del patch para que se cargue con AsyncCamoufox mockeado from app.main import app from fastapi.testclient import TestClient yield TestClient(app) def test_visit_missing_url(): from app.main import app from fastapi.testclient import TestClient client = TestClient(app) response = client.post("/api/v1/visit", json={}) assert response.status_code == 422 class TestVisitEndpoint: """Tests que mockean AsyncCamoufox para evitar llamadas reales al browser.""" def test_visit_invalid_url(self): with mocked_client() as client: response = client.post("/api/v1/visit", json={"url": "not-a-url"}) assert response.status_code == 422 def test_visit_valid_url(self): with mocked_client() as client: response = client.post( "/api/v1/visit", json={"url": "https://example.com", "screenshot": False}, ) assert response.status_code == 200 body = response.json() assert body["status"] == "OK" assert "html" in body["data"] def test_visit_with_screenshot(self): with mocked_client( {"html": "Example", "screenshot": "base64data"} ) as client: response = client.post( "/api/v1/visit", json={"url": "https://example.com", "screenshot": True}, ) assert response.status_code == 200 body = response.json() assert body["status"] == "OK" assert "screenshot" in body["data"] def test_visit_with_invalid_waituntil(self): with mocked_client() as client: response = client.post( "/api/v1/visit", json={"url": "https://example.com", "waitUntil": "invalid"}, ) assert response.status_code == 422 def test_visit_with_valid_waituntil(self): with mocked_client() as client: for wait_until in ["load", "domcontentloaded", "networkidle", "commit"]: response = client.post( "/api/v1/visit", json={"url": "https://example.com", "waitUntil": wait_until}, ) assert response.status_code == 200 def test_visit_response_format(self): with mocked_client() as client: response = client.post( "/api/v1/visit", json={"url": "https://example.com"}, ) body = response.json() assert "status" in body assert "message" in body assert "data" in body assert "code" in body assert body["code"] == "UNKNOWN_CODE" def test_visit_with_wait_time_valid(self): with mocked_client() as client: response = client.post( "/api/v1/visit", json={"url": "https://example.com", "waitTime": 10}, ) assert response.status_code == 200 def test_visit_with_wait_time_invalid_negative(self): with mocked_client() as client: response = client.post( "/api/v1/visit", json={"url": "https://example.com", "waitTime": -5}, ) assert response.status_code == 422 def test_visit_with_wait_time_invalid_above_max(self): with mocked_client() as client: response = client.post( "/api/v1/visit", json={"url": "https://example.com", "waitTime": 120}, ) assert response.status_code == 422 class TestVisitEndpointController: """Tests que verifican el controller directamente con VisitUseCase mockeado.""" def test_controller_visit_uses_visit_use_case(self): """Test que cubre la llamada a use_case.execute.""" from app.controllers.api_controller import ApiController from app.models.schemas import VisitRequest from fastapi import Request from unittest.mock import MagicMock controller = ApiController() mock_request = MagicMock(spec=Request) mock_request.scope = {"raw_headers_dict": {"user-agent": "test"}} with patch.object(controller, "visit_use_case") as mock_use_case: async def async_execute(*args, **kwargs): return {"html": "Controller", "screenshot": None} mock_use_case.execute = async_execute import asyncio result = asyncio.run( controller.visit(mock_request, VisitRequest(url="https://example.com")) ) assert result["status"] == "OK" assert result["data"]["html"] == "Controller" def test_controller_visit_error_handling(self): """Test que cubre el manejo de errores.""" from app.controllers.api_controller import ApiController from app.models.schemas import VisitRequest from fastapi import Request from unittest.mock import MagicMock controller = ApiController() mock_request = MagicMock(spec=Request) mock_request.scope = {"raw_headers_dict": {"user-agent": "test"}} with patch.object(controller, "visit_use_case") as mock_use_case: async def async_execute(*args, **kwargs): raise Exception("Test error") mock_use_case.execute = async_execute import asyncio result = asyncio.run( controller.visit(mock_request, VisitRequest(url="https://example.com")) ) assert result["status"] == "NO_OK" assert "Test error" in result["message"]