"""Tests for tools controller.""" import pytest from fastapi.testclient import TestClient from unittest.mock import patch, AsyncMock, MagicMock from src.main import app from src.domains.tools.controller import tools_controller @pytest.fixture def client(): """Create a test client.""" return TestClient(app) class TestDNSLookup: """Test /tools/dns/lookup endpoint.""" @patch("src.controllers.tools_controller.DNSService") def test_dns_lookup_returns_200(self, mock_dns_class, client): """DNS lookup should return 200 for valid request.""" mock_service = MagicMock() mock_service.lookup = AsyncMock(return_value=MagicMock( success=True, domain="example.com", record_type="A", records=[{"value": "93.184.216.34"}], nameserver_used="8.8.8.8", query_time_ms=50, error_message=None )) mock_dns_class.return_value = mock_service response = client.post( "/tools/dns/lookup", json={"domain": "example.com", "record_type": "A"} ) assert response.status_code == 200 def test_dns_lookup_returns_result(self, client): """DNS lookup should return lookup results. Patches the live singleton's `dns_service` attribute, not the `src.controllers.tools_controller.DNSService` class: that module is the legacy top-level package (not wired into `src.main`, see CLAUDE.md "Legacy top-level packages"). `client` exercises `src.main.app`, which routes through `src.domains.tools.controller.tools_controller`, a singleton built at import time — so patching the class there would also miss, since `tools_controller.dns_service` is already a constructed instance by the time a test patches the class. Patching the instance attribute directly is the only patch that actually intercepts this request path. """ mock_response = MagicMock() mock_response.success = True mock_response.domain = "example.com" mock_response.record_type = "A" mock_response.records = [{"value": "93.184.216.34"}] mock_response.nameserver_used = "8.8.8.8" mock_response.query_time_ms = 50 mock_response.error_message = None mock_response.model_dump = MagicMock(return_value={ "success": True, "domain": "example.com", "record_type": "A", "records": [{"value": "93.184.216.34"}], "nameserver_used": "8.8.8.8", "query_time_ms": 50, "error_message": None }) mock_service = MagicMock() mock_service.lookup = AsyncMock(return_value=mock_response) with patch.object(tools_controller, "dns_service", mock_service): response = client.post( "/tools/dns/lookup", json={"domain": "example.com", "record_type": "A"} ) data = response.json() assert data["success"] is True assert data["domain"] == "example.com" def test_dns_lookup_requires_domain(self, client): """DNS lookup should require domain parameter.""" response = client.post( "/tools/dns/lookup", json={"record_type": "A"} ) assert response.status_code == 422 @patch("src.controllers.tools_controller.DNSService") def test_dns_lookup_handles_dns_query_error(self, mock_dns_class, client): """DNS lookup should handle DNSQueryError.""" from src.dns.exceptions import DNSQueryError mock_service = MagicMock() mock_service.lookup = AsyncMock(side_effect=DNSQueryError("Unsupported record type")) mock_dns_class.return_value = mock_service response = client.post( "/tools/dns/lookup", json={"domain": "example.com", "record_type": "INVALID"} ) assert response.status_code == 400 @patch("src.controllers.tools_controller.DNSService") def test_dns_lookup_accepts_custom_nameserver(self, mock_dns_class, client): """DNS lookup should accept custom nameserver.""" mock_response = MagicMock() mock_response.success = True mock_response.domain = "example.com" mock_response.record_type = "A" mock_response.records = [] mock_response.nameserver_used = "1.1.1.1" mock_response.query_time_ms = 30 mock_response.error_message = None mock_response.model_dump = MagicMock(return_value={ "success": True, "domain": "example.com", "record_type": "A", "records": [], "nameserver_used": "1.1.1.1", "query_time_ms": 30, "error_message": None }) mock_service = MagicMock() mock_service.lookup = AsyncMock(return_value=mock_response) mock_dns_class.return_value = mock_service response = client.post( "/tools/dns/lookup", json={"domain": "example.com", "record_type": "A", "nameserver": "1.1.1.1"} ) assert response.status_code == 200 class TestToolsControllerInit: """Test ToolsController initialization.""" def test_controller_has_correct_prefix(self): """Controller should have /tools prefix.""" from src.controllers.tools_controller import tools_controller assert tools_controller.prefix == "/tools" def test_controller_has_correct_tags(self): """Controller should have Tools tag.""" from src.controllers.tools_controller import tools_controller assert "Tools" in tools_controller.tags