""" PyTest: requests_handling/caller """ from unittest.mock import Mock, patch import pytest import requests from corelibs.requests_handling.caller import Caller, ErrorResponse, ProxyConfig class TestCallerInit: """Tests for Caller initialization""" def test_init_with_required_params_only(self): """Test Caller initialization with only required parameters""" header = {"Authorization": "Bearer token"} caller = Caller(header=header) assert caller.headers == header assert caller.timeout == 20 assert caller.verify is True assert caller.proxy is None assert caller.ca_file is None def test_init_with_all_params(self): """Test Caller initialization with all parameters""" header = {"Authorization": "Bearer token", "Content-Type": "application/json"} proxy: ProxyConfig = { "type": "socks5", "host": "proxy.example.com:8080", "port": "8080" } caller = Caller(header=header, timeout=30, proxy=proxy, verify=False) assert caller.headers == header assert caller.timeout == 30 assert caller.verify is False assert caller.proxy == proxy def test_init_with_empty_header(self): """Test Caller initialization with empty header""" caller = Caller(header={}) assert caller.headers == {} assert caller.timeout == 20 def test_init_custom_timeout(self): """Test Caller initialization with custom timeout""" caller = Caller(header={}, timeout=60) assert caller.timeout == 60 def test_init_verify_false(self): """Test Caller initialization with verify=False""" caller = Caller(header={}, verify=False) assert caller.verify is False def test_init_with_ca_file(self): """Test Caller initialization with ca_file parameter""" ca_file_path = "/path/to/ca/cert.pem" caller = Caller(header={}, ca_file=ca_file_path) assert caller.ca_file == ca_file_path class TestCallerGet: """Tests for Caller.get method""" @patch('corelibs.requests_handling.caller.requests.get') def test_get_basic(self, mock_get: Mock): """Test basic GET request""" mock_response = Mock(spec=requests.Response) mock_response.status_code = 200 mock_get.return_value = mock_response caller = Caller(header={"Authorization": "Bearer token"}) response = caller.get("https://api.example.com/data") assert response == mock_response mock_get.assert_called_once_with( "https://api.example.com/data", params=None, headers={"Authorization": "Bearer token"}, timeout=20, verify=True, proxies=None, cert=None ) @patch('corelibs.requests_handling.caller.requests.get') def test_get_with_params(self, mock_get: Mock): """Test GET request with query parameters""" mock_response = Mock(spec=requests.Response) mock_get.return_value = mock_response caller = Caller(header={}) params = {"page": 1, "limit": 10} response = caller.get("https://api.example.com/data", params=params) assert response == mock_response mock_get.assert_called_once_with( "https://api.example.com/data", params=params, headers={}, timeout=20, verify=True, proxies=None, cert=None ) @patch('corelibs.requests_handling.caller.requests.get') def test_get_with_custom_timeout(self, mock_get: Mock): """Test GET request uses default timeout from instance""" mock_response = Mock(spec=requests.Response) mock_get.return_value = mock_response caller = Caller(header={}, timeout=45) caller.get("https://api.example.com/data") mock_get.assert_called_once() assert mock_get.call_args[1]["timeout"] == 45 @patch('corelibs.requests_handling.caller.requests.get') def test_get_with_verify_false(self, mock_get: Mock): """Test GET request with verify=False""" mock_response = Mock(spec=requests.Response) mock_get.return_value = mock_response caller = Caller(header={}, verify=False) caller.get("https://api.example.com/data") mock_get.assert_called_once() assert mock_get.call_args[1]["verify"] is False @patch('corelibs.requests_handling.caller.requests.get') def test_get_with_proxy(self, mock_get: Mock): """Test GET request with proxy""" mock_response = Mock(spec=requests.Response) mock_get.return_value = mock_response proxy: ProxyConfig = { "type": "socks5", "host": "proxy.example.com:8080", "port": "8080" } caller = Caller(header={}, proxy=proxy) caller.get("https://api.example.com/data") mock_get.assert_called_once() assert mock_get.call_args[1]["proxies"] == proxy @patch('corelibs.requests_handling.caller.requests.get') def test_get_invalid_schema_returns_none(self, mock_get: Mock): """Test GET request with invalid URL schema returns ErrorResponse""" mock_get.side_effect = requests.exceptions.InvalidSchema("Invalid URL") caller = Caller(header={}) response = caller.get("invalid://example.com") assert isinstance(response, ErrorResponse) assert response.code == 200 assert "Invalid URL during 'get'" in response.message assert response.action == "get" assert response.url == "invalid://example.com" @patch('corelibs.requests_handling.caller.requests.get') def test_get_timeout_returns_none(self, mock_get: Mock): """Test GET request timeout returns ErrorResponse""" mock_get.side_effect = requests.exceptions.ReadTimeout("Timeout") caller = Caller(header={}) response = caller.get("https://api.example.com/data") assert isinstance(response, ErrorResponse) assert response.code == 300 assert "Timeout (20s) during 'get'" in response.message assert response.action == "get" assert response.url == "https://api.example.com/data" @patch('corelibs.requests_handling.caller.requests.get') def test_get_connection_error_returns_none(self, mock_get: Mock): """Test GET request connection error returns ErrorResponse""" mock_get.side_effect = requests.exceptions.ConnectionError("Connection failed") caller = Caller(header={}) response = caller.get("https://api.example.com/data") assert isinstance(response, ErrorResponse) assert response.code == 400 assert "Connection error during 'get'" in response.message assert response.action == "get" assert response.url == "https://api.example.com/data" class TestCallerPost: """Tests for Caller.post method""" @patch('corelibs.requests_handling.caller.requests.post') def test_post_basic(self, mock_post: Mock): """Test basic POST request""" mock_response = Mock(spec=requests.Response) mock_response.status_code = 201 mock_post.return_value = mock_response caller = Caller(header={"Content-Type": "application/json"}) data = {"name": "test", "value": 123} response = caller.post("https://api.example.com/data", data=data) assert response == mock_response mock_post.assert_called_once_with( "https://api.example.com/data", params=None, json=data, headers={"Content-Type": "application/json"}, timeout=20, verify=True, proxies=None, cert=None ) @patch('corelibs.requests_handling.caller.requests.post') def test_post_without_data(self, mock_post: Mock): """Test POST request without data""" mock_response = Mock(spec=requests.Response) mock_post.return_value = mock_response caller = Caller(header={}) response = caller.post("https://api.example.com/data") assert response == mock_response mock_post.assert_called_once() # Data defaults to None, which becomes {} in __call assert mock_post.call_args[1]["json"] == {} @patch('corelibs.requests_handling.caller.requests.post') def test_post_with_params(self, mock_post: Mock): """Test POST request with query parameters""" mock_response = Mock(spec=requests.Response) mock_post.return_value = mock_response caller = Caller(header={}) data = {"key": "value"} params = {"version": "v1"} response = caller.post("https://api.example.com/data", data=data, params=params) assert response == mock_response mock_post.assert_called_once() assert mock_post.call_args[1]["params"] == params assert mock_post.call_args[1]["json"] == data @patch('corelibs.requests_handling.caller.requests.post') def test_post_invalid_schema_returns_none(self, mock_post: Mock): """Test POST request with invalid URL schema returns ErrorResponse""" mock_post.side_effect = requests.exceptions.InvalidSchema("Invalid URL") caller = Caller(header={}) response = caller.post("invalid://example.com", data={"test": "data"}) assert isinstance(response, ErrorResponse) assert response.code == 200 assert "Invalid URL during 'post'" in response.message assert response.action == "post" assert response.url == "invalid://example.com" @patch('corelibs.requests_handling.caller.requests.post') def test_post_timeout_returns_none(self, mock_post: Mock): """Test POST request timeout returns ErrorResponse""" mock_post.side_effect = requests.exceptions.ReadTimeout("Timeout") caller = Caller(header={}) response = caller.post("https://api.example.com/data", data={"test": "data"}) assert isinstance(response, ErrorResponse) assert response.code == 300 assert "Timeout (20s) during 'post'" in response.message assert response.action == "post" assert response.url == "https://api.example.com/data" @patch('corelibs.requests_handling.caller.requests.post') def test_post_connection_error_returns_none(self, mock_post: Mock): """Test POST request connection error returns ErrorResponse""" mock_post.side_effect = requests.exceptions.ConnectionError("Connection failed") caller = Caller(header={}) response = caller.post("https://api.example.com/data", data={"test": "data"}) assert isinstance(response, ErrorResponse) assert response.code == 400 assert "Connection error during 'post'" in response.message assert response.action == "post" assert response.url == "https://api.example.com/data" class TestCallerPut: """Tests for Caller.put method""" @patch('corelibs.requests_handling.caller.requests.put') def test_put_basic(self, mock_put: Mock): """Test basic PUT request""" mock_response = Mock(spec=requests.Response) mock_response.status_code = 200 mock_put.return_value = mock_response caller = Caller(header={"Content-Type": "application/json"}) data = {"id": 1, "name": "updated"} response = caller.put("https://api.example.com/data/1", data=data) assert response == mock_response mock_put.assert_called_once_with( "https://api.example.com/data/1", params=None, json=data, headers={"Content-Type": "application/json"}, timeout=20, verify=True, proxies=None, cert=None ) @patch('corelibs.requests_handling.caller.requests.put') def test_put_with_params(self, mock_put: Mock): """Test PUT request with query parameters""" mock_response = Mock(spec=requests.Response) mock_put.return_value = mock_response caller = Caller(header={}) data = {"name": "test"} params = {"force": "true"} response = caller.put("https://api.example.com/data/1", data=data, params=params) assert response == mock_response mock_put.assert_called_once() assert mock_put.call_args[1]["params"] == params @patch('corelibs.requests_handling.caller.requests.put') def test_put_timeout_returns_none(self, mock_put: Mock): """Test PUT request timeout returns ErrorResponse""" mock_put.side_effect = requests.exceptions.ReadTimeout("Timeout") caller = Caller(header={}) response = caller.put("https://api.example.com/data/1", data={"test": "data"}) assert isinstance(response, ErrorResponse) assert response.code == 300 assert "Timeout (20s) during 'put'" in response.message assert response.action == "put" assert response.url == "https://api.example.com/data/1" class TestCallerPatch: """Tests for Caller.patch method""" @patch('corelibs.requests_handling.caller.requests.patch') def test_patch_basic(self, mock_patch: Mock): """Test basic PATCH request""" mock_response = Mock(spec=requests.Response) mock_response.status_code = 200 mock_patch.return_value = mock_response caller = Caller(header={"Content-Type": "application/json"}) data = {"status": "active"} response = caller.patch("https://api.example.com/data/1", data=data) assert response == mock_response mock_patch.assert_called_once_with( "https://api.example.com/data/1", params=None, json=data, headers={"Content-Type": "application/json"}, timeout=20, verify=True, proxies=None, cert=None ) @patch('corelibs.requests_handling.caller.requests.patch') def test_patch_with_params(self, mock_patch: Mock): """Test PATCH request with query parameters""" mock_response = Mock(spec=requests.Response) mock_patch.return_value = mock_response caller = Caller(header={}) data = {"field": "value"} params = {"notify": "false"} response = caller.patch("https://api.example.com/data/1", data=data, params=params) assert response == mock_response mock_patch.assert_called_once() assert mock_patch.call_args[1]["params"] == params @patch('corelibs.requests_handling.caller.requests.patch') def test_patch_connection_error_returns_none(self, mock_patch: Mock): """Test PATCH request connection error returns ErrorResponse""" mock_patch.side_effect = requests.exceptions.ConnectionError("Connection failed") caller = Caller(header={}) response = caller.patch("https://api.example.com/data/1", data={"test": "data"}) assert isinstance(response, ErrorResponse) assert response.code == 400 assert "Connection error during 'patch'" in response.message assert response.action == "patch" assert response.url == "https://api.example.com/data/1" class TestCallerDelete: """Tests for Caller.delete method""" @patch('corelibs.requests_handling.caller.requests.delete') def test_delete_basic(self, mock_delete: Mock): """Test basic DELETE request""" mock_response = Mock(spec=requests.Response) mock_response.status_code = 204 mock_delete.return_value = mock_response caller = Caller(header={"Authorization": "Bearer token"}) response = caller.delete("https://api.example.com/data/1") assert response == mock_response mock_delete.assert_called_once_with( "https://api.example.com/data/1", params=None, headers={"Authorization": "Bearer token"}, timeout=20, verify=True, proxies=None, cert=None ) @patch('corelibs.requests_handling.caller.requests.delete') def test_delete_with_params(self, mock_delete: Mock): """Test DELETE request with query parameters""" mock_response = Mock(spec=requests.Response) mock_delete.return_value = mock_response caller = Caller(header={}) params = {"force": "true"} response = caller.delete("https://api.example.com/data/1", params=params) assert response == mock_response mock_delete.assert_called_once() assert mock_delete.call_args[1]["params"] == params @patch('corelibs.requests_handling.caller.requests.delete') def test_delete_invalid_schema_returns_none(self, mock_delete: Mock): """Test DELETE request with invalid URL schema returns ErrorResponse""" mock_delete.side_effect = requests.exceptions.InvalidSchema("Invalid URL") caller = Caller(header={}) response = caller.delete("invalid://example.com/data/1") assert isinstance(response, ErrorResponse) assert response.code == 200 assert "Invalid URL during 'delete'" in response.message assert response.action == "delete" assert response.url == "invalid://example.com/data/1" class TestCallerParametrized: """Parametrized tests for all HTTP methods""" @pytest.mark.parametrize("method,http_method", [ ("get", "get"), ("post", "post"), ("put", "put"), ("patch", "patch"), ("delete", "delete"), ]) @patch('corelibs.requests_handling.caller.requests') def test_all_methods_use_correct_headers(self, mock_requests: Mock, method: str, http_method: str): """Test that all HTTP methods use the headers correctly""" mock_response = Mock(spec=requests.Response) mock_http_method = getattr(mock_requests, http_method) mock_http_method.return_value = mock_response headers = {"Authorization": "Bearer token", "X-Custom": "value"} caller = Caller(header=headers) # Call the method caller_method = getattr(caller, method) if method in ["get", "delete"]: caller_method("https://api.example.com/data") else: caller_method("https://api.example.com/data", data={"key": "value"}) # Verify headers were passed mock_http_method.assert_called_once() assert mock_http_method.call_args[1]["headers"] == headers @pytest.mark.parametrize("method,http_method", [ ("get", "get"), ("post", "post"), ("put", "put"), ("patch", "patch"), ("delete", "delete"), ]) @patch('corelibs.requests_handling.caller.requests') def test_all_methods_use_timeout(self, mock_requests: Mock, method: str, http_method: str): """Test that all HTTP methods use the timeout correctly""" mock_response = Mock(spec=requests.Response) mock_http_method = getattr(mock_requests, http_method) mock_http_method.return_value = mock_response timeout = 45 caller = Caller(header={}, timeout=timeout) # Call the method caller_method = getattr(caller, method) if method in ["get", "delete"]: caller_method("https://api.example.com/data") else: caller_method("https://api.example.com/data", data={"key": "value"}) # Verify timeout was passed mock_http_method.assert_called_once() assert mock_http_method.call_args[1]["timeout"] == timeout @pytest.mark.parametrize("exception_class,expected_message", [ (requests.exceptions.InvalidSchema, "Invalid URL during"), (requests.exceptions.ReadTimeout, "Timeout"), (requests.exceptions.ConnectionError, "Connection error during"), ]) @patch('corelibs.requests_handling.caller.requests.get') def test_exception_handling( self, mock_get: Mock, exception_class: type, expected_message: str ): """Test exception handling for all exception types""" mock_get.side_effect = exception_class("Test error") caller = Caller(header={}) response = caller.get("https://api.example.com/data") assert isinstance(response, ErrorResponse) assert expected_message in response.message class TestCallerIntegration: """Integration tests for Caller""" @patch('corelibs.requests_handling.caller.requests') def test_multiple_requests_maintain_state(self, mock_requests: Mock): """Test that multiple requests maintain caller state""" mock_response = Mock(spec=requests.Response) mock_requests.get.return_value = mock_response mock_requests.post.return_value = mock_response headers = {"Authorization": "Bearer token"} caller = Caller(header=headers, timeout=30, verify=False) # Make multiple requests caller.get("https://api.example.com/data1") caller.post("https://api.example.com/data2", data={"key": "value"}) # Verify both used same configuration assert mock_requests.get.call_args[1]["headers"] == headers assert mock_requests.get.call_args[1]["timeout"] == 30 assert mock_requests.get.call_args[1]["verify"] is False assert mock_requests.post.call_args[1]["headers"] == headers assert mock_requests.post.call_args[1]["timeout"] == 30 assert mock_requests.post.call_args[1]["verify"] is False @patch('corelibs.requests_handling.caller.requests.post') def test_post_with_complex_data(self, mock_post: Mock): """Test POST request with complex nested data""" mock_response = Mock(spec=requests.Response) mock_post.return_value = mock_response caller = Caller(header={}) complex_data = { "user": { "name": "John Doe", "email": "john@example.com", "preferences": { "notifications": True, "theme": "dark" } }, "tags": ["important", "urgent"], "count": 42 } response = caller.post("https://api.example.com/users", data=complex_data) assert response == mock_response mock_post.assert_called_once() assert mock_post.call_args[1]["json"] == complex_data @patch('corelibs.requests_handling.caller.requests') def test_all_http_methods_work_together(self, mock_requests: Mock): """Test that all HTTP methods can be used with the same Caller instance""" mock_response = Mock(spec=requests.Response) for method in ['get', 'post', 'put', 'patch', 'delete']: getattr(mock_requests, method).return_value = mock_response caller = Caller(header={"Authorization": "Bearer token"}) # Test all methods caller.get("https://api.example.com/data") caller.post("https://api.example.com/data", data={"new": "data"}) caller.put("https://api.example.com/data/1", data={"updated": "data"}) caller.patch("https://api.example.com/data/1", data={"field": "value"}) caller.delete("https://api.example.com/data/1") # Verify all were called mock_requests.get.assert_called_once() mock_requests.post.assert_called_once() mock_requests.put.assert_called_once() mock_requests.patch.assert_called_once() mock_requests.delete.assert_called_once() class TestCallerEdgeCases: """Edge case tests for Caller""" @patch('corelibs.requests_handling.caller.requests.get') def test_empty_url(self, mock_get: Mock): """Test with empty URL""" mock_response = Mock(spec=requests.Response) mock_get.return_value = mock_response caller = Caller(header={}) response = caller.get("") assert response == mock_response mock_get.assert_called_once_with( "", params=None, headers={}, timeout=20, verify=True, proxies=None, cert=None ) @patch('corelibs.requests_handling.caller.requests.post') def test_post_with_empty_data(self, mock_post: Mock): """Test POST with explicitly empty data dict""" mock_response = Mock(spec=requests.Response) mock_post.return_value = mock_response caller = Caller(header={}) response = caller.post("https://api.example.com/data", data={}) assert response == mock_response mock_post.assert_called_once() assert mock_post.call_args[1]["json"] == {} @patch('corelibs.requests_handling.caller.requests.get') def test_get_with_empty_params(self, mock_get: Mock): """Test GET with explicitly empty params dict""" mock_response = Mock(spec=requests.Response) mock_get.return_value = mock_response caller = Caller(header={}) response = caller.get("https://api.example.com/data", params={}) assert response == mock_response mock_get.assert_called_once() assert mock_get.call_args[1]["params"] == {} @patch('corelibs.requests_handling.caller.requests.post') def test_post_with_none_values_in_data(self, mock_post: Mock): """Test POST with None values in data""" mock_response = Mock(spec=requests.Response) mock_post.return_value = mock_response caller = Caller(header={}) data = {"key1": None, "key2": "value", "key3": None} response = caller.post("https://api.example.com/data", data=data) assert response == mock_response mock_post.assert_called_once() assert mock_post.call_args[1]["json"] == data @patch('corelibs.requests_handling.caller.requests.get') def test_very_long_url(self, mock_get: Mock): """Test with very long URL""" mock_response = Mock(spec=requests.Response) mock_get.return_value = mock_response caller = Caller(header={}) long_url = "https://api.example.com/" + "a" * 1000 response = caller.get(long_url) assert response == mock_response mock_get.assert_called_once_with( long_url, params=None, headers={}, timeout=20, verify=True, proxies=None, cert=None ) @patch('corelibs.requests_handling.caller.requests.get') def test_special_characters_in_url(self, mock_get: Mock): """Test URL with special characters""" mock_response = Mock(spec=requests.Response) mock_get.return_value = mock_response caller = Caller(header={}) url = "https://api.example.com/data?query=test%20value&id=123" response = caller.get(url) assert response == mock_response mock_get.assert_called_once_with( url, params=None, headers={}, timeout=20, verify=True, proxies=None, cert=None ) def test_timeout_zero(self): """Test Caller with timeout of 0""" caller = Caller(header={}, timeout=0) assert caller.timeout == 0 def test_negative_timeout(self): """Test Caller with negative timeout""" caller = Caller(header={}, timeout=-1) assert caller.timeout == -1 @patch('corelibs.requests_handling.caller.requests.get') def test_unicode_in_headers(self, mock_get: Mock): """Test headers with unicode characters""" mock_response = Mock(spec=requests.Response) mock_get.return_value = mock_response headers = {"X-Custom": "测试", "Authorization": "Bearer token"} caller = Caller(header=headers) response = caller.get("https://api.example.com/data") assert response == mock_response mock_get.assert_called_once() assert mock_get.call_args[1]["headers"] == headers @patch('corelibs.requests_handling.caller.requests.post') def test_unicode_in_data(self, mock_post: Mock): """Test data with unicode characters""" mock_response = Mock(spec=requests.Response) mock_post.return_value = mock_response caller = Caller(header={}) data = {"name": "用户", "message": "こんにちは", "emoji": "🚀"} response = caller.post("https://api.example.com/data", data=data) assert response == mock_response mock_post.assert_called_once() assert mock_post.call_args[1]["json"] == data class TestCallerProxyHandling: """Tests for proxy handling""" @patch('corelibs.requests_handling.caller.requests.get') def test_proxy_configuration(self, mock_get: Mock): """Test that proxy configuration is passed to requests""" mock_response = Mock(spec=requests.Response) mock_get.return_value = mock_response proxy: ProxyConfig = { "type": "socks5", "host": "proxy.example.com:8080", "port": "8080" } caller = Caller(header={}, proxy=proxy) caller.get("https://api.example.com/data") mock_get.assert_called_once() assert mock_get.call_args[1]["proxies"] == proxy @patch('corelibs.requests_handling.caller.requests.post') def test_proxy_with_auth(self, mock_post: Mock): """Test proxy with authentication""" mock_response = Mock(spec=requests.Response) mock_post.return_value = mock_response proxy: ProxyConfig = { "type": "socks5", "host": "proxy.example.com:8080", "port": "8080" } caller = Caller(header={}, proxy=proxy) caller.post("https://api.example.com/data", data={"test": "data"}) mock_post.assert_called_once() assert mock_post.call_args[1]["proxies"] == proxy class TestCallerTimeoutHandling: """Tests for timeout parameter handling""" @patch('corelibs.requests_handling.caller.requests.get') def test_timeout_parameter_none_uses_default(self, mock_get: Mock): """Test that None timeout uses the instance default""" mock_response = Mock(spec=requests.Response) mock_get.return_value = mock_response caller = Caller(header={}, timeout=30) # The private __timeout method is called internally caller.get("https://api.example.com/data") mock_get.assert_called_once() assert mock_get.call_args[1]["timeout"] == 30 class TestCallerResponseHandling: """Tests for response handling""" @patch('corelibs.requests_handling.caller.requests.get') def test_response_object_returned_correctly(self, mock_get: Mock): """Test that response object is returned correctly""" mock_response = Mock(spec=requests.Response) mock_response.status_code = 200 mock_response.text = "Success" mock_response.json.return_value = {"status": "ok"} mock_get.return_value = mock_response caller = Caller(header={}) response = caller.get("https://api.example.com/data") assert not isinstance(response, ErrorResponse) assert response.status_code == 200 assert response.text == "Success" assert response.json() == {"status": "ok"} @patch('corelibs.requests_handling.caller.requests.get') def test_response_with_different_status_codes(self, mock_get: Mock): """Test response handling with different status codes""" for status_code in [200, 201, 204, 400, 401, 404, 500]: mock_response = Mock(spec=requests.Response) mock_response.status_code = status_code mock_get.return_value = mock_response caller = Caller(header={}) response = caller.get("https://api.example.com/data") assert not isinstance(response, ErrorResponse) assert response.status_code == status_code # __END__