"""Unit tests for OwsClient and AsyncOwsClient.""" import json from typing import Any, AsyncIterator from unittest.mock import AsyncMock, MagicMock, patch import httpx import pytest from _pytest.monkeypatch import MonkeyPatch from owsclient import AsyncOwsClient, OwsClient from owsclient.test.mock import OwsClientMock TEST_SERVICE_NAME = "ows-permissions" TEST_PATH = "/hello/" TEST_RESPONSE_STATUS_CODE = 200 TEST_RESPONSE_JSON = {"status": "ok"} @pytest.fixture(autouse=True) def setup_service_mapping(monkeypatch: MonkeyPatch) -> None: """Fixture for service map.""" monkeypatch.setenv( "OWSREQUEST_SERVICE_MAP", json.dumps({TEST_SERVICE_NAME: "http://ows-permission.test/"}), ) @pytest.fixture def ows_client() -> OwsClient: """Fixture for OwsClient.""" return OwsClient(environment="test", service_name="ows-test") @pytest.fixture async def async_ows_client() -> AsyncIterator[AsyncOwsClient]: """Fixture for AsyncOwsClient.""" async_ows_client = AsyncOwsClient(environment="test", service_name="ows-test") yield async_ows_client await async_ows_client.close() @pytest.mark.parametrize( ("test_description", "mock_service_url", "expected_url"), [ ( "Service url with http protocol", "http://ows-permission.test/", "http://ows-permission.test/hello/", ), ( "Service url with https protocol", "https://ows-permission.test/", "https://ows-permission.test/hello/", ), ( "Service url without protocol", "some-service.theorchard.com", "http://some-service.theorchard.com/hello/", ), ( "Service url without trailing slash", "http://ows-permission.test", "http://ows-permission.test/hello/", ), ( "localhost service url with protocol", "http://localhost:8080/", "http://localhost:8080/hello/", ), ( "localhost service url without protocol", "localhost:8080/", "http://localhost:8080/hello/", ), ( "some-service port without protocol", "some-service:3442", "http://some-service:3442/hello/", ), ( "some-service port with protocol", "http://some-service:3442", "http://some-service:3442/hello/", ), ], ) @patch("owsclient.base.discover_service_url") def test_prepare_url( mock_discover_service_url: MagicMock, ows_client: OwsClient, test_description: str, mock_service_url: str, expected_url: str, ) -> None: """Test URL is prepared correctly.""" mock_discover_service_url.return_value = ("test", mock_service_url) url = ows_client.prepare_url( service_name=TEST_SERVICE_NAME, path=TEST_PATH, ) assert url == expected_url, test_description mock_discover_service_url.assert_called_once_with( environment="test", service_name=TEST_SERVICE_NAME, ) def test_ows_client_request( ows_client_mock: OwsClientMock, ows_client: OwsClient ) -> None: """Test request is made using OwsClient.""" path = "/test/" response_status = 200 response_json = {"status": "ok"} ows_client_mock.request(TEST_SERVICE_NAME, method="GET", path=path).mock( return_value=httpx.Response(status_code=response_status, json=response_json) ) response = ows_client.request(TEST_SERVICE_NAME, method="GET", path=path) assert response.status_code == response_status assert response.json() == response_json @pytest.mark.parametrize( ( "test_description", "additional_ows_client_kwargs", "expected_additional_httpx_client_kwargs", ), [ ( "default timeout", {}, {"timeout": httpx.Timeout(5.0)}, ), ( "custom timeout", {"timeout": httpx.Timeout(5.0, read=45.0)}, {"timeout": httpx.Timeout(5.0, read=45.0)}, ), ], ) @patch("httpx.Client") def test_ows_client_kwargs_passed_to_httpx_client( httpx_client_mock: MagicMock, test_description: str, additional_ows_client_kwargs: dict[str, Any], expected_additional_httpx_client_kwargs: dict[str, Any], ) -> None: """Test OwsClient kwargs passed to httpx.Client.""" ows_client = OwsClient( environment="test", service_name="ows-test", **additional_ows_client_kwargs ) ows_client.get(TEST_SERVICE_NAME, path="/test/") assert all( httpx_client_mock.call_args.kwargs[key] == value for key, value in expected_additional_httpx_client_kwargs.items() ) @patch("httpx.Client.send") def test_ows_client_request_timeout_overrides_default(mock_send: MagicMock) -> None: """Test specifying timeout in OwsClient request overrides default timeout.""" default_timeout = 1 request_timeout = default_timeout + 1 ows_client = OwsClient( environment="test", service_name="ows-test", timeout=httpx.Timeout(default_timeout), ) ows_client.get(TEST_SERVICE_NAME, path="/test/") ows_client.get( TEST_SERVICE_NAME, path="/test/", timeout=httpx.Timeout(request_timeout), ) default_timeout_request, request_timeout_request = mock_send.call_args_list assert ( default_timeout_request.args[0].extensions["timeout"] == httpx.Timeout(default_timeout).as_dict() ) assert ( request_timeout_request.args[0].extensions["timeout"] == httpx.Timeout(request_timeout).as_dict() ) @pytest.mark.parametrize("method", ["HEAD", "GET", "POST", "PUT", "PATCH", "DELETE"]) def test_ows_client_method( method: str, ows_client_mock: OwsClientMock, ows_client: OwsClient ) -> None: """Test OwsClient request is made with different request methods.""" path = "/test/" response_status = 200 response_json = {"status": "ok"} ows_client_mock_request = getattr(ows_client_mock, method.lower()) ows_client_mock_request(TEST_SERVICE_NAME, path=path).mock( return_value=httpx.Response(status_code=response_status, json=response_json) ) ows_client_request = getattr(ows_client, method.lower()) response = ows_client_request(TEST_SERVICE_NAME, path=path) assert response.status_code == response_status assert response.json() == response_json @pytest.mark.anyio @pytest.mark.parametrize( ( "test_description", "additional_async_ows_client_kwargs", "expected_additional_httpx_async_client_kwargs", ), [ ( "default timeout", {}, {"timeout": httpx.Timeout(5.0)}, ), ( "custom timeout", {"timeout": httpx.Timeout(5.0, read=45.0)}, {"timeout": httpx.Timeout(5.0, read=45.0)}, ), ], ) @patch("httpx.AsyncClient") async def test_async_ows_client_kwargs_passed_to_httpx_async_client( httpx_async_client_mock: MagicMock, test_description: str, additional_async_ows_client_kwargs: dict[str, Any], expected_additional_httpx_async_client_kwargs: dict[str, Any], ) -> None: """Test AsyncOwsClient kwargs passed to httpx.AsyncClient.""" httpx_async_client_mock.return_value.request = AsyncMock() async_ows_client = AsyncOwsClient( environment="test", service_name="ows-test", **additional_async_ows_client_kwargs, ) await async_ows_client.get(TEST_SERVICE_NAME, path="/test/") assert all( httpx_async_client_mock.call_args.kwargs[key] == value for key, value in expected_additional_httpx_async_client_kwargs.items() ) @pytest.mark.anyio @patch("httpx.AsyncClient.send") async def test_async_ows_client_request_timeout_overrides_default( mock_send: AsyncMock, ) -> None: """Test specifying timeout in AsyncOwsClient request overrides default timeout.""" default_timeout = 1 request_timeout = default_timeout + 1 async_ows_client = AsyncOwsClient( environment="test", service_name="ows-test", timeout=httpx.Timeout(default_timeout), ) await async_ows_client.get(TEST_SERVICE_NAME, path="/test/") await async_ows_client.get( TEST_SERVICE_NAME, path="/test/", timeout=httpx.Timeout(request_timeout), ) default_timeout_request, request_timeout_request = mock_send.call_args_list assert ( default_timeout_request.args[0].extensions["timeout"] == httpx.Timeout(default_timeout).as_dict() ) assert ( request_timeout_request.args[0].extensions["timeout"] == httpx.Timeout(request_timeout).as_dict() ) @pytest.mark.anyio async def test_async_ows_client_request( ows_client_mock: OwsClientMock, async_ows_client: AsyncOwsClient ) -> None: """Test request is made using AsyncOwsClient.""" path = "/test-async/" response_status = 200 response_json = {"status": "ok"} ows_client_mock.request(TEST_SERVICE_NAME, path=path, method="POST").mock( return_value=httpx.Response(status_code=response_status, json=response_json) ) response = await async_ows_client.request( TEST_SERVICE_NAME, path=path, method="POST" ) assert response.status_code == response_status assert response.json() == response_json @pytest.mark.parametrize("method", ["HEAD", "GET", "POST", "PUT", "PATCH", "DELETE"]) @pytest.mark.anyio async def test_async_ows_client_method( method: str, ows_client_mock: OwsClientMock, async_ows_client: AsyncOwsClient ) -> None: """Test OwsClient request is made with different request methods.""" path = "/test-async/" response_status = 200 response_json = {"status": "ok"} ows_client_mock_request = getattr(ows_client_mock, method.lower()) ows_client_mock_request(TEST_SERVICE_NAME, path=path).mock( return_value=httpx.Response(status_code=response_status, json=response_json) ) async_ows_client_request = getattr(async_ows_client, method.lower()) response = await async_ows_client_request(TEST_SERVICE_NAME, path=path) assert response.status_code == response_status assert response.json() == response_json