Skip to content

Commit 9d8c8de

Browse files
Pull request #30: Developer
Merge in SDK/python_telesign from developer to master Squashed commit of the following: commit 7949c0e Author: Juan Camilo Martinez <jmartinez@telesign.com> Date: Thu Jul 9 19:48:42 2026 +0000 Pull request #31: Update changelog Merge in SDK/python_telesign from feature/EOA15099-1 to developer Squashed commit of the following: commit 9b7c4a461395fb78fd54900ab1f9015d135e5753 Author: Juan Camilo Martinez <jmartinez@telesign.com> Date: Thu Jul 9 11:47:45 2026 -0500 Adding the missing unit tests commit 385f35a Author: Juan Camilo Martinez <jmartinez@telesign.com> Date: Wed Jul 8 16:22:38 2026 +0000 Pull request #29: Added support for Email Intelligence API Merge in SDK/python_telesign from feature/EOA15099 to developer Squashed commit of the following: commit 6a64a1aa9be00a818b3e1bcaa636471eb5a7badf Author: Juan Camilo Martinez <jmartinez@telesign.com> Date: Mon Jul 6 15:04:34 2026 -0500 Added support for Email Intelligence API commit 2fa8bd2 Author: Julian Andres Ramirez <jramirez@telesign.com> Date: Wed Jun 3 14:38:27 2026 +0000 Pull request #28: Added missing unit tests Merge in SDK/python_telesign from feature/EOA-11948 to developer Squashed commit of the following: commit b734a2178a4a9bb1f3d55e0933bfeb324359c0fe Author: Julian Andres Ramirez <jramirez@telesign.com> Date: Tue Jun 2 12:09:39 2026 -0500 Minor fixes and cleanup commit 00b6cdf9c93f652b74cd2d9f0ce034e88b01965c Author: Julian Andres Ramirez <jramirez@telesign.com> Date: Mon Jun 1 17:13:21 2026 -0500 Get phone number from environment variables commit 20e93ab7bbbd0e32ab2f174d6ff71ca2b4c9bcd6 Author: Julian Andres Ramirez <jramirez@telesign.com> Date: Fri May 29 17:49:39 2026 -0500 Added missing unit tests
1 parent 64165d7 commit 9d8c8de

7 files changed

Lines changed: 288 additions & 24 deletions

File tree

RELEASE

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
4.1.0
2+
- Added support for Email Intelligence API
3+
- Adding the missing unit tests
4+
15
4.0.1
26
- Added fix for pkg_resources removed from setuptools 82
37

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
here = path.abspath(path.dirname(__file__))
77

8-
version = "4.0.1"
8+
version = "4.1.0"
99

1010
version_path = path.join(here, "telesign", "_version.py")
1111
with open(version_path, "w") as f:

telesign/score.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
DETECT_HOST = "https://detect.telesign.com"
44
INTELLIGENCE_RESOURCE = "/intelligence/phone"
5+
EMAIL_INTELLIGENCE_RESOURCE = "/intelligence/email"
6+
57

68
class ScoreClient(RestClient):
79
"""
@@ -29,7 +31,22 @@ def score(self, phone_number, account_lifecycle_event, **params):
2931
params["phone_number"] = phone_number
3032
params["account_lifecycle_event"] = account_lifecycle_event
3133

32-
return self.post(
33-
INTELLIGENCE_RESOURCE,
34-
**params
35-
)
34+
return self.post(INTELLIGENCE_RESOURCE, **params)
35+
36+
def email_intelligence(self, email_address, account_lifecycle_event, **params):
37+
"""
38+
Obtain a risk recommendation for this email address, as well as other relevant information using Email Intelligence API.
39+
Required parameters:
40+
- email_address
41+
- account_lifecycle_event ("create", "sign-in", "transact", "update", "delete")
42+
"""
43+
if not email_address:
44+
raise ValueError("email_address cannot be null or empty")
45+
46+
if not account_lifecycle_event:
47+
raise ValueError("account_lifecycle_event cannot be null or empty")
48+
49+
params["email_address"] = email_address
50+
params["account_lifecycle_event"] = account_lifecycle_event
51+
52+
return self.post(EMAIL_INTELLIGENCE_RESOURCE, **params)

tests/test_messaging.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
from __future__ import unicode_literals
2+
import json
3+
import os
4+
from unittest import TestCase, mock
5+
from telesign.messaging import MessagingClient
6+
7+
class TestMessaging(TestCase):
8+
def setUp(self):
9+
self.customer_id = os.getenv('CUSTOMER_ID', 'FFFFFFFF-EEEE-DDDD-1234-AB1234567890')
10+
self.api_key = os.getenv('API_KEY', 'EXAMPLE----TE8sTgg45yusumoN6BYsBVkh+yRJ5czgsnCehZaOYldPJdmFh6NeX8kunZ2zU1YWaUw/0wV6xfw==')
11+
self.phone_number_test = os.getenv('PHONE_NUMBER', 'phone_number')
12+
self.client = MessagingClient(self.customer_id, self.api_key)
13+
14+
def test_messaging_constructor(self):
15+
self.assertEqual(self.client.customer_id, self.customer_id)
16+
self.assertEqual(self.client.api_key, self.api_key)
17+
18+
@mock.patch('telesign.rest.requests.Session.post')
19+
def test_messaging_message(self, mock_post):
20+
expected_response = {
21+
"reference_id": "0123456789ABCDEF0123456789ABCDEF",
22+
"external_id": None,
23+
"status": {
24+
"code": 290,
25+
"description": "Message in progress"
26+
}
27+
}
28+
mock_response = mock.Mock(
29+
status_code=200,
30+
headers={'Content-Type': 'application/json'},
31+
ok=True,
32+
text=json.dumps(expected_response)
33+
)
34+
mock_response.json.return_value = expected_response
35+
mock_post.return_value = mock_response
36+
37+
response = self.client.message(self.phone_number_test, "Hello, this is a test message!", "ARN")
38+
39+
called_url = mock_post.call_args[0][0]
40+
self.assertIn('/v1/messaging', called_url)
41+
called_kwargs = mock_post.call_args[1] if mock_post.call_args else {}
42+
called_data = called_kwargs.get('data', '')
43+
44+
self.assertTrue(called_data, "POST data is empty, expected form parameters")
45+
self.assertEqual(response.headers.get('Content-Type'), 'application/json', "Content-Type args do not match expected")
46+
self.assertEqual(response.status_code, 200, "Status code args do not match expected")
47+
self.assertEqual(response.json, expected_response, "Response does not match expected mock response")
48+
49+
@mock.patch('telesign.rest.requests.Session.get')
50+
def test_messaging_status(self, mock_get):
51+
expected_response = {
52+
"status": {
53+
"updated_on": "2026-05-29T17:38:49.049000Z",
54+
"code": 203,
55+
"description": "Delivered to gateway"
56+
},
57+
"reference_id": "0123456789ABCDEF0123456789ABCDEF"
58+
}
59+
mock_response = mock.Mock(
60+
status_code=200,
61+
headers={'Content-Type': 'application/json'},
62+
ok=True,
63+
text=json.dumps(expected_response)
64+
)
65+
mock_response.json.return_value = expected_response
66+
mock_get.return_value = mock_response
67+
68+
response = self.client.status("0123456789ABCDEF0123456789ABCDEF")
69+
70+
called_url = mock_get.call_args[0][0]
71+
self.assertIn('/v1/messaging/0123456789ABCDEF0123456789ABCDEF', called_url)
72+
73+
self.assertEqual(response.headers.get('Content-Type'), 'application/json', "Content-Type args do not match expected")
74+
self.assertEqual(response.status_code, 200, "Status code args do not match expected")
75+
self.assertEqual(response.json, expected_response, "Response does not match expected mock response")

tests/test_rest.py

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from unittest import TestCase
66
from uuid import UUID
77

8+
import requests
9+
810
from telesign.rest import RestClient
911
from telesign.util import AuthMethod
1012

@@ -269,6 +271,50 @@ def test_delete(self, mock_generate_telesign_headers):
269271
"client.session.delete.call_args args do not match expected")
270272
self.assertEqual(delete_kwargs, expected_delete_kwargs,
271273
"client.session.delete.call_args kwargs do not match expected")
274+
275+
@patch('requests.Session.patch')
276+
@patch('telesign.rest.RestClient.generate_telesign_headers', return_value={})
277+
def test_patch(self, mock_generate_telesign_headers, mock_patch):
278+
test_host = 'https://test.com'
279+
test_resource = '/test/resource'
280+
test_params = {'test': '123_\u03ff_test'}
281+
282+
client = RestClient(self.customer_id, self.api_key, rest_endpoint=test_host)
283+
284+
expected_patch_args = ('https://test.com/test/resource',)
285+
expected_patch_kwargs = {'headers': {}, 'params': 'test=123_%CF%BF_test', 'timeout': client.timeout}
286+
287+
client.patch(test_resource, **test_params)
288+
289+
self.assertEqual(client.session.patch.call_count, 1, "client.session.patch not called")
290+
291+
patch_args, patch_kwargs = client.session.patch.call_args
292+
self.assertEqual(patch_args, expected_patch_args,
293+
"client.session.patch.call_args args do not match expected")
294+
self.assertEqual(patch_kwargs, expected_patch_kwargs,
295+
"client.session.patch.call_args kwargs do not match expected")
296+
297+
@patch("time.time")
298+
def test_create_session(self, mock_time):
299+
mock_time.return_value = 1000
300+
client = RestClient(self.customer_id, self.api_key)
301+
302+
self.assertIsNotNone(client.session)
303+
self.assertEqual(client._session_created_at, 1000)
304+
self.assertIsInstance(client.session, requests.Session)
305+
306+
@patch("time.time")
307+
def test_ensure_session(self, mock_time):
308+
mock_time.return_value = 1000
309+
client = RestClient(self.customer_id, self.api_key, pool_recycle=10)
310+
initial_session = client.session
311+
initial_created_at = client._session_created_at
312+
313+
mock_time.return_value = 1012
314+
client._ensure_session()
315+
316+
self.assertIsNot(client.session, initial_session)
317+
self.assertNotEqual(client._session_created_at, initial_created_at)
272318

273319
@patch('telesign.rest.RestClient.generate_telesign_headers', return_value={})
274320
def test_post_basic_auth(self, mock_generate_telesign_headers):
@@ -295,7 +341,6 @@ def test_post_basic_auth(self, mock_generate_telesign_headers):
295341
def test_session_adapter_is_httpadapter(self):
296342
client = RestClient(self.customer_id, self.api_key)
297343
https_adapter = client.session.adapters["https://"]
298-
import requests
299344
self.assertIsInstance(https_adapter, requests.adapters.HTTPAdapter)
300345

301346
@patch("time.time")
@@ -309,4 +354,6 @@ def test_session_refresh_on_pool_recycle(self, mock_time):
309354
# Force a request (any method calls _ensure_session)
310355
client._ensure_session()
311356
created_at_second = client._session_created_at
312-
self.assertNotEqual(created_at_first, created_at_second)
357+
self.assertNotEqual(created_at_first, created_at_second)
358+
359+

tests/test_score.py

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,86 @@
22
import os
33
from unittest import TestCase, mock
44
from telesign.score import ScoreClient
5+
from urllib.parse import quote
6+
57

68
class TestScoreClient(TestCase):
79
def setUp(self):
8-
self.customer_id = os.getenv('CUSTOMER_ID', 'FFFFFFFF-EEEE-DDDD-1234-AB1234567890')
9-
self.api_key = os.getenv('API_KEY', 'ABC12345yusumoN6BYsBVkh+yRJ5czgsnCehZaOYldPJdmFh6NeX8kunZ2zU1YWaUw/0wV6xfw==')
10-
self.phone_number_test = os.getenv('PHONE_NUMBER', 'phone_number')
10+
self.customer_id = os.getenv(
11+
"CUSTOMER_ID", "FFFFFFFF-EEEE-DDDD-1234-AB1234567890"
12+
)
13+
self.api_key = os.getenv(
14+
"API_KEY",
15+
"ABC12345yusumoN6BYsBVkh+yRJ5czgsnCehZaOYldPJdmFh6NeX8kunZ2zU1YWaUw/0wV6xfw==",
16+
)
17+
self.phone_number_test = os.getenv("PHONE_NUMBER", "11234567890")
1118
self.account_lifecycle_event = "create"
19+
self.email_address_test = os.getenv("EMAIL_ADDRESS", "support@vero-finto.com")
1220

13-
@mock.patch('telesign.rest.requests.Session.post')
21+
@mock.patch("telesign.rest.requests.Session.post")
1422
def test_score_method(self, mock_post):
1523
mock_response = mock.Mock()
1624
mock_response.ok = True
1725
mock_response.status_code = 200
18-
mock_response.headers = {'Content-Type': 'application/json'}
26+
mock_response.headers = {"Content-Type": "application/json"}
1927
mock_response.json.return_value = {
20-
"risk": {
21-
"level": "LOW",
22-
"recommendation": "allow"
23-
}
28+
"risk": {"level": "LOW", "recommendation": "allow"}
2429
}
2530
mock_post.return_value = mock_response
2631

2732
client = ScoreClient(self.customer_id, self.api_key)
2833
response = client.score(self.phone_number_test, self.account_lifecycle_event)
2934

3035
called_url = mock_post.call_args[0][0]
31-
self.assertIn('/intelligence/phone', called_url)
36+
self.assertIn("/intelligence/phone", called_url)
37+
38+
called_kwargs = mock_post.call_args[1] if mock_post.call_args else {}
39+
called_data = called_kwargs.get("data", "")
40+
41+
self.assertTrue(called_data, "POST data is empty, expected form parameters")
42+
self.assertIn(f"phone_number={self.phone_number_test}", called_data)
43+
self.assertIn(
44+
f"account_lifecycle_event={self.account_lifecycle_event}", called_data
45+
)
46+
47+
self.assertEqual(response.status_code, 200)
48+
self.assertTrue(response.ok)
49+
self.assertEqual(response.headers.get("Content-Type"), "application/json")
50+
self.assertIn("risk", response.json)
51+
self.assertEqual(response.json["risk"]["level"], "LOW")
52+
self.assertEqual(response.json["risk"]["recommendation"], "allow")
53+
54+
@mock.patch("telesign.rest.requests.Session.post")
55+
def test_email_intelligence_method(self, mock_post):
56+
mock_response = mock.Mock()
57+
mock_response.ok = True
58+
mock_response.status_code = 200
59+
mock_response.headers = {"Content-Type": "application/json"}
60+
mock_response.json.return_value = {
61+
"risk": {"level": "LOW", "recommendation": "allow"}
62+
}
63+
mock_post.return_value = mock_response
64+
65+
client = ScoreClient(self.customer_id, self.api_key)
66+
response = client.email_intelligence(
67+
self.email_address_test, self.account_lifecycle_event
68+
)
69+
70+
called_url = mock_post.call_args[0][0]
71+
self.assertIn("/intelligence/email", called_url)
3272

3373
called_kwargs = mock_post.call_args[1] if mock_post.call_args else {}
34-
called_data = called_kwargs.get('data', '')
74+
called_data = called_kwargs.get("data", "")
3575

3676
self.assertTrue(called_data, "POST data is empty, expected form parameters")
37-
self.assertIn(f'phone_number={self.phone_number_test}', called_data)
38-
self.assertIn(f'account_lifecycle_event={self.account_lifecycle_event}', called_data)
77+
self.assertIn(f"email_address={quote(self.email_address_test)}", called_data)
78+
self.assertIn(
79+
f"account_lifecycle_event={self.account_lifecycle_event}", called_data
80+
)
3981

4082
self.assertEqual(response.status_code, 200)
4183
self.assertTrue(response.ok)
42-
self.assertEqual(response.headers.get('Content-Type'), 'application/json')
43-
self.assertIn('risk', response.json)
44-
self.assertEqual(response.json['risk']['level'], 'LOW')
45-
self.assertEqual(response.json['risk']['recommendation'], 'allow')
84+
self.assertEqual(response.headers.get("Content-Type"), "application/json")
85+
self.assertIn("risk", response.json)
86+
self.assertEqual(response.json["risk"]["level"], "LOW")
87+
self.assertEqual(response.json["risk"]["recommendation"], "allow")

tests/test_voice.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
from __future__ import unicode_literals
2+
import json
3+
import os
4+
from unittest import TestCase, mock
5+
from telesign.voice import VoiceClient
6+
7+
class TestVoice(TestCase):
8+
def setUp(self):
9+
self.customer_id = os.getenv('CUSTOMER_ID', 'FFFFFFFF-EEEE-DDDD-1234-AB1234567890')
10+
self.api_key = os.getenv('API_KEY', 'EXAMPLE----TE8sTgg45yusumoN6BYsBVkh+yRJ5czgsnCehZaOYldPJdmFh6NeX8kunZ2zU1YWaUw/0wV6xfw==')
11+
self.phone_number_test = os.getenv('PHONE_NUMBER', 'phone_number')
12+
self.client = VoiceClient(self.customer_id, self.api_key)
13+
14+
def test_voice_constructor(self):
15+
self.assertEqual(self.client.customer_id, self.customer_id)
16+
self.assertEqual(self.client.api_key, self.api_key)
17+
18+
@mock.patch('telesign.rest.requests.Session.post')
19+
def test_voice_call(self, mock_post):
20+
expected_response = {
21+
"reference_id": "0123456789ABCDEF0123456789ABCDEF",
22+
"external_id": None,
23+
"status": {
24+
"code": 130,
25+
"description": "Call blocked by TeleSign",
26+
"updated_on": "2026-05-29T21:09:19.378673Z"
27+
},
28+
"voice": {
29+
"caller_id": "+16268723943"
30+
}
31+
}
32+
mock_response = mock.Mock(
33+
status_code=200,
34+
headers={'Content-Type': 'application/json'},
35+
ok=True,
36+
text=json.dumps(expected_response)
37+
)
38+
mock_response.json.return_value = expected_response
39+
mock_post.return_value = mock_response
40+
41+
response = self.client.call(self.phone_number_test, "Hello, this is a test message!", "ARN")
42+
43+
called_url = mock_post.call_args[0][0]
44+
self.assertIn('/v1/voice', called_url)
45+
called_kwargs = mock_post.call_args[1] if mock_post.call_args else {}
46+
called_data = called_kwargs.get('data', '')
47+
48+
self.assertTrue(called_data, "POST data is empty, expected form parameters")
49+
self.assertEqual(response.headers.get('Content-Type'), 'application/json', "Content-Type args do not match expected")
50+
self.assertEqual(response.status_code, 200, "Status code args do not match expected")
51+
self.assertEqual(response.json, expected_response, "Response does not match expected mock response")
52+
53+
@mock.patch('telesign.rest.requests.Session.get')
54+
def test_voice_status(self, mock_get):
55+
expected_response = {
56+
"reference_id": "0123456789ABCDEF0123456789ABCDEF",
57+
"status": {
58+
"code": 100,
59+
"description": "Call answered",
60+
"updated_on": "2026-05-29T17:38:49.049000Z"
61+
},
62+
}
63+
mock_response = mock.Mock(
64+
status_code=201,
65+
headers={'Content-Type': 'application/json'},
66+
ok=True,
67+
text=json.dumps(expected_response)
68+
)
69+
mock_response.json.return_value = expected_response
70+
mock_get.return_value = mock_response
71+
72+
response = self.client.status("0123456789ABCDEF0123456789ABCDEF")
73+
74+
called_url = mock_get.call_args[0][0]
75+
self.assertIn('/v1/voice/0123456789ABCDEF0123456789ABCDEF', called_url)
76+
77+
self.assertEqual(response.headers.get('Content-Type'), 'application/json', "Content-Type args do not match expected")
78+
self.assertEqual(response.status_code, 201, "Status code args do not match expected")
79+
self.assertEqual(response.json, expected_response, "Response does not match expected mock response")

0 commit comments

Comments
 (0)