Skip to content

Commit 686048d

Browse files
committed
feat(sdk): add basic tests for http client
1 parent 6be8e15 commit 686048d

3 files changed

Lines changed: 272 additions & 0 deletions

File tree

tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Tests package

tests/conftest.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import pytest
2+
from src.thecompaniesapi import Client, HttpClient
3+
4+
5+
@pytest.fixture
6+
def http_client():
7+
"""Fixture providing an HttpClient instance for testing."""
8+
return HttpClient(api_token="test-token")
9+
10+
11+
@pytest.fixture
12+
def http_client_with_visitor():
13+
"""Fixture providing an HttpClient with visitor ID for testing."""
14+
return HttpClient(api_token="test-token", visitor_id="test-visitor-123")
15+
16+
17+
@pytest.fixture
18+
def client():
19+
"""Fixture providing a Client instance for testing."""
20+
return Client(api_token="test-token")
21+
22+
23+
@pytest.fixture
24+
def custom_client():
25+
"""Fixture providing a Client with custom configuration for testing."""
26+
return Client(
27+
api_token="custom-token",
28+
api_url="https://custom.api.com",
29+
visitor_id="custom-visitor",
30+
timeout=120
31+
)

tests/test_client.py

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
import json
2+
import pytest
3+
import responses
4+
from unittest.mock import Mock, patch
5+
6+
from src.thecompaniesapi import Client, HttpClient, ApiError
7+
8+
9+
class TestHttpClient:
10+
"""Test the HttpClient class functionality."""
11+
12+
def test_init_default_params(self):
13+
"""Test HttpClient initialization with default parameters."""
14+
client = HttpClient(api_token="test-token")
15+
16+
assert client.api_token == "test-token"
17+
assert client.api_url == "https://api.thecompaniesapi.com"
18+
assert client.visitor_id is None
19+
assert client.timeout == 300
20+
21+
def test_init_custom_params(self):
22+
"""Test HttpClient initialization with custom parameters."""
23+
client = HttpClient(
24+
api_token="custom-token",
25+
api_url="https://custom.api.com",
26+
visitor_id="visitor-123",
27+
timeout=60
28+
)
29+
30+
assert client.api_token == "custom-token"
31+
assert client.api_url == "https://custom.api.com"
32+
assert client.visitor_id == "visitor-123"
33+
assert client.timeout == 60
34+
35+
def test_setup_default_headers(self):
36+
"""Test that default headers are set correctly."""
37+
client = HttpClient(api_token="test-token", visitor_id="visitor-123")
38+
39+
headers = client.session.headers
40+
assert headers['Content-Type'] == 'application/json'
41+
assert headers['Accept'] == 'application/json'
42+
assert headers['Authorization'] == 'Basic test-token'
43+
assert headers['Tca-Visitor-Id'] == 'visitor-123'
44+
assert 'thecompaniesapi-python-sdk' in headers['User-Agent']
45+
46+
def test_setup_headers_no_visitor_id(self):
47+
"""Test headers when no visitor ID is provided."""
48+
client = HttpClient(api_token="test-token")
49+
50+
headers = client.session.headers
51+
assert 'Tca-Visitor-Id' not in headers # Should not be present when not provided
52+
53+
def test_serialize_query_params(self):
54+
"""Test query parameter serialization."""
55+
client = HttpClient(api_token="test-token")
56+
57+
params = {
58+
"string": "hello",
59+
"number": 42,
60+
"boolean_true": True,
61+
"boolean_false": False,
62+
"list": ["item1", "item2"],
63+
"dict": {"key": "value", "nested": {"deep": "data"}},
64+
"none_value": None
65+
}
66+
67+
result = client._serialize_query_params(params)
68+
69+
assert result["string"] == "hello"
70+
assert result["number"] == "42"
71+
assert result["boolean_true"] == "true"
72+
assert result["boolean_false"] == "false"
73+
assert result["list"] == '%5B%22item1%22%2C%22item2%22%5D' # URL-encoded ["item1","item2"]
74+
assert result["dict"] == '%7B%22key%22%3A%22value%22%2C%22nested%22%3A%7B%22deep%22%3A%22data%22%7D%7D' # URL-encoded {"key":"value","nested":{"deep":"data"}}
75+
assert "none_value" not in result
76+
77+
def test_prepare_url(self):
78+
"""Test URL preparation."""
79+
client = HttpClient(api_token="test-token", api_url="https://api.example.com")
80+
81+
# Test with leading slash
82+
assert client._prepare_url("/v2/health") == "https://api.example.com/v2/health"
83+
84+
# Test without leading slash
85+
assert client._prepare_url("v2/health") == "https://api.example.com/v2/health"
86+
87+
# Test with complex path
88+
assert client._prepare_url("companies/search") == "https://api.example.com/companies/search"
89+
90+
@responses.activate
91+
def test_get_request_success(self):
92+
"""Test successful GET request."""
93+
responses.add(
94+
responses.GET,
95+
"https://api.thecompaniesapi.com/v2/health",
96+
json={"status": "ok"},
97+
status=200
98+
)
99+
100+
client = HttpClient(api_token="test-token")
101+
result = client.get("/v2/health")
102+
103+
assert result == {"status": "ok"}
104+
105+
@responses.activate
106+
def test_get_request_with_params(self):
107+
"""Test GET request with query parameters."""
108+
responses.add(
109+
responses.GET,
110+
"https://api.thecompaniesapi.com/v2/companies",
111+
json={"data": []},
112+
status=200
113+
)
114+
115+
client = HttpClient(api_token="test-token")
116+
params = {"size": 10, "query": ["test"]}
117+
result = client.get("/v2/companies", params=params)
118+
119+
# Check that the request was made with serialized params
120+
assert len(responses.calls) == 1
121+
request_url = responses.calls[0].request.url
122+
assert "size=10" in request_url
123+
assert "query=%255B%2522test%2522%255D" in request_url # Double URL-encoded JSON (our encoding + requests encoding)
124+
125+
@responses.activate
126+
def test_post_request_success(self):
127+
"""Test successful POST request."""
128+
responses.add(
129+
responses.POST,
130+
"https://api.thecompaniesapi.com/v2/companies/search",
131+
json={"data": {"companies": []}},
132+
status=200
133+
)
134+
135+
client = HttpClient(api_token="test-token")
136+
payload = {"query": [{"attribute": "name", "value": "test"}]}
137+
result = client.post("/v2/companies/search", json_data=payload)
138+
139+
assert result == {"data": {"companies": []}}
140+
141+
# Verify the request body
142+
request_body = json.loads(responses.calls[0].request.body)
143+
assert request_body == payload
144+
145+
@responses.activate
146+
def test_request_error_handling(self):
147+
"""Test error handling for HTTP errors."""
148+
responses.add(
149+
responses.GET,
150+
"https://api.thecompaniesapi.com/v2/error",
151+
status=404
152+
)
153+
154+
client = HttpClient(api_token="test-token")
155+
156+
with pytest.raises(ApiError, match="Request failed"):
157+
client.get("/v2/error")
158+
159+
@responses.activate
160+
def test_non_json_response(self):
161+
"""Test handling of non-JSON responses."""
162+
responses.add(
163+
responses.GET,
164+
"https://api.thecompaniesapi.com/v2/text",
165+
body="Plain text response",
166+
status=200
167+
)
168+
169+
client = HttpClient(api_token="test-token")
170+
result = client.get("/v2/text")
171+
172+
assert result == {"data": "Plain text response", "status": 200}
173+
174+
175+
class TestClient:
176+
"""Test the main Client class."""
177+
178+
def test_init_success(self):
179+
"""Test successful client initialization."""
180+
client = Client(api_token="test-token")
181+
182+
assert client.http.api_token == "test-token"
183+
assert isinstance(client.http, HttpClient)
184+
185+
def test_init_no_token_error(self):
186+
"""Test that Client raises error when no API token provided."""
187+
with pytest.raises(ValueError, match="api_token is required"):
188+
Client()
189+
190+
def test_init_with_custom_params(self):
191+
"""Test Client initialization with custom parameters."""
192+
client = Client(
193+
api_token="test-token",
194+
api_url="https://custom.api.com",
195+
visitor_id="visitor-123",
196+
timeout=60
197+
)
198+
199+
assert client.http.api_token == "test-token"
200+
assert client.http.api_url == "https://custom.api.com"
201+
assert client.http.visitor_id == "visitor-123"
202+
assert client.http.timeout == 60
203+
204+
@responses.activate
205+
def test_fetch_api_health(self):
206+
"""Test the example fetch_api_health method."""
207+
responses.add(
208+
responses.GET,
209+
"https://api.thecompaniesapi.com/v2/health",
210+
json={"status": "healthy"},
211+
status=200
212+
)
213+
214+
client = Client(api_token="test-token")
215+
result = client.fetch_api_health()
216+
217+
assert result == {"status": "healthy"}
218+
219+
def test_http_client_delegation(self):
220+
"""Test that Client properly delegates to HttpClient."""
221+
client = Client(api_token="test-token")
222+
223+
# Mock the HttpClient's get method
224+
with patch.object(client.http, 'get', return_value={"mocked": True}) as mock_get:
225+
result = client.fetch_api_health()
226+
227+
mock_get.assert_called_once_with('/v2/health')
228+
assert result == {"mocked": True}
229+
230+
231+
class TestApiError:
232+
"""Test the ApiError exception class."""
233+
234+
def test_api_error_creation(self):
235+
"""Test ApiError can be created and raised."""
236+
error = ApiError("Test error message")
237+
assert str(error) == "Test error message"
238+
239+
with pytest.raises(ApiError, match="Test error message"):
240+
raise error

0 commit comments

Comments
 (0)