From fa227c04e2ac9e61afe777ec71d2c85ed1c87101 Mon Sep 17 00:00:00 2001 From: jyothish6190 Date: Thu, 27 Oct 2022 12:00:58 +0530 Subject: [PATCH 1/2] feat: Update client libraries with PSP Merchant Management API --- sift/client.py | 131 ++++++++++++- tests/test_client.py | 448 ++++++++++++++++++++++++++++++------------- 2 files changed, 446 insertions(+), 133 deletions(-) diff --git a/sift/client.py b/sift/client.py index adf690e..dd9565f 100644 --- a/sift/client.py +++ b/sift/client.py @@ -2,12 +2,13 @@ See: https://siftscience.com/docs/references/events-api """ +import decimal import json import requests import requests.auth import sys if sys.version_info[0] < 3: - import urllib.request, urllib.parse, urllib.error + import six.moves.urllib as urllib _UNICODE_STRING = str else: import urllib.parse @@ -26,6 +27,11 @@ def _quote_path(s): # optional arg to override this return urllib.parse.quote(s, '') +class DecimalEncoder(json.JSONEncoder): + def default(self, o): + if isinstance(o, decimal.Decimal): + return (str(o),) + return super(DecimalEncoder, self).default(o) class Client(object): @@ -79,6 +85,7 @@ def track( return_score=False, return_action=False, return_workflow_status=False, + return_route_info=False, force_workflow_run=False, abuse_types=None, timeout=None, @@ -106,6 +113,9 @@ def track( include the status of any workflow run as a result of the tracked event. + return_route_info: Whether to get the route information from the Workflow Decision. + This parameter must be used with the return_workflow_status query parameter. + force_workflow_run: TODO:(rlong) Add after Rishabh adds documentation. abuse_types(optional): List of abuse types, specifying for which abuse types a score @@ -152,13 +162,16 @@ def track( if return_workflow_status: params['return_workflow_status'] = 'true' + if return_route_info: + params['return_route_info'] = 'true' + if force_workflow_run: params['force_workflow_run'] = 'true' try: response = self.session.post( path, - data=json.dumps(properties), + data=json.dumps(properties, cls=DecimalEncoder), headers=headers, timeout=timeout, params=params) @@ -723,6 +736,112 @@ def apply_content_decision(self, user_id, content_id, properties, timeout=None): except requests.exceptions.RequestException as e: raise ApiException(str(e), url) + def create_psp_merchant_profile(self, properties, timeout=None): + """Create a new PSP Merchant profile + Args: + properties: A dict of merchant profile data. + Returns + A sift.client.Response object if the call succeeded, else raises an ApiException + """ + + if timeout is None: + timeout = self.timeout + + url = self._psp_merchant_url(self.account_id) + + try: + return Response(self.session.post( + url, + data=json.dumps(properties), + auth=requests.auth.HTTPBasicAuth(self.api_key, ''), + headers={'Content-type': 'application/json', + 'Accept': '*/*', + 'User-Agent': self._user_agent()}, + timeout=timeout)) + + except requests.exceptions.RequestException as e: + raise ApiException(str(e), url) + + def update_psp_merchant_profile(self, merchant_id, properties, timeout=None): + """Update already existing PSP Merchant profile + Args: + merchant_id: id of merchant + properties: A dict of merchant profile data. + Returns + A sift.client.Response object if the call succeeded, else raises an ApiException + """ + + if timeout is None: + timeout = self.timeout + + url = self._psp_merchant_id_url(self.account_id, merchant_id) + + try: + return Response(self.session.put( + url, + data=json.dumps(properties), + auth=requests.auth.HTTPBasicAuth(self.api_key, ''), + headers={'Content-type': 'application/json', + 'Accept': '*/*', + 'User-Agent': self._user_agent()}, + timeout=timeout)) + + except requests.exceptions.RequestException as e: + raise ApiException(str(e), url) + + def get_psp_merchant_profiles(self, batch_token=None, batch_size=None, timeout=None): + """Gets all PSP merchant profiles. + + Returns: + A sift.client.Response object if the call succeeded. + Otherwise, raises an ApiException. + """ + + if timeout is None: + timeout = self.timeout + + url = self._psp_merchant_url(self.account_id) + params = {} + + if batch_size: + params['batch_size'] = batch_size + + if batch_token: + params['batch_token'] = batch_token + try: + return Response(self.session.get( + url, + auth=requests.auth.HTTPBasicAuth(self.api_key, ''), + headers={'User-Agent': self._user_agent()}, + params=params, + timeout=timeout)) + + except requests.exceptions.RequestException as e: + raise ApiException(str(e), url) + + def get_a_psp_merchant_profile(self, merchant_id, timeout=None): + """Gets a PSP merchant profile using merchant id. + + Returns: + A sift.client.Response object if the call succeeded. + Otherwise, raises an ApiException. + """ + + if timeout is None: + timeout = self.timeout + + url = self._psp_merchant_id_url(self.account_id, merchant_id) + + try: + return Response(self.session.get( + url, + auth=requests.auth.HTTPBasicAuth(self.api_key, ''), + headers={'User-Agent': self._user_agent()}, + timeout=timeout)) + except requests.exceptions.RequestException as e: + raise ApiException(str(e), url) + + def _user_agent(self): return 'SiftScience/v%s sift-python/%s' % (sift.version.API_VERSION, sift.version.VERSION) @@ -773,6 +892,14 @@ def _content_apply_decisions_url(self, account_id, user_id, content_id): return (API3_URL + '/v3/accounts/%s/users/%s/content/%s/decisions' % (_quote_path(account_id), _quote_path(user_id), _quote_path(content_id))) + def _psp_merchant_url(self, account_id): + return (self.url + '/v3/accounts/%s/psp_management/merchants' % + (_quote_path(account_id))) + + def _psp_merchant_id_url(self, account_id, merchant_id): + return (self.url + '/v3/accounts/%s/psp_management/merchants/%s' % + (_quote_path(account_id), _quote_path(merchant_id))) + class Response(object): diff --git a/tests/test_client.py b/tests/test_client.py index 6c0b8f1..58c5883 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,13 +1,17 @@ import datetime -import warnings import json -import mock -import sift -import unittest import sys +import unittest +import warnings +from decimal import Decimal + +import mock import requests.exceptions + +import sift + if sys.version_info[0] < 3: - import urllib.request, urllib.parse, urllib.error + import six.moves.urllib as urllib else: import urllib.parse @@ -16,7 +20,7 @@ def valid_transaction_properties(): return { '$buyer_user_id': '123456', '$seller_user_id': '654321', - '$amount': 1253200, + '$amount': Decimal('1253200.0'), '$currency_code': 'USD', '$time': int(datetime.datetime.now().strftime('%s')), '$transaction_id': 'my_transaction_id', @@ -42,6 +46,56 @@ def valid_label_properties(): } +def valid_psp_merchant_properties(): + return { + "$id": "api-key-1", + "$name": "Wonderful Payments Inc.", + "$description": "Wonderful Payments payment provider.", + "$address": { + "$name": "Alany", + "$address_1": "Big Payment blvd, 22", + "$address_2": "apt, 8", + "$city": "New Orleans", + "$region": "NA", + "$country": "US", + "$zipcode": "76830", + "$phone": "0394888320", + }, + "$category": "1002", + "$service_level": "Platinum", + "$status": "active", + "$risk_profile": { + "$level": "low", + "$score": 10 + } + } + + +def valid_psp_merchant_properties_response(): + return """{ + "id":"api-key-1", + "name": "Wonderful Payments Inc.", + "description": "Wonderful Payments payment provider.", + "category": "1002", + "service_level": "Platinum", + "status": "active", + "risk_profile": { + "level": "low", + "score": "10" + }, + "address": { + "name": "Alany", + "address_1": "Big Payment blvd, 22", + "address_2": "apt, 8", + "city": "New Orleans", + "region": "NA", + "country": "US", + "zipcode": "76830", + "phone": "0394888320" + } + }""" + + def score_response_json(): return """{ "status": 0, @@ -73,6 +127,24 @@ def score_response_json(): }""" +def workflow_statuses_json(): + return """{ + "route" : { + "name" : "my route" + }, + "history": [ + { + "app": "decision", + "name": "Order Looks OK", + "state": "running", + "config": { + "decision_id": "order_looks_ok_payment_abuse" + } + } + ] + }""" + + # A sample response from the /{version}/users/{userId}/score API. USER_SCORE_RESPONSE_JSON = """{ "status": 0, @@ -179,13 +251,13 @@ def test_global_api_key(self): client2 = sift.Client(local_api_key) # test that global api key is assigned - assert(client1.api_key == sift.api_key) + assert (client1.api_key == sift.api_key) # test that local api key is assigned - assert(client2.api_key == local_api_key) + assert (client2.api_key == local_api_key) client2 = sift.Client() # test that client2 is assigned a new object with global api_key - assert(client2.api_key == sift.api_key) + assert (client2.api_key == sift.api_key) def test_constructor_requires_valid_api_key(self): self.assertRaises(TypeError, sift.Client, None) @@ -232,9 +304,9 @@ def test_event_ok(self): timeout=mock.ANY, params={}) self.assertIsInstance(response, sift.client.Response) - assert(response.is_ok()) - assert(response.api_status == 0) - assert(response.api_error_message == "OK") + assert (response.is_ok()) + assert (response.api_status == 0) + assert (response.api_error_message == "OK") def test_event_with_timeout_param_ok(self): event = '$transaction' @@ -255,9 +327,9 @@ def test_event_with_timeout_param_ok(self): timeout=test_timeout, params={}) self.assertIsInstance(response, sift.client.Response) - assert(response.is_ok()) - assert(response.api_status == 0) - assert(response.api_error_message == "OK") + assert (response.is_ok()) + assert (response.api_status == 0) + assert (response.api_error_message == "OK") def test_score_ok(self): mock_response = mock.Mock() @@ -274,11 +346,11 @@ def test_score_ok(self): headers=mock.ANY, timeout=mock.ANY) self.assertIsInstance(response, sift.client.Response) - assert(response.is_ok()) - assert(response.api_error_message == "OK") - assert(response.body['score'] == 0.85) - assert(response.body['scores']['content_abuse']['score'] == 0.14) - assert(response.body['scores']['payment_abuse']['score'] == 0.97) + assert (response.is_ok()) + assert (response.api_error_message == "OK") + assert (response.body['score'] == 0.85) + assert (response.body['scores']['content_abuse']['score'] == 0.14) + assert (response.body['scores']['payment_abuse']['score'] == 0.97) def test_score_with_timeout_param_ok(self): test_timeout = 5 @@ -296,11 +368,11 @@ def test_score_with_timeout_param_ok(self): headers=mock.ANY, timeout=test_timeout) self.assertIsInstance(response, sift.client.Response) - assert(response.is_ok()) - assert(response.api_error_message == "OK") - assert(response.body['score'] == 0.85) - assert(response.body['scores']['content_abuse']['score'] == 0.14) - assert(response.body['scores']['payment_abuse']['score'] == 0.97) + assert (response.is_ok()) + assert (response.api_error_message == "OK") + assert (response.body['score'] == 0.85) + assert (response.body['scores']['content_abuse']['score'] == 0.14) + assert (response.body['scores']['payment_abuse']['score'] == 0.97) def test_get_user_score_ok(self): """Test the GET /{version}/users/{userId}/score API, i.e. client.get_user_score() @@ -320,12 +392,12 @@ def test_get_user_score_ok(self): headers=mock.ANY, timeout=test_timeout) self.assertIsInstance(response, sift.client.Response) - assert(response.is_ok()) - assert(response.api_error_message == "OK") - assert(response.body['entity_id'] == '12345') - assert(response.body['scores']['content_abuse']['score'] == 0.14) - assert(response.body['scores']['payment_abuse']['score'] == 0.97) - assert('latest_decisions' in response.body) + assert (response.is_ok()) + assert (response.api_error_message == "OK") + assert (response.body['entity_id'] == '12345') + assert (response.body['scores']['content_abuse']['score'] == 0.14) + assert (response.body['scores']['payment_abuse']['score'] == 0.97) + assert ('latest_decisions' in response.body) def test_get_user_score_with_abuse_types_ok(self): """Test the GET /{version}/users/{userId}/score?abuse_types=... API, i.e. client.get_user_score() @@ -347,12 +419,12 @@ def test_get_user_score_with_abuse_types_ok(self): headers=mock.ANY, timeout=test_timeout) self.assertIsInstance(response, sift.client.Response) - assert(response.is_ok()) - assert(response.api_error_message == "OK") - assert(response.body['entity_id'] == '12345') - assert(response.body['scores']['content_abuse']['score'] == 0.14) - assert(response.body['scores']['payment_abuse']['score'] == 0.97) - assert('latest_decisions' in response.body) + assert (response.is_ok()) + assert (response.api_error_message == "OK") + assert (response.body['entity_id'] == '12345') + assert (response.body['scores']['content_abuse']['score'] == 0.14) + assert (response.body['scores']['payment_abuse']['score'] == 0.97) + assert ('latest_decisions' in response.body) def test_rescore_user_ok(self): """Test the POST /{version}/users/{userId}/score API, i.e. client.rescore_user() @@ -372,12 +444,12 @@ def test_rescore_user_ok(self): headers=mock.ANY, timeout=test_timeout) self.assertIsInstance(response, sift.client.Response) - assert(response.is_ok()) - assert(response.api_error_message == "OK") - assert(response.body['entity_id'] == '12345') - assert(response.body['scores']['content_abuse']['score'] == 0.14) - assert(response.body['scores']['payment_abuse']['score'] == 0.97) - assert('latest_decisions' in response.body) + assert (response.is_ok()) + assert (response.api_error_message == "OK") + assert (response.body['entity_id'] == '12345') + assert (response.body['scores']['content_abuse']['score'] == 0.14) + assert (response.body['scores']['payment_abuse']['score'] == 0.97) + assert ('latest_decisions' in response.body) def test_rescore_user_with_abuse_types_ok(self): """Test the POST /{version}/users/{userId}/score?abuse_types=... API, i.e. client.rescore_user() @@ -399,12 +471,12 @@ def test_rescore_user_with_abuse_types_ok(self): headers=mock.ANY, timeout=test_timeout) self.assertIsInstance(response, sift.client.Response) - assert(response.is_ok()) - assert(response.api_error_message == "OK") - assert(response.body['entity_id'] == '12345') - assert(response.body['scores']['content_abuse']['score'] == 0.14) - assert(response.body['scores']['payment_abuse']['score'] == 0.97) - assert('latest_decisions' in response.body) + assert (response.is_ok()) + assert (response.api_error_message == "OK") + assert (response.body['entity_id'] == '12345') + assert (response.body['scores']['content_abuse']['score'] == 0.14) + assert (response.body['scores']['payment_abuse']['score'] == 0.97) + assert ('latest_decisions' in response.body) def test_sync_score_ok(self): event = '$transaction' @@ -428,12 +500,41 @@ def test_sync_score_ok(self): timeout=mock.ANY, params={'return_score': 'true', 'abuse_types': 'payment_abuse,content_abuse,legacy'}) self.assertIsInstance(response, sift.client.Response) - assert(response.is_ok()) - assert(response.api_status == 0) - assert(response.api_error_message == "OK") - assert(response.body['score_response']['score'] == 0.85) - assert(response.body['score_response']['scores']['content_abuse']['score'] == 0.14) - assert(response.body['score_response']['scores']['payment_abuse']['score'] == 0.97) + assert (response.is_ok()) + assert (response.api_status == 0) + assert (response.api_error_message == "OK") + assert (response.body['score_response']['score'] == 0.85) + assert (response.body['score_response']['scores']['content_abuse']['score'] == 0.14) + assert (response.body['score_response']['scores']['payment_abuse']['score'] == 0.97) + + def test_sync_workflow_ok(self): + event = '$transaction' + mock_response = mock.Mock() + mock_response.content = ('{"status": 0, "error_message": "OK", "workflow_statuses": %s}' + % workflow_statuses_json()) + mock_response.json.return_value = json.loads(mock_response.content) + mock_response.status_code = 200 + mock_response.headers = response_with_data_header() + with mock.patch.object(self.sift_client.session, 'post') as mock_post: + mock_post.return_value = mock_response + response = self.sift_client.track( + event, + valid_transaction_properties(), + return_workflow_status=True, + return_route_info=True, + abuse_types=['payment_abuse', 'content_abuse', 'legacy']) + mock_post.assert_called_with( + 'https://api.siftscience.com/v205/events', + data=mock.ANY, + headers=mock.ANY, + timeout=mock.ANY, + params={'return_workflow_status': 'true', 'return_route_info': 'true', + 'abuse_types': 'payment_abuse,content_abuse,legacy'}) + self.assertIsInstance(response, sift.client.Response) + assert (response.is_ok()) + assert (response.api_status == 0) + assert (response.api_error_message == "OK") + assert (response.body['workflow_statuses']['route']['name'] == 'my route') def test_get_decisions_fails(self): with self.assertRaises(ValueError): @@ -484,8 +585,8 @@ def test_get_decisions(self): timeout=3) self.assertIsInstance(response, sift.client.Response) - assert(response.is_ok()) - assert(response.body['data'][0]['id'] == 'block_user') + assert (response.is_ok()) + assert (response.body['data'][0]['id'] == 'block_user') def test_get_decisions_entity_session(self): mock_response = mock.Mock() @@ -531,19 +632,19 @@ def test_get_decisions_entity_session(self): timeout=3) self.assertIsInstance(response, sift.client.Response) - assert(response.is_ok()) - assert(response.body['data'][0]['id'] == 'block_session') + assert (response.is_ok()) + assert (response.body['data'][0]['id'] == 'block_session') def test_apply_decision_to_user_ok(self): user_id = '54321' mock_response = mock.Mock() apply_decision_request = { - 'decision_id': 'user_looks_ok_legacy', - 'source': 'MANUAL_REVIEW', - 'analyst': 'analyst@biz.com', - 'description': 'called user and verified account', - 'time': 1481569575 - } + 'decision_id': 'user_looks_ok_legacy', + 'source': 'MANUAL_REVIEW', + 'analyst': 'analyst@biz.com', + 'description': 'called user and verified account', + 'time': 1481569575 + } apply_decision_response_json = """ { "entity": { @@ -569,17 +670,17 @@ def test_apply_decision_to_user_ok(self): auth=mock.ANY, data=data, headers=mock.ANY, timeout=mock.ANY) self.assertIsInstance(response, sift.client.Response) - assert(response.body['entity']['type'] == 'user') - assert(response.http_status_code == 200) - assert(response.is_ok()) + assert (response.body['entity']['type'] == 'user') + assert (response.http_status_code == 200) + assert (response.is_ok()) def test_validate_no_user_id_string_fails(self): apply_decision_request = { - 'decision_id': 'user_looks_ok_legacy', - 'source': 'MANUAL_REVIEW', - 'analyst': 'analyst@biz.com', - 'description': 'called user and verified account', - } + 'decision_id': 'user_looks_ok_legacy', + 'source': 'MANUAL_REVIEW', + 'analyst': 'analyst@biz.com', + 'description': 'called user and verified account', + } with self.assertRaises(TypeError): self.sift_client._validate_apply_decision_request(apply_decision_request, 123) @@ -659,10 +760,10 @@ def test_apply_decision_to_order_ok(self): order_id = '43210' mock_response = mock.Mock() apply_decision_request = { - 'decision_id': 'order_looks_bad_payment_abuse', - 'source': 'AUTOMATED_RULE', - 'time': 1481569575 - } + 'decision_id': 'order_looks_bad_payment_abuse', + 'source': 'AUTOMATED_RULE', + 'time': 1481569575 + } apply_decision_response_json = """ { @@ -689,19 +790,19 @@ def test_apply_decision_to_order_ok(self): 'https://api3.siftscience.com/v3/accounts/ACCT/users/%s/orders/%s/decisions' % (user_id, order_id), auth=mock.ANY, data=data, headers=mock.ANY, timeout=mock.ANY) self.assertIsInstance(response, sift.client.Response) - assert(response.is_ok()) - assert(response.http_status_code == 200) - assert(response.body['entity']['type'] == 'order') + assert (response.is_ok()) + assert (response.http_status_code == 200) + assert (response.body['entity']['type'] == 'order') def test_apply_decision_to_session_ok(self): user_id = '54321' session_id = 'gigtleqddo84l8cm15qe4il' mock_response = mock.Mock() apply_decision_request = { - 'decision_id': 'session_looks_bad_ato', - 'source': 'AUTOMATED_RULE', - 'time': 1481569575 - } + 'decision_id': 'session_looks_bad_ato', + 'source': 'AUTOMATED_RULE', + 'time': 1481569575 + } apply_decision_response_json = """ { @@ -728,19 +829,19 @@ def test_apply_decision_to_session_ok(self): 'https://api3.siftscience.com/v3/accounts/ACCT/users/%s/sessions/%s/decisions' % (user_id, session_id), auth=mock.ANY, data=data, headers=mock.ANY, timeout=mock.ANY) self.assertIsInstance(response, sift.client.Response) - assert(response.is_ok()) - assert(response.http_status_code == 200) - assert(response.body['entity']['type'] == 'login') + assert (response.is_ok()) + assert (response.http_status_code == 200) + assert (response.body['entity']['type'] == 'login') def test_apply_decision_to_content_ok(self): user_id = '54321' content_id = 'listing-1231' mock_response = mock.Mock() apply_decision_request = { - 'decision_id': 'content_looks_bad_content_abuse', - 'source': 'AUTOMATED_RULE', - 'time': 1481569575 - } + 'decision_id': 'content_looks_bad_content_abuse', + 'source': 'AUTOMATED_RULE', + 'time': 1481569575 + } apply_decision_response_json = """ { @@ -767,9 +868,9 @@ def test_apply_decision_to_content_ok(self): 'https://api3.siftscience.com/v3/accounts/ACCT/users/%s/content/%s/decisions' % (user_id, content_id), auth=mock.ANY, data=data, headers=mock.ANY, timeout=mock.ANY) self.assertIsInstance(response, sift.client.Response) - assert(response.is_ok()) - assert(response.http_status_code == 200) - assert(response.body['entity']['type'] == 'create_content') + assert (response.is_ok()) + assert (response.http_status_code == 200) + assert (response.body['entity']['type'] == 'create_content') def test_label_user_ok(self): user_id = '54321' @@ -794,9 +895,9 @@ def test_label_user_ok(self): 'https://api.siftscience.com/v205/users/%s/labels' % user_id, data=data, headers=mock.ANY, timeout=mock.ANY, params={}) self.assertIsInstance(response, sift.client.Response) - assert(response.is_ok()) - assert(response.api_status == 0) - assert(response.api_error_message == "OK") + assert (response.is_ok()) + assert (response.api_status == 0) + assert (response.api_error_message == "OK") def test_label_user_with_timeout_param_ok(self): user_id = '54321' @@ -823,9 +924,9 @@ def test_label_user_with_timeout_param_ok(self): 'https://api.siftscience.com/v205/users/%s/labels' % user_id, data=data, headers=mock.ANY, timeout=test_timeout, params={}) self.assertIsInstance(response, sift.client.Response) - assert(response.is_ok()) - assert(response.api_status == 0) - assert(response.api_error_message == "OK") + assert (response.is_ok()) + assert (response.api_status == 0) + assert (response.api_error_message == "OK") def test_unlabel_user_ok(self): user_id = '54321' @@ -840,7 +941,7 @@ def test_unlabel_user_ok(self): timeout=mock.ANY, params={'api_key': self.test_key, 'abuse_type': 'account_abuse'}) self.assertIsInstance(response, sift.client.Response) - assert(response.is_ok()) + assert (response.is_ok()) def test_unicode_string_parameter_support(self): # str is unicode in python 3, so no need to check as this was covered @@ -856,15 +957,15 @@ def test_unicode_string_parameter_support(self): with mock.patch.object(self.sift_client.session, 'post') as mock_post: mock_post.return_value = mock_response - assert(self.sift_client.track( + assert (self.sift_client.track( '$transaction', valid_transaction_properties())) - assert(self.sift_client.label( + assert (self.sift_client.label( user_id, valid_label_properties())) with mock.patch.object(self.sift_client.session, 'get') as mock_get: mock_get.return_value = mock_response - assert(self.sift_client.score( + assert (self.sift_client.score( user_id, abuse_types=['payment_abuse', 'content_abuse'])) def test_unlabel_user_with_special_chars_ok(self): @@ -880,7 +981,7 @@ def test_unlabel_user_with_special_chars_ok(self): timeout=mock.ANY, params={'api_key': self.test_key}) self.assertIsInstance(response, sift.client.Response) - assert(response.is_ok()) + assert (response.is_ok()) def test_label_user__with_special_chars_ok(self): user_id = '54321=.-_+@:&^%!$' @@ -909,9 +1010,9 @@ def test_label_user__with_special_chars_ok(self): timeout=mock.ANY, params={}) self.assertIsInstance(response, sift.client.Response) - assert(response.is_ok()) - assert(response.api_status == 0) - assert(response.api_error_message == "OK") + assert (response.is_ok()) + assert (response.api_status == 0) + assert (response.api_error_message == "OK") def test_score__with_special_user_id_chars_ok(self): user_id = '54321=.-_+@:&^%!$' @@ -929,11 +1030,11 @@ def test_score__with_special_user_id_chars_ok(self): headers=mock.ANY, timeout=mock.ANY) self.assertIsInstance(response, sift.client.Response) - assert(response.is_ok()) - assert(response.api_error_message == "OK") - assert(response.body['score'] == 0.85) - assert(response.body['scores']['content_abuse']['score'] == 0.14) - assert(response.body['scores']['payment_abuse']['score'] == 0.97) + assert (response.is_ok()) + assert (response.api_error_message == "OK") + assert (response.body['score'] == 0.85) + assert (response.body['scores']['content_abuse']['score'] == 0.14) + assert (response.body['scores']['payment_abuse']['score'] == 0.97) def test_exception_during_track_call(self): warnings.simplefilter("always") @@ -981,15 +1082,15 @@ def test_return_actions_on_track(self): params={'return_action': 'true'}) self.assertIsInstance(response, sift.client.Response) - assert(response.is_ok()) - assert(response.api_status == 0) - assert(response.api_error_message == "OK") + assert (response.is_ok()) + assert (response.api_status == 0) + assert (response.api_error_message == "OK") actions = response.body["score_response"]['actions'] - assert(actions) - assert(actions[0]['action']) - assert(actions[0]['action']['id'] == 'freds_action') - assert(actions[0]['triggers']) + assert (actions) + assert (actions[0]['action']) + assert (actions[0]['action']['id'] == 'freds_action') + assert (actions[0]['triggers']) def test_get_workflow_status(self): mock_response = mock.Mock() @@ -1046,8 +1147,8 @@ def test_get_workflow_status(self): headers=mock.ANY, auth=mock.ANY, timeout=3) self.assertIsInstance(response, sift.client.Response) - assert(response.is_ok()) - assert(response.body['state'] == 'running') + assert (response.is_ok()) + assert (response.body['state'] == 'running') def test_get_user_decisions(self): mock_response = mock.Mock() @@ -1077,8 +1178,8 @@ def test_get_user_decisions(self): headers=mock.ANY, auth=mock.ANY, timeout=mock.ANY) self.assertIsInstance(response, sift.client.Response) - assert(response.is_ok()) - assert(response.body['decisions']['payment_abuse']['decision']['id'] == 'user_decision') + assert (response.is_ok()) + assert (response.body['decisions']['payment_abuse']['decision']['id'] == 'user_decision') def test_get_order_decisions(self): mock_response = mock.Mock() @@ -1115,9 +1216,9 @@ def test_get_order_decisions(self): headers=mock.ANY, auth=mock.ANY, timeout=mock.ANY) self.assertIsInstance(response, sift.client.Response) - assert(response.is_ok()) - assert(response.body['decisions']['payment_abuse']['decision']['id'] == 'decision7') - assert(response.body['decisions']['promotion_abuse']['decision']['id'] == 'good_order') + assert (response.is_ok()) + assert (response.body['decisions']['payment_abuse']['decision']['id'] == 'decision7') + assert (response.body['decisions']['promotion_abuse']['decision']['id'] == 'good_order') def test_get_session_decisions(self): mock_response = mock.Mock() @@ -1147,8 +1248,8 @@ def test_get_session_decisions(self): headers=mock.ANY, auth=mock.ANY, timeout=mock.ANY) self.assertIsInstance(response, sift.client.Response) - assert(response.is_ok()) - assert(response.body['decisions']['account_takeover']['decision']['id'] == 'session_decision') + assert (response.is_ok()) + assert (response.body['decisions']['account_takeover']['decision']['id'] == 'session_decision') def test_get_content_decisions(self): mock_response = mock.Mock() @@ -1178,8 +1279,8 @@ def test_get_content_decisions(self): headers=mock.ANY, auth=mock.ANY, timeout=mock.ANY) self.assertIsInstance(response, sift.client.Response) - assert(response.is_ok()) - assert(response.body['decisions']['content_abuse']['decision']['id'] == 'content_looks_bad_content_abuse') + assert (response.is_ok()) + assert (response.body['decisions']['content_abuse']['decision']['id'] == 'content_looks_bad_content_abuse') def test_provided_session(self): session = mock.Mock() @@ -1196,6 +1297,91 @@ def test_provided_session(self): client.track(event, valid_transaction_properties()) session.post.assert_called_once() + def test_get_psp_merchant_profile(self): + """Test the GET /{version}/accounts/{accountId}/scorepsp_management/merchants?batch_type=...""" + test_timeout = 5 + mock_response = mock.Mock() + mock_response.content = valid_psp_merchant_properties_response() + mock_response.json.return_value = json.loads(mock_response.content) + mock_response.status_code = 200 + mock_response.headers = response_with_data_header() + with mock.patch.object(self.sift_client.session, 'get') as mock_post: + mock_post.return_value = mock_response + response = self.sift_client.get_psp_merchant_profiles( + timeout=test_timeout) + mock_post.assert_called_with( + 'https://api.siftscience.com/v3/accounts/ACCT/psp_management/merchants', + params={}, + headers=mock.ANY, auth=mock.ANY, + timeout=test_timeout) + self.assertIsInstance(response, sift.client.Response) + assert ('address' in response.body) + + def test_get_psp_merchant_profile_id(self): + """Test the GET /{version}/accounts/{accountId}/scorepsp_management/merchants/{merchantId} + """ + test_timeout = 5 + mock_response = mock.Mock() + mock_response.content = valid_psp_merchant_properties_response() + mock_response.json.return_value = json.loads(mock_response.content) + mock_response.status_code = 200 + mock_response.headers = response_with_data_header() + with mock.patch.object(self.sift_client.session, 'get') as mock_post: + mock_post.return_value = mock_response + response = self.sift_client.get_a_psp_merchant_profile( + merchant_id='api-key-1', timeout=test_timeout) + mock_post.assert_called_with( + 'https://api.siftscience.com/v3/accounts/ACCT/psp_management/merchants/api-key-1', + headers=mock.ANY, + auth=mock.ANY, + timeout=test_timeout) + self.assertIsInstance(response, sift.client.Response) + assert ('address' in response.body) + + def test_create_psp_merchant_profile(self): + mock_response = mock.Mock() + mock_response.content = valid_psp_merchant_properties_response() + mock_response.json.return_value = json.loads(mock_response.content) + mock_response.status_code = 200 + mock_response.headers = response_with_data_header() + + with mock.patch.object(self.sift_client.session, 'post') as mock_post: + mock_post.return_value = mock_response + + response = self.sift_client.create_psp_merchant_profile( + valid_psp_merchant_properties()) + mock_post.assert_called_with( + 'https://api.siftscience.com/v3/accounts/ACCT/psp_management/merchants', + data=json.dumps(valid_psp_merchant_properties()), + headers=mock.ANY, + auth=mock.ANY, + timeout=mock.ANY) + + self.assertIsInstance(response, sift.client.Response) + assert ('address' in response.body) + + def test_update_psp_merchant_profile(self): + mock_response = mock.Mock() + mock_response.content = valid_psp_merchant_properties_response() + mock_response.json.return_value = json.loads(mock_response.content) + mock_response.status_code = 200 + mock_response.headers = response_with_data_header() + + with mock.patch.object(self.sift_client.session, 'put') as mock_post: + mock_post.return_value = mock_response + + response = self.sift_client.update_psp_merchant_profile('api-key-1', + valid_psp_merchant_properties()) + mock_post.assert_called_with( + 'https://api.siftscience.com/v3/accounts/ACCT/psp_management/merchants/api-key-1', + data=json.dumps(valid_psp_merchant_properties()), + headers=mock.ANY, + auth=mock.ANY, + timeout=mock.ANY) + + self.assertIsInstance(response, sift.client.Response) + assert ('address' in response.body) + def main(): unittest.main() From af235967e84d31be3a1daffadbc351cd724f84f0 Mon Sep 17 00:00:00 2001 From: sbogolii-sift Date: Mon, 7 Nov 2022 17:16:45 +0200 Subject: [PATCH 2/2] API-6867: Updated version --- CHANGES.md | 3 +++ sift/version.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index fe23ca9..fbb1207 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,6 @@ +5.2.0 2022-11-07 +- Update PSP Merchant Management API + 5.1.0 2022-06-22 - Added return_route_info query parameter - Fixed decimal amount json serialization bug diff --git a/sift/version.py b/sift/version.py index 3435dbb..a3aeddc 100644 --- a/sift/version.py +++ b/sift/version.py @@ -1,2 +1,2 @@ -VERSION = '5.1.0' +VERSION = '5.2.0' API_VERSION = '205'