test_api_controller.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. """
  2. Tests unitarios del endpoint /visit con mocks para no depender de Camoufox.
  3. Importamos app.main dentro del contexto del patch para asegurar que todos
  4. los módulos se carguen con AsyncCamoufox mockeado.
  5. """
  6. from contextlib import contextmanager
  7. from unittest.mock import AsyncMock, patch
  8. @contextmanager
  9. def mocked_client(mock_response=None):
  10. """Context manager que crea un TestClient con AsyncCamoufox mockeado.
  11. Importa app.main dentro del contexto del patch."""
  12. if mock_response is None:
  13. mock_response = {"html": "<html>Example</html>", "screenshot": None}
  14. with patch("app.use_cases.camoufox.wrapper.AsyncCamoufox") as MockClass:
  15. mock_browser = AsyncMock()
  16. mock_page = AsyncMock()
  17. mock_page.content = AsyncMock(return_value=mock_response["html"])
  18. mock_page.screenshot = AsyncMock(
  19. return_value=b"fake_screenshot_bytes" if mock_response.get("screenshot") else None
  20. )
  21. mock_page.set_extra_http_headers = AsyncMock()
  22. mock_page.goto = AsyncMock()
  23. mock_page.wait_for_load_state = AsyncMock()
  24. mock_page.set_default_navigation_timeout = AsyncMock()
  25. mock_browser.new_page = AsyncMock(return_value=mock_page)
  26. MockClass.return_value.__aenter__ = AsyncMock(return_value=mock_browser)
  27. MockClass.return_value.__aexit__ = AsyncMock(return_value=False)
  28. # Importar app DENTRO del patch para que se cargue con AsyncCamoufox mockeado
  29. from app.main import app
  30. from fastapi.testclient import TestClient
  31. yield TestClient(app)
  32. def test_visit_missing_url():
  33. from app.main import app
  34. from fastapi.testclient import TestClient
  35. client = TestClient(app)
  36. response = client.post("/api/v1/visit", json={})
  37. assert response.status_code == 422
  38. class TestVisitEndpoint:
  39. """Tests que mockean AsyncCamoufox para evitar llamadas reales al browser."""
  40. def test_visit_invalid_url(self):
  41. with mocked_client() as client:
  42. response = client.post("/api/v1/visit", json={"url": "not-a-url"})
  43. assert response.status_code == 422
  44. def test_visit_valid_url(self):
  45. with mocked_client() as client:
  46. response = client.post(
  47. "/api/v1/visit",
  48. json={"url": "https://example.com", "screenshot": False},
  49. )
  50. assert response.status_code == 200
  51. body = response.json()
  52. assert body["status"] == "OK"
  53. assert "html" in body["data"]
  54. def test_visit_with_screenshot(self):
  55. with mocked_client(
  56. {"html": "<html>Example</html>", "screenshot": "base64data"}
  57. ) as client:
  58. response = client.post(
  59. "/api/v1/visit",
  60. json={"url": "https://example.com", "screenshot": True},
  61. )
  62. assert response.status_code == 200
  63. body = response.json()
  64. assert body["status"] == "OK"
  65. assert "screenshot" in body["data"]
  66. def test_visit_with_invalid_waituntil(self):
  67. with mocked_client() as client:
  68. response = client.post(
  69. "/api/v1/visit",
  70. json={"url": "https://example.com", "waitUntil": "invalid"},
  71. )
  72. assert response.status_code == 422
  73. def test_visit_with_valid_waituntil(self):
  74. with mocked_client() as client:
  75. for wait_until in ["load", "domcontentloaded", "networkidle", "commit"]:
  76. response = client.post(
  77. "/api/v1/visit",
  78. json={"url": "https://example.com", "waitUntil": wait_until},
  79. )
  80. assert response.status_code == 200
  81. def test_visit_response_format(self):
  82. with mocked_client() as client:
  83. response = client.post(
  84. "/api/v1/visit",
  85. json={"url": "https://example.com"},
  86. )
  87. body = response.json()
  88. assert "status" in body
  89. assert "message" in body
  90. assert "data" in body
  91. assert "code" in body
  92. assert body["code"] == "UNKNOWN_CODE"
  93. class TestVisitEndpointController:
  94. """Tests que verifican el controller directamente con VisitUseCase mockeado."""
  95. def test_controller_visit_uses_visit_use_case(self):
  96. """Test que cubre la llamada a use_case.execute."""
  97. from app.controllers.api_controller import ApiController
  98. from app.models.schemas import VisitRequest
  99. from fastapi import Request
  100. from unittest.mock import MagicMock
  101. controller = ApiController()
  102. mock_request = MagicMock(spec=Request)
  103. mock_request.scope = {"raw_headers_dict": {"user-agent": "test"}}
  104. with patch.object(controller, "visit_use_case") as mock_use_case:
  105. async def async_execute(*args, **kwargs):
  106. return {"html": "<html>Controller</html>", "screenshot": None}
  107. mock_use_case.execute = async_execute
  108. import asyncio
  109. result = asyncio.run(
  110. controller.visit(mock_request, VisitRequest(url="https://example.com"))
  111. )
  112. assert result["status"] == "OK"
  113. assert result["data"]["html"] == "<html>Controller</html>"
  114. def test_controller_visit_error_handling(self):
  115. """Test que cubre el manejo de errores."""
  116. from app.controllers.api_controller import ApiController
  117. from app.models.schemas import VisitRequest
  118. from fastapi import Request
  119. from unittest.mock import MagicMock
  120. controller = ApiController()
  121. mock_request = MagicMock(spec=Request)
  122. mock_request.scope = {"raw_headers_dict": {"user-agent": "test"}}
  123. with patch.object(controller, "visit_use_case") as mock_use_case:
  124. async def async_execute(*args, **kwargs):
  125. raise Exception("Test error")
  126. mock_use_case.execute = async_execute
  127. import asyncio
  128. result = asyncio.run(
  129. controller.visit(mock_request, VisitRequest(url="https://example.com"))
  130. )
  131. assert result["status"] == "NO_OK"
  132. assert "Test error" in result["message"]