AD-SearchAPI 1.0.2__tar.gz → 1.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AD-SearchAPI
3
- Version: 1.0.2
3
+ Version: 1.0.3
4
4
  Summary: A Python client library for the Search API
5
5
  Home-page: https://github.com/AntiChrist-Coder/search_api_library
6
6
  Author: Search API Team
@@ -40,7 +40,7 @@ Acquire your API key through @ADSearchEngine_bot on Telegram.
40
40
 
41
41
  ## Installation
42
42
 
43
- pip install search-api
43
+ pip install AD-SearchAPI
44
44
 
45
45
  ## Quick Start
46
46
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AD-SearchAPI
3
- Version: 1.0.2
3
+ Version: 1.0.3
4
4
  Summary: A Python client library for the Search API
5
5
  Home-page: https://github.com/AntiChrist-Coder/search_api_library
6
6
  Author: Search API Team
@@ -40,7 +40,7 @@ Acquire your API key through @ADSearchEngine_bot on Telegram.
40
40
 
41
41
  ## Installation
42
42
 
43
- pip install search-api
43
+ pip install AD-SearchAPI
44
44
 
45
45
  ## Quick Start
46
46
 
@@ -5,7 +5,7 @@ Acquire your API key through @ADSearchEngine_bot on Telegram.
5
5
 
6
6
  ## Installation
7
7
 
8
- pip install search-api
8
+ pip install AD-SearchAPI
9
9
 
10
10
  ## Quick Start
11
11
 
@@ -1,5 +1,7 @@
1
1
  import json
2
2
  import re
3
+ import gzip
4
+ import io
3
5
  from datetime import date
4
6
  from decimal import Decimal
5
7
  from typing import Dict, List, Optional, Union
@@ -15,6 +17,12 @@ import logging
15
17
  from requests import Session, Response
16
18
  from requests.exceptions import RequestException, Timeout, ConnectionError, HTTPError
17
19
 
20
+ try:
21
+ import brotli
22
+ BROTLI_AVAILABLE = True
23
+ except ImportError:
24
+ BROTLI_AVAILABLE = False
25
+
18
26
  from .exceptions import (
19
27
  AuthenticationError,
20
28
  InsufficientBalanceError,
@@ -198,6 +206,12 @@ class SearchAPI:
198
206
  def _make_request(
199
207
  self, params: Optional[Dict] = None
200
208
  ) -> Dict:
209
+ """
210
+ Make a GET request to the Search API.
211
+
212
+ Handles gzipped and Brotli-compressed responses explicitly since some APIs
213
+ don't properly set headers for automatic decompression.
214
+ """
201
215
  if params is None:
202
216
  params = {}
203
217
  params['api_key'] = self.config.api_key
@@ -217,18 +231,33 @@ class SearchAPI:
217
231
  )
218
232
  response.raise_for_status()
219
233
 
220
- if not response.text.strip():
234
+ if self.config.debug_mode:
235
+ content_encoding = response.headers.get('Content-Encoding', 'none')
236
+ content_length = response.headers.get('Content-Length', 'unknown')
237
+ logger.debug(f"Response headers - Content-Encoding: {content_encoding}, Content-Length: {content_length}")
238
+ logger.debug(f"Response encoding: {response.encoding}")
239
+
240
+ content_encoding = response.headers.get('Content-Encoding', '')
241
+ response_content = response.content
242
+
243
+ try:
244
+ response_text = self._try_decompress_response(response_content, content_encoding)
245
+ except Exception as e:
246
+ logger.error(f"Failed to decompress/decode response: {str(e)}")
247
+ raise SearchAPIError(f"Failed to decompress/decode response: {str(e)}")
248
+
249
+ if not response_text.strip():
221
250
  if self.config.debug_mode:
222
251
  logger.warning(f"Empty response received from {self.BASE_URL}")
223
252
  return {}
224
253
 
225
254
  try:
226
- result = response.json()
255
+ result = json.loads(response_text)
227
256
  if self.config.debug_mode:
228
257
  logger.debug(f"Response received: {result}")
229
258
  return result
230
259
  except json.JSONDecodeError as e:
231
- logger.error(f"Failed to parse JSON response: {response.text}")
260
+ logger.error(f"Failed to parse JSON response: {response_text}")
232
261
  raise SearchAPIError(f"Invalid JSON response: {str(e)}")
233
262
 
234
263
  except Timeout as e:
@@ -458,4 +487,66 @@ class SearchAPI:
458
487
  domain=domain,
459
488
  results=email_results,
460
489
  total_results=len(email_results)
461
- )
490
+ )
491
+
492
+ def _try_decompress_response(self, response_content: bytes, content_encoding: str) -> str:
493
+ if self.config.debug_mode:
494
+ logger.debug(f"Attempting to decompress content with encoding: {content_encoding}")
495
+ logger.debug(f"Content length: {len(response_content)}")
496
+ if len(response_content) >= 4:
497
+ logger.debug(f"First 4 bytes: {response_content[:4].hex()}")
498
+
499
+ if 'br' in content_encoding.lower() and BROTLI_AVAILABLE:
500
+ try:
501
+ decompressed = brotli.decompress(response_content)
502
+ result = decompressed.decode('utf-8')
503
+ if self.config.debug_mode:
504
+ logger.debug("Successfully decompressed with Brotli")
505
+ return result
506
+ except Exception as e:
507
+ if self.config.debug_mode:
508
+ logger.debug(f"Brotli decompression failed: {str(e)}")
509
+
510
+ if 'gzip' in content_encoding.lower():
511
+ try:
512
+ decompressed = gzip.decompress(response_content)
513
+ result = decompressed.decode('utf-8')
514
+ if self.config.debug_mode:
515
+ logger.debug("Successfully decompressed with gzip")
516
+ return result
517
+ except Exception as e:
518
+ if self.config.debug_mode:
519
+ logger.debug(f"Gzip decompression failed: {str(e)}")
520
+
521
+ if len(response_content) >= 2 and response_content[:2] == b'\x1f\x8b':
522
+ try:
523
+ decompressed = gzip.decompress(response_content)
524
+ result = decompressed.decode('utf-8')
525
+ if self.config.debug_mode:
526
+ logger.debug("Successfully decompressed gzip by magic bytes")
527
+ return result
528
+ except Exception as e:
529
+ if self.config.debug_mode:
530
+ logger.debug(f"Gzip magic bytes decompression failed: {str(e)}")
531
+
532
+ if len(response_content) >= 2 and response_content[:2] == b'\xce\xb2' and BROTLI_AVAILABLE:
533
+ try:
534
+ decompressed = brotli.decompress(response_content)
535
+ result = decompressed.decode('utf-8')
536
+ if self.config.debug_mode:
537
+ logger.debug("Successfully decompressed Brotli by magic bytes")
538
+ return result
539
+ except Exception as e:
540
+ if self.config.debug_mode:
541
+ logger.debug(f"Brotli magic bytes decompression failed: {str(e)}")
542
+
543
+ try:
544
+ result = response_content.decode('utf-8')
545
+ if self.config.debug_mode:
546
+ logger.debug("Successfully decoded as plain text")
547
+ return result
548
+ except Exception as e:
549
+ if self.config.debug_mode:
550
+ logger.debug(f"Plain text decoding failed: {str(e)}")
551
+
552
+ raise SearchAPIError(f"Failed to decompress or decode response content. Content-Encoding: {content_encoding}, Content length: {len(response_content)}")
@@ -108,6 +108,6 @@ class SearchAPIConfig:
108
108
 
109
109
  api_key: str
110
110
  max_retries: int = 3
111
- timeout: int = 30
111
+ timeout: int = 90
112
112
  proxy: Optional[Dict[str, str]] = None # Format: {"http": "http://proxy:port", "https": "https://proxy:port"}
113
113
  debug_mode: bool = False
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="AD-SearchAPI",
5
- version="1.0.2",
5
+ version="1.0.3",
6
6
  packages=find_packages(),
7
7
  install_requires=[
8
8
  "requests>=2.31.0",
@@ -0,0 +1,340 @@
1
+ import pytest
2
+ from unittest.mock import Mock, patch
3
+ from datetime import date
4
+ from decimal import Decimal
5
+
6
+ from search_api import SearchAPI, SearchAPIConfig
7
+ from search_api.exceptions import (
8
+ AuthenticationError,
9
+ ValidationError,
10
+ SearchAPIError,
11
+ )
12
+ from search_api.models import Address, PhoneNumber
13
+
14
+
15
+ @pytest.fixture
16
+ def client():
17
+ return SearchAPI(api_key="test_api_key")
18
+
19
+
20
+ @pytest.fixture
21
+ def mock_response():
22
+ return {
23
+ "name": "John Doe",
24
+ "dob": "1990-01-01",
25
+ "addresses": [
26
+ "123 Main St, New York, NY 10001",
27
+ {
28
+ "street": "456 Park Ave",
29
+ "city": "New York",
30
+ "state": "NY",
31
+ "postal_code": "10022",
32
+ "zestimate": 1500000,
33
+ },
34
+ ],
35
+ "numbers": ["+12125551234", "+12125556789"],
36
+ }
37
+
38
+
39
+ def test_init_with_api_key():
40
+ client = SearchAPI(api_key="test_api_key")
41
+ assert client.config.api_key == "test_api_key"
42
+ assert client.config.base_url == "https://search-api.dev"
43
+
44
+
45
+ def test_init_with_config():
46
+ config = SearchAPIConfig(
47
+ api_key="test_api_key",
48
+ max_retries=5,
49
+ timeout=60,
50
+ base_url="https://custom-api.dev",
51
+ )
52
+ client = SearchAPI(config=config)
53
+ assert client.config == config
54
+
55
+
56
+ def test_init_without_api_key_or_config():
57
+ with pytest.raises(ValueError):
58
+ SearchAPI()
59
+
60
+
61
+ @patch("requests.Session.request")
62
+ def test_search_email_success(mock_request, client, mock_response):
63
+ mock_request.return_value.json.return_value = mock_response
64
+ mock_request.return_value.status_code = 200
65
+
66
+ result = client.search_email("test@example.com")
67
+
68
+ assert result.name == "John Doe"
69
+ assert result.dob == date(1990, 1, 1)
70
+ assert len(result.addresses) == 2
71
+ assert len(result.phone_numbers) == 2
72
+ assert isinstance(result.addresses[0], Address)
73
+ assert isinstance(result.phone_numbers[0], PhoneNumber)
74
+ assert result.addresses[1].zestimate == Decimal("1500000")
75
+
76
+
77
+ @patch("requests.Session.request")
78
+ def test_search_email_invalid_format(client):
79
+ with pytest.raises(ValidationError):
80
+ client.search_email("invalid-email")
81
+
82
+
83
+ @patch("requests.Session.request")
84
+ def test_search_email_api_error(client):
85
+ mock_response = Mock()
86
+ mock_response.status_code = 401
87
+ mock_response.text = "Invalid API key"
88
+ mock_response.json.return_value = {"error": "Invalid API key"}
89
+
90
+ with patch("requests.Session.request", return_value=mock_response):
91
+ with pytest.raises(AuthenticationError):
92
+ client.search_email("test@example.com")
93
+
94
+
95
+ @patch("requests.Session.request")
96
+ def test_search_phone_success(mock_request, client, mock_response):
97
+ mock_request.return_value.json.return_value = mock_response
98
+ mock_request.return_value.status_code = 200
99
+
100
+ result = client.search_phone("+12125551234")
101
+
102
+ assert result.name == "John Doe"
103
+ assert result.dob == date(1990, 1, 1)
104
+ assert len(result.addresses) == 2
105
+ assert len(result.phone_numbers) == 2
106
+ assert isinstance(result.addresses[0], Address)
107
+ assert isinstance(result.phone_numbers[0], PhoneNumber)
108
+ assert result.addresses[1].zestimate == Decimal("1500000")
109
+
110
+
111
+ @patch("requests.Session.request")
112
+ def test_search_phone_invalid_format(client):
113
+ with pytest.raises(ValidationError):
114
+ client.search_phone("invalid-phone")
115
+
116
+
117
+ @patch("requests.Session.request")
118
+ def test_search_domain_success(mock_request, client):
119
+ mock_response = {
120
+ "results": [
121
+ {
122
+ "email": "test1@example.com",
123
+ "name": "John Doe",
124
+ "addresses": ["123 Main St, New York, NY 10001"],
125
+ "phone_numbers": ["+12125551234"],
126
+ },
127
+ {
128
+ "email": "test2@example.com",
129
+ "name": "Jane Smith",
130
+ "addresses": ["456 Park Ave, New York, NY 10022"],
131
+ "phone_numbers": ["+12125556789"],
132
+ },
133
+ ]
134
+ }
135
+ mock_request.return_value.json.return_value = mock_response
136
+ mock_request.return_value.status_code = 200
137
+
138
+ result = client.search_domain("example.com")
139
+
140
+ assert result.domain == "example.com"
141
+ assert result.total_results == 2
142
+ assert len(result.results) == 2
143
+ assert result.results[0].email == "test1@example.com"
144
+ assert result.results[1].email == "test2@example.com"
145
+
146
+
147
+ @patch("requests.Session.request")
148
+ def test_search_domain_major_domain(client):
149
+ with pytest.raises(ValidationError):
150
+ client.search_domain("gmail.com")
151
+
152
+
153
+ @patch("requests.Session.request")
154
+ def test_search_domain_invalid_format(client):
155
+ with pytest.raises(ValidationError):
156
+ client.search_domain("invalid-domain")
157
+
158
+
159
+ def test_format_address(client):
160
+ address = "123 main st, new york, ny 10001"
161
+ formatted = client._format_address(address)
162
+ assert formatted == "123 Main Street, New York, NY 10001"
163
+
164
+
165
+ def test_parse_phone_number(client):
166
+ phone = "+12125551234"
167
+ result = client._parse_phone_number(phone)
168
+ assert isinstance(result, PhoneNumber)
169
+ assert result.number == "+12125551234"
170
+ assert result.is_valid is True
171
+
172
+
173
+ def test_parse_phone_number_invalid(client):
174
+ phone = "invalid-phone"
175
+ result = client._parse_phone_number(phone)
176
+ assert isinstance(result, PhoneNumber)
177
+ assert result.number == "invalid-phone"
178
+ assert result.is_valid is False
179
+
180
+
181
+ @patch("requests.Session.request")
182
+ def test_compression_handling(mock_request, client):
183
+ """Test that the client properly handles gzipped/compressed responses."""
184
+ # Mock response with gzip encoding
185
+ mock_response = Mock()
186
+ mock_response.status_code = 200
187
+ mock_response.headers = {
188
+ 'Content-Encoding': 'gzip',
189
+ 'Content-Length': '1234'
190
+ }
191
+ mock_response.text = '{"name": "John Doe", "email": "test@example.com"}'
192
+ mock_response.json.return_value = {"name": "John Doe", "email": "test@example.com"}
193
+ mock_response.raise_for_status.return_value = None
194
+
195
+ mock_request.return_value = mock_response
196
+
197
+ # Test that the request is made with proper headers
198
+ result = client.search_email("test@example.com")
199
+
200
+ # Verify that Accept-Encoding header is set in the session
201
+ assert "Accept-Encoding" in client.session.headers
202
+ assert "gzip" in client.session.headers["Accept-Encoding"]
203
+
204
+ # Verify that the request was made
205
+ mock_request.assert_called_once()
206
+
207
+ # Verify that the response was properly handled
208
+ assert result.name == "John Doe"
209
+ assert result.email == "test@example.com"
210
+
211
+
212
+ @patch("requests.Session.request")
213
+ def test_gzip_decompression(mock_request, client):
214
+ """Test that gzipped responses are properly decompressed."""
215
+ import gzip
216
+ import json
217
+
218
+ # Create a gzipped JSON response
219
+ original_data = {"name": "Jane Smith", "email": "jane@example.com"}
220
+ json_str = json.dumps(original_data)
221
+ gzipped_content = gzip.compress(json_str.encode('utf-8'))
222
+
223
+ # Mock response with gzipped content
224
+ mock_response = Mock()
225
+ mock_response.status_code = 200
226
+ mock_response.headers = {
227
+ 'Content-Encoding': 'gzip',
228
+ 'Content-Length': str(len(gzipped_content))
229
+ }
230
+ mock_response.content = gzipped_content
231
+ mock_response.text = json_str # This should be the decompressed text
232
+ mock_response.raise_for_status.return_value = None
233
+
234
+ mock_request.return_value = mock_response
235
+
236
+ # Test that gzipped response is properly handled
237
+ result = client.search_email("jane@example.com")
238
+
239
+ # Verify that the response was properly decompressed and parsed
240
+ assert result.name == "Jane Smith"
241
+ assert result.email == "jane@example.com"
242
+
243
+
244
+ @patch("requests.Session.request")
245
+ def test_compression_debug_logging(mock_request, client):
246
+ """Test that compression information is logged in debug mode."""
247
+ # Create client with debug mode enabled
248
+ debug_config = SearchAPIConfig(api_key="test_api_key", debug_mode=True)
249
+ debug_client = SearchAPI(config=debug_config)
250
+
251
+ # Mock response with compression headers
252
+ mock_response = Mock()
253
+ mock_response.status_code = 200
254
+ mock_response.headers = {
255
+ 'Content-Encoding': 'gzip',
256
+ 'Content-Length': '567'
257
+ }
258
+ mock_response.text = '{"name": "Jane Smith"}'
259
+ mock_response.json.return_value = {"name": "Jane Smith"}
260
+ mock_response.raise_for_status.return_value = None
261
+ mock_response.encoding = 'utf-8'
262
+
263
+ mock_request.return_value = mock_response
264
+
265
+ # Test that debug logging includes compression info
266
+ with patch('logging.Logger.debug') as mock_debug:
267
+ debug_client.search_email("test@example.com")
268
+
269
+ # Verify that compression headers were logged
270
+ debug_calls = [call[0][0] for call in mock_debug.call_args_list]
271
+ compression_logged = any('Content-Encoding' in str(call) for call in debug_calls)
272
+ assert compression_logged, "Compression headers should be logged in debug mode"
273
+
274
+
275
+ @patch("requests.Session.request")
276
+ def test_gzip_magic_bytes_detection(mock_request, client):
277
+ """Test that gzipped responses are detected by magic bytes even without Content-Encoding header."""
278
+ import gzip
279
+ import json
280
+
281
+ # Create a gzipped JSON response
282
+ original_data = {"name": "Bob Johnson", "email": "bob@example.com"}
283
+ json_str = json.dumps(original_data)
284
+ gzipped_content = gzip.compress(json_str.encode('utf-8'))
285
+
286
+ # Mock response with gzipped content but no Content-Encoding header
287
+ mock_response = Mock()
288
+ mock_response.status_code = 200
289
+ mock_response.headers = {
290
+ 'Content-Length': str(len(gzipped_content))
291
+ # No Content-Encoding header
292
+ }
293
+ mock_response.content = gzipped_content
294
+ mock_response.text = json_str
295
+ mock_response.raise_for_status.return_value = None
296
+
297
+ mock_request.return_value = mock_response
298
+
299
+ # Test that gzipped response is detected by magic bytes and properly handled
300
+ result = client.search_email("bob@example.com")
301
+
302
+ # Verify that the response was properly decompressed and parsed
303
+ assert result.name == "Bob Johnson"
304
+ assert result.email == "bob@example.com"
305
+
306
+
307
+ @patch("requests.Session.request")
308
+ def test_brotli_decompression(mock_request, client):
309
+ """Test that Brotli-compressed responses are properly decompressed."""
310
+ try:
311
+ import brotli
312
+ except ImportError:
313
+ pytest.skip("brotli library not available")
314
+
315
+ import json
316
+
317
+ # Create a Brotli-compressed JSON response
318
+ original_data = {"name": "Alice Brown", "email": "alice@example.com"}
319
+ json_str = json.dumps(original_data)
320
+ brotli_content = brotli.compress(json_str.encode('utf-8'))
321
+
322
+ # Mock response with Brotli-compressed content
323
+ mock_response = Mock()
324
+ mock_response.status_code = 200
325
+ mock_response.headers = {
326
+ 'Content-Encoding': 'br',
327
+ 'Content-Length': str(len(brotli_content))
328
+ }
329
+ mock_response.content = brotli_content
330
+ mock_response.text = json_str
331
+ mock_response.raise_for_status.return_value = None
332
+
333
+ mock_request.return_value = mock_response
334
+
335
+ # Test that Brotli-compressed response is properly handled
336
+ result = client.search_email("alice@example.com")
337
+
338
+ # Verify that the response was properly decompressed and parsed
339
+ assert result.name == "Alice Brown"
340
+ assert result.email == "alice@example.com"
@@ -1,178 +0,0 @@
1
- import pytest
2
- from unittest.mock import Mock, patch
3
- from datetime import date
4
- from decimal import Decimal
5
-
6
- from search_api import SearchAPI, SearchAPIConfig
7
- from search_api.exceptions import (
8
- AuthenticationError,
9
- ValidationError,
10
- SearchAPIError,
11
- )
12
- from search_api.models import Address, PhoneNumber
13
-
14
-
15
- @pytest.fixture
16
- def client():
17
- return SearchAPI(api_key="test_api_key")
18
-
19
-
20
- @pytest.fixture
21
- def mock_response():
22
- return {
23
- "name": "John Doe",
24
- "dob": "1990-01-01",
25
- "addresses": [
26
- "123 Main St, New York, NY 10001",
27
- {
28
- "street": "456 Park Ave",
29
- "city": "New York",
30
- "state": "NY",
31
- "postal_code": "10022",
32
- "zestimate": 1500000,
33
- },
34
- ],
35
- "numbers": ["+12125551234", "+12125556789"],
36
- }
37
-
38
-
39
- def test_init_with_api_key():
40
- client = SearchAPI(api_key="test_api_key")
41
- assert client.config.api_key == "test_api_key"
42
- assert client.config.base_url == "https://search-api.dev"
43
-
44
-
45
- def test_init_with_config():
46
- config = SearchAPIConfig(
47
- api_key="test_api_key",
48
- max_retries=5,
49
- timeout=60,
50
- base_url="https://custom-api.dev",
51
- )
52
- client = SearchAPI(config=config)
53
- assert client.config == config
54
-
55
-
56
- def test_init_without_api_key_or_config():
57
- with pytest.raises(ValueError):
58
- SearchAPI()
59
-
60
-
61
- @patch("requests.Session.request")
62
- def test_search_email_success(mock_request, client, mock_response):
63
- mock_request.return_value.json.return_value = mock_response
64
- mock_request.return_value.status_code = 200
65
-
66
- result = client.search_email("test@example.com")
67
-
68
- assert result.name == "John Doe"
69
- assert result.dob == date(1990, 1, 1)
70
- assert len(result.addresses) == 2
71
- assert len(result.phone_numbers) == 2
72
- assert isinstance(result.addresses[0], Address)
73
- assert isinstance(result.phone_numbers[0], PhoneNumber)
74
- assert result.addresses[1].zestimate == Decimal("1500000")
75
-
76
-
77
- @patch("requests.Session.request")
78
- def test_search_email_invalid_format(client):
79
- with pytest.raises(ValidationError):
80
- client.search_email("invalid-email")
81
-
82
-
83
- @patch("requests.Session.request")
84
- def test_search_email_api_error(client):
85
- mock_response = Mock()
86
- mock_response.status_code = 401
87
- mock_response.text = "Invalid API key"
88
- mock_response.json.return_value = {"error": "Invalid API key"}
89
-
90
- with patch("requests.Session.request", return_value=mock_response):
91
- with pytest.raises(AuthenticationError):
92
- client.search_email("test@example.com")
93
-
94
-
95
- @patch("requests.Session.request")
96
- def test_search_phone_success(mock_request, client, mock_response):
97
- mock_request.return_value.json.return_value = mock_response
98
- mock_request.return_value.status_code = 200
99
-
100
- result = client.search_phone("+12125551234")
101
-
102
- assert result.name == "John Doe"
103
- assert result.dob == date(1990, 1, 1)
104
- assert len(result.addresses) == 2
105
- assert len(result.phone_numbers) == 2
106
- assert isinstance(result.addresses[0], Address)
107
- assert isinstance(result.phone_numbers[0], PhoneNumber)
108
- assert result.addresses[1].zestimate == Decimal("1500000")
109
-
110
-
111
- @patch("requests.Session.request")
112
- def test_search_phone_invalid_format(client):
113
- with pytest.raises(ValidationError):
114
- client.search_phone("invalid-phone")
115
-
116
-
117
- @patch("requests.Session.request")
118
- def test_search_domain_success(mock_request, client):
119
- mock_response = {
120
- "results": [
121
- {
122
- "email": "test1@example.com",
123
- "name": "John Doe",
124
- "addresses": ["123 Main St, New York, NY 10001"],
125
- "phone_numbers": ["+12125551234"],
126
- },
127
- {
128
- "email": "test2@example.com",
129
- "name": "Jane Smith",
130
- "addresses": ["456 Park Ave, New York, NY 10022"],
131
- "phone_numbers": ["+12125556789"],
132
- },
133
- ]
134
- }
135
- mock_request.return_value.json.return_value = mock_response
136
- mock_request.return_value.status_code = 200
137
-
138
- result = client.search_domain("example.com")
139
-
140
- assert result.domain == "example.com"
141
- assert result.total_results == 2
142
- assert len(result.results) == 2
143
- assert result.results[0].email == "test1@example.com"
144
- assert result.results[1].email == "test2@example.com"
145
-
146
-
147
- @patch("requests.Session.request")
148
- def test_search_domain_major_domain(client):
149
- with pytest.raises(ValidationError):
150
- client.search_domain("gmail.com")
151
-
152
-
153
- @patch("requests.Session.request")
154
- def test_search_domain_invalid_format(client):
155
- with pytest.raises(ValidationError):
156
- client.search_domain("invalid-domain")
157
-
158
-
159
- def test_format_address(client):
160
- address = "123 main st, new york, ny 10001"
161
- formatted = client._format_address(address)
162
- assert formatted == "123 Main Street, New York, NY 10001"
163
-
164
-
165
- def test_parse_phone_number(client):
166
- phone = "+12125551234"
167
- result = client._parse_phone_number(phone)
168
- assert isinstance(result, PhoneNumber)
169
- assert result.number == "+12125551234"
170
- assert result.is_valid is True
171
-
172
-
173
- def test_parse_phone_number_invalid(client):
174
- phone = "invalid-phone"
175
- result = client._parse_phone_number(phone)
176
- assert isinstance(result, PhoneNumber)
177
- assert result.number == "invalid-phone"
178
- assert result.is_valid is False
File without changes
File without changes