def test_get_offers_execute(self): setup_mock("execute", responses) opts = deepcopy(self.get_offers_options) execute = { "mboxes": [{ "name": "cart", "index": 1 }], "pageLoad": { "product": { "id": "123", "categoryId": "456" }, "order": { "id": "111", "total": 100 } } } opts["request"]["prefetch"] = None opts["request"]["execute"] = execute opts["request"] = create_delivery_request(opts["request"]) result = self.client.get_offers(opts) self.assertEqual(len(responses.calls), 1) validate_response(self, result)
def test_get_offers_sync(self): setup_mock("default", responses) opts = deepcopy(self.get_offers_options) opts["request"] = create_delivery_request(opts["request"]) result = self.client.get_offers(opts) self.assertEqual(len(responses.calls), 1) validate_response(self, result)
def test_get_attributes_async(self): setup_mock("get_attributes", responses) opts = deepcopy(self.get_attributes_options) prefetch = {"mboxes": [{"name": "feature-flag-a", "index": 2}]} opts["request"]["prefetch"] = prefetch opts["request"]["execute"] = None opts["request"] = create_delivery_request(opts["request"]) shared = {"has_response": False} def verify_callback(attributes_provider): self.assertEqual(len(responses.calls), 1) shared["has_response"] = True self.assertTrue( attributes_provider.get_value("feature-flag-b", "show_feature_y")) self.assertDictEqual( attributes_provider.as_object("feature-flag-b"), { "purchase_experience": "beta2", "show_feature_y": True, "cart_version": 1.3, "customer_survey_value": 102 }) opts["callback"] = verify_callback thread = self.client.get_attributes(["feature-flag-b"], opts) try: thread.get( timeout=5) # Blocks current thread to keep test runner alive except multiprocessing.context.TimeoutError: self.fail("Test case timed out waiting for callback to be invoked") self.assertTrue(shared.get("has_response"))
def test_get_offers_does_remote_request_if_hybrid_request_and_decisioning_engine_not_ready( self): get_offers_opts = { "request": TARGET_REQUEST_DICT, "session_id": "dummy_session", "decisioning_method": DecisioningMethod.HYBRID.value } get_offers_opts["request"]["prefetch"] = { "mboxes": [{ "name": "mbox-feature-flags", "index": 1 }] } get_offers_opts["request"] = create_delivery_request( get_offers_opts["request"]) client_opts = dict(CONFIG) client_opts["decisioning_method"] = DecisioningMethod.SERVER_SIDE.value with patch.object(DeliveryApi, "execute", return_value=create_delivery_response(DELIVERY_RESPONSE)) \ as mock_delivery_api: client = TargetClient.create(client_opts) result = client.get_offers(get_offers_opts) self.assertEqual(mock_delivery_api.call_count, 1) self.assertEqual(result["response"].status, OK) self.assertIsNotNone(result["response"].prefetch) self.assertEqual(result["meta"]["decisioning_method"], DecisioningMethod.SERVER_SIDE.value) self.assertEqual(result["meta"]["remote_mboxes"], []) self.assertEqual(result["meta"]["remote_views"], [])
def test_get_offers_async_error(self): setup_mock("invalid_request", responses, 400) shared = {"has_response": False} client_options = {"client": "bad", "organization_id": "bad"} client = TargetClient.create(client_options) def callback(): self.fail("Error callback should have been invoked") def err_callback(error): self.assertEqual(error.status, 400) self.assertEqual( json.loads(error.body), { "status": 400, "message": "Invalid request: Invalid imsOrg - bad" }) shared["has_response"] = True async_opts = deepcopy(self.get_offers_options) async_opts["request"] = create_delivery_request(async_opts["request"]) async_opts["callback"] = callback async_opts["err_callback"] = err_callback thread = client.get_offers(async_opts) try: thread.get( timeout=5) # Blocks current thread to keep test runner alive except multiprocessing.context.TimeoutError: self.fail("Test case timed out waiting for callback to be invoked") except delivery_api_client.exceptions.ApiException: pass # thread.get re-throws exception, but we"ve already handled inside err_callback self.assertTrue(shared.get("has_response"))
def test_send_notifications_invalid_callback(self): opts = deepcopy(self.send_notifications_options) opts["request"] = create_delivery_request(opts["request"]) opts["callback"] = "Should be a fn" with self.assertRaises(Exception) as err: self.client.send_notifications(opts) self.assertEqual(str(err.exception), "Callback must be a callable function")
def test_get_offers_prefetch(self): setup_mock("prefetch", responses) opts = deepcopy(self.get_offers_options) prefetch = { "mboxes": [{ "name": "cart", "index": 1 }], "pageLoad": { "product": { "id": "123", "categoryId": "456" }, "order": { "id": "111", "total": 100 } }, "views": [{ "product": { "id": "123", "categoryId": "456" }, "order": { "id": "111", "total": 100 } }] } opts["request"]["execute"] = None opts["request"]["prefetch"] = prefetch opts["request"] = create_delivery_request(opts["request"]) result = self.client.get_offers(opts) expected_metrics = [{ "type": "click", "selector": "#parent", "eventToken": "xydsb1vdkls", "analytics": { "payload": { "pe": "tnt", "tnta": "347565:1:0|2,347565:1:0|1" } } }] self.assertEqual(len(responses.calls), 1) validate_response(self, result) delivery_response = result.get("response") validate_metrics(delivery_response.prefetch.metrics, expected_metrics) response_tokens = result.get("response_tokens") self.assertIsNotNone(response_tokens) self.assertEqual(len(response_tokens), 2) for response_token in response_tokens: self.assertIsNotNone(response_token.get("activity.id")) self.assertIsNotNone(response_token.get("experience.id"))
def test_send_notifications_sync(self): setup_mock("notifications", responses) opts = deepcopy(self.send_notifications_options) opts["request"] = create_delivery_request(opts["request"]) result = self.client.send_notifications(opts) self.assertEqual(len(responses.calls), 1) validate_response(self, result)
def create_target_delivery_request(get_offers_opts): """Converts dict representation of get_offers options to TargetDeliveryRequest object""" return TargetDeliveryRequest( request=create_delivery_request(get_offers_opts.get("request")), target_cookie=get_offers_opts.get("targetCookie"), target_location_hint=get_offers_opts.get("targetLocationHint"), consumer_id=get_offers_opts.get("consumerId"), customer_ids=get_offers_opts.get("customerIds"), session_id=get_offers_opts.get("sessionId"), visitor=get_offers_opts.get("visitor"))
def test_get_mbox_names(self): opts = deepcopy(self.get_attributes_options) opts["request"] = create_delivery_request(opts["request"]) result = get_mbox_names(opts["request"]) self.assertTrue(isinstance(result, set)) self.assertEqual(len(result), 4) self.assertTrue("one" in result) self.assertTrue("two" in result) self.assertTrue("three" in result) self.assertTrue("four" in result)
def test_get_offers_target_cookie(self): setup_mock("default", responses) opts = deepcopy(self.get_offers_options) opts["request"] = create_delivery_request(opts["request"]) opts["target_cookie"] = "PC#1.5_9#9596233214|b#2#1296233214" result = self.client.get_offers(opts) self.assertEqual(len(responses.calls), 1) validate_response(self, result) self.assertIsNotNone(result.get("target_cookie")) self.assertIsNotNone(result.get("target_location_hint_cookie"))
def test_send_notifications_invalid_notifications(self): request_opts = { "context": { "channel": "web", }, "notifications": [] } opts = {"request": create_delivery_request(request_opts)} with self.assertRaises(Exception) as err: self.client.send_notifications(opts) self.assertEqual(str(err.exception), "Notifications list is required in request")
def test_get_offers_customer_ids(self): setup_mock("customer_ids", responses) opts = deepcopy(self.get_offers_options) customer_ids = [{ "id": "999", "integrationCode": "foo", "authenticatedState": "authenticated" }] opts["request"]["id"]["customerIds"] = customer_ids opts["request"] = create_delivery_request(opts["request"]) result = self.client.get_offers(opts) self.assertEqual(len(responses.calls), 1) validate_response(self, result)
def test_get_offers_sync_error(self): setup_mock("invalid_request", responses, 400) client_options = {"client": "bad", "organization_id": "bad"} client = TargetClient.create(client_options) get_offers_opts = deepcopy(self.get_offers_options) get_offers_opts["request"] = create_delivery_request( get_offers_opts["request"]) with self.assertRaises( delivery_api_client.exceptions.ApiException) as err: client.get_offers(get_offers_opts) self.assertEqual(json.loads(err.exception.body), { "status": 400, "message": "Invalid request: Invalid imsOrg - bad" })
def test_get_offers_invalid_execute(self): request_opts = { "context": { "channel": "web", }, "execute": { "notmboxes": [] } } opts = {"request": create_delivery_request(request_opts)} with self.assertRaises(Exception) as err: self.client.get_offers(opts) self.assertEqual(str(err.exception), "Either pageLoad or mboxes is required in execute")
def test_get_attributes_from_api_response(self): setup_mock("get_attributes", responses) opts = deepcopy(self.get_attributes_options) opts["request"] = create_delivery_request(opts["request"]) feature_a = self.client.get_attributes(["feature-flag-a"], opts) self.assertEqual( feature_a.get_value("feature-flag-a", "payment_experience"), "legacy") self.assertFalse( feature_a.get_value("feature-flag-a", "show_feature_x")) self.assertEqual( feature_a.get_value("feature-flag-a", "payment_gateway_version"), 2.3) self.assertEqual( feature_a.get_value("feature-flag-a", "customer_feedback_value"), 10) self.assertDictEqual( feature_a.as_object("feature-flag-a"), { "payment_experience": "legacy", "show_feature_x": False, "payment_gateway_version": 2.3, "customer_feedback_value": 10 }) features = self.client.get_attributes( ["feature-flag-a", "feature-flag-b"]) self.assertEqual( features.get_value("feature-flag-b", "purchase_experience"), "beta2") self.assertTrue(features.get_value("feature-flag-b", "show_feature_y")) self.assertEqual(features.get_value("feature-flag-b", "cart_version"), 1.3) self.assertEqual( features.get_value("feature-flag-b", "customer_survey_value"), 102) self.assertDictEqual( features.as_object("feature-flag-b"), { "purchase_experience": "beta2", "show_feature_y": True, "cart_version": 1.3, "customer_survey_value": 102 })
def test_get_attributes_attribute_does_not_exist(self): setup_mock("get_attributes", responses) opts = deepcopy(self.get_attributes_options) prefetch = {"mboxes": [{"name": "feature-flag-a", "index": 2}]} execute = {"mboxes": [{"index": 2, "name": "feature-flag-b"}]} opts["request"]["prefetch"] = prefetch opts["request"]["execute"] = execute opts["request"] = create_delivery_request(opts["request"]) attributes = self.client.get_attributes(["unknown-flag"], opts) with self.assertRaises(Exception) as err: attributes.get_value("unknown-flag", "payment_experience") self.assertEqual( "Attribute 'payment_experience' does not exist for mbox 'unknown-flag'", str(err.exception))
def test_get_offers_invalid_callback(self): request_opts = { "execute": { "mboxes": [{}] }, "prefetch": { "mboxes": [{}] } } opts = { "request": create_delivery_request(request_opts), "callback": "Should be a fn" } with self.assertRaises(Exception) as err: self.client.get_offers(opts) self.assertEqual(str(err.exception), "Callback must be a callable function")
def test_get_offers_hybrid_does_remote_request_if_necessary(self): get_offers_opts = { "request": TARGET_REQUEST_DICT, "session_id": "dummy_session" } get_offers_opts["request"]["prefetch"] = { "mboxes": [{ "name": "mbox-feature-flags", "index": 1 }, { "name": "remote-only-mbox-a", "index": 2 }, { "name": "remote-only-mbox-b", "index": 2 }] } get_offers_opts["request"] = create_delivery_request( get_offers_opts["request"]) client_opts = dict(CONFIG) client_opts["decisioning_method"] = DecisioningMethod.HYBRID.value with patch.object(DeliveryApi, "execute", return_value=create_delivery_response(DELIVERY_RESPONSE)) \ as mock_delivery_api: with patch( "target_decisioning_engine.artifact_provider.urllib3.PoolManager" ) as mock_artifact_provider: artifact_instance = mock_artifact_provider.return_value artifact_response = HTTPResponse( status=OK, body=json.dumps(FEATURE_FLAG_ARTIFACT)) artifact_instance.request.return_value = artifact_response client = TargetClient.create(client_opts) self.assertEqual(mock_artifact_provider.call_count, 1) result = client.get_offers(get_offers_opts) self.assertEqual(mock_delivery_api.call_count, 1) self.assertEqual(result["response"].status, OK) self.assertIsNotNone(result["response"].prefetch) self.assertEqual(result["meta"]["decisioning_method"], DecisioningMethod.HYBRID.value) self.assertSetEqual( set(result["meta"]["remote_mboxes"]), set(["remote-only-mbox-a", "remote-only-mbox-b"])) self.assertEqual(result["meta"]["remote_views"], [])
def test_get_offers_with_preview(self): setup_mock("default", responses) opts = deepcopy(self.get_offers_options) opts["request"]["preview"] = {"token": "sdfdlbys3355"} opts["request"] = create_delivery_request(opts["request"]) request_spy = spy_decorator(ApiClient.request) with patch.object(ApiClient, "request", request_spy): result = self.client.get_offers(opts) # validate Delivery API request expected_req_preview = request_spy.mock.call_args[1]["body"][ "preview"] self.assertEqual(expected_req_preview.get("token"), "sdfdlbys3355") # validate Delivery API response self.assertEqual(len(responses.calls), 1) validate_response(self, result)
def test_get_offers_on_device_partial_returns_206_and_remote_mbox_names( self): get_offers_opts = { "request": TARGET_REQUEST_DICT, "session_id": "dummy_session" } get_offers_opts["request"]["prefetch"] = { "mboxes": [{ "name": "mbox-feature-flags", "index": 1 }, { "name": "remote-only-mbox-a", "index": 2 }, { "name": "remote-only-mbox-b", "index": 2 }] } get_offers_opts["request"] = create_delivery_request( get_offers_opts["request"]) client_opts = dict(CONFIG) client_opts["decisioning_method"] = DecisioningMethod.ON_DEVICE.value with patch( "target_decisioning_engine.artifact_provider.urllib3.PoolManager" ) as mock_artifact_provider: artifact_instance = mock_artifact_provider.return_value artifact_response = HTTPResponse( status=OK, body=json.dumps(FEATURE_FLAG_ARTIFACT)) artifact_instance.request.return_value = artifact_response client = TargetClient.create(client_opts) self.assertEqual(mock_artifact_provider.call_count, 1) result = client.get_offers(get_offers_opts) self.assertEqual(result["response"].status, PARTIAL_CONTENT) self.assertIsNotNone(result["response"].prefetch) self.assertEqual(result["meta"]["decisioning_method"], DecisioningMethod.ON_DEVICE.value) self.assertSetEqual( set(result["meta"]["remote_mboxes"]), set(["remote-only-mbox-a", "remote-only-mbox-b"])) self.assertEqual(result["meta"]["remote_views"], [])
def test_has_remote_dependency_mboxes_and_views(self): request_opts = { "execute": { "mboxes": [{ "name": "mbox1" }, { "name": "mbox2" }] }, "prefetch": { "mboxes": [{ "name": "mbox3" }], "views": [{ "name": "view1" }, { "name": "view2" }, { "name": "view3" }] } } request = create_delivery_request(request_opts) remote_mboxes = ["mbox1"] local_mboxes = ["mbox2"] remote_views = ["view1"] local_views = ["view2"] artifact = DecisioningArtifact({ "remoteMboxes": remote_mboxes, "localMboxes": local_mboxes, "remoteViews": remote_views, "localViews": local_views }) result = has_remote_dependency(artifact, request) self.assertEqual(result.get("remote_needed"), True) self.assertEqual(set(result.get("remote_mboxes")), set(["mbox1", "mbox3"])) self.assertEqual(set(result.get("remote_views")), set(["view1", "view3"]))
def test_get_attributes_add_mbox_to_delivery_request(self): setup_mock("get_attributes", responses) opts = deepcopy(self.get_attributes_options) prefetch = {"mboxes": [{"name": "feature-flag-a", "index": 2}]} opts["request"]["prefetch"] = prefetch opts["request"]["execute"] = None opts["request"] = create_delivery_request(opts["request"]) attributes = self.client.get_attributes(["feature-flag-b"], opts) self.assertTrue( attributes.get_value("feature-flag-b", "show_feature_y")) self.assertDictEqual( attributes.as_object("feature-flag-b"), { "purchase_experience": "beta2", "show_feature_y": True, "cart_version": 1.3, "customer_survey_value": 102 })
def test_add_mboxes_to_request_adds_execute(self): opts = deepcopy(self.get_attributes_options) execute = { "mboxes": [] } opts["request"]["prefetch"] = None opts["request"]["execute"] = execute opts["request"] = create_delivery_request(opts["request"]) mboxes_to_add = ["mbox-foo", "mbox-bar", "mbox-baz"] add_mboxes_to_request( mboxes_to_add, opts["request"], "execute" ) self.assertEqual(len(opts["request"].execute.mboxes), 3) for index, mbox in enumerate(opts["request"].execute.mboxes): self.assertEqual(mbox.index, index + 1) self.assertEqual(mbox.name, mboxes_to_add[index])
def test_get_offers_async(self): setup_mock("default", responses) shared = {"has_response": False} async_opts = deepcopy(self.get_offers_options) async_opts["request"] = create_delivery_request(async_opts["request"]) def verify_callback(result): self.assertEqual(len(responses.calls), 1) validate_response(self, result) shared["has_response"] = True async_opts["callback"] = verify_callback thread = self.client.get_offers(async_opts) try: thread.get( timeout=5) # Blocks current thread to keep test runner alive except multiprocessing.context.TimeoutError: self.fail("Test case timed out waiting for callback to be invoked") self.assertTrue(shared.get("has_response"))
def test_get_offers_with_trace(self): setup_mock("debug_trace", responses) opts = deepcopy(self.get_offers_options) opts["request"]["trace"] = { "authorizationToken": "token", "usage": { "a": "b", "c": "d" } } opts["request"] = create_delivery_request(opts["request"]) request_spy = spy_decorator(ApiClient.request) with patch.object(ApiClient, "request", request_spy): result = self.client.get_offers(opts) # validate Delivery API request expected_req_trace = request_spy.mock.call_args[1]["body"]["trace"] self.assertTrue(isinstance(expected_req_trace, dict)) self.assertEqual(expected_req_trace.get("authorizationToken"), "token") self.assertEqual(expected_req_trace.get("usage"), { "a": "b", "c": "d" }) # validate Delivery API response self.assertEqual(len(responses.calls), 1) validate_response(self, result) if not result.get("response").execute.mboxes: self.fail("Expected execute mboxes in DeliveryResponse") for mbox in result.get("response").execute.mboxes: self.assertIsNotNone(mbox.trace) if not result.get("response").prefetch.mboxes: self.fail("Expected prefetch mboxes in DeliveryResponse") for mbox in result.get("response").prefetch.mboxes: self.assertIsNotNone(mbox.trace)
def test_add_mboxes_to_request_no_duplicates_preserves_existing( self): opts = deepcopy(self.get_attributes_options) prefetch = { "mboxes": [ { "name": "mbox-foo", "index": 6 }, { "name": "mbox-jab", "index": 2 } ] } opts["request"]["prefetch"] = prefetch opts["request"]["execute"] = None opts["request"] = create_delivery_request(opts["request"]) mboxes_to_add = ["mbox-foo", "mbox-bar", "mbox-baz"] add_mboxes_to_request( mboxes_to_add, opts["request"], "prefetch" ) self.assertEqual(len(opts["request"].prefetch.mboxes), 4) resulting_mboxes = opts["request"].prefetch.mboxes self.assertEqual(resulting_mboxes[0].index, 6) self.assertEqual(resulting_mboxes[0].name, "mbox-foo") self.assertEqual(resulting_mboxes[1].index, 2) self.assertEqual(resulting_mboxes[1].name, "mbox-jab") self.assertEqual(resulting_mboxes[2].index, 7) self.assertEqual(resulting_mboxes[2].name, "mbox-bar") self.assertEqual(resulting_mboxes[3].index, 8) self.assertEqual(resulting_mboxes[3].name, "mbox-baz")
def test_get_offers_raises_exception_if_client_configured_as_server_side_but_request_made_with_on_device( self): get_offers_opts = { "request": TARGET_REQUEST_DICT, "session_id": "dummy_session", "decisioning_method": DecisioningMethod.ON_DEVICE.value } get_offers_opts["request"]["prefetch"] = { "mboxes": [{ "name": "mbox-feature-flags", "index": 1 }] } get_offers_opts["request"] = create_delivery_request( get_offers_opts["request"]) client_opts = dict(CONFIG) client_opts["decisioning_method"] = DecisioningMethod.SERVER_SIDE.value client = TargetClient.create(client_opts) with self.assertRaises(Exception) as err: client.get_offers(get_offers_opts) self.assertEqual(str(err.exception), DECISIONING_ENGINE_NOT_READY)
from target_tools.enums import DecisioningMethod from target_tools.tests.delivery_request_setup import create_delivery_request from target_tools.tests.helpers import read_json_file from target_decisioning_engine.constants import OK ARTIFACT_AB_SIMPLE_FILE = "../../target_decisioning_engine/tests/schema/artifacts/TEST_ARTIFACT_AB_SIMPLE.json" ARTIFACT_BLANK_FILE = "../../target_decisioning_engine/tests/schema/artifacts/TEST_ARTIFACT_BLANK.json" CURRENT_DIR = os.path.dirname(__file__) ARTIFACT_AB_SIMPLE = read_json_file(CURRENT_DIR, ARTIFACT_AB_SIMPLE_FILE) ARTIFACT_BLANK = read_json_file(CURRENT_DIR, ARTIFACT_BLANK_FILE) TARGET_REQUEST = create_delivery_request({ "prefetch": { "mboxes": [{ "name": "mbox-something", "index": 1 }] } }) REQUEST_BASE = { "id": { "tntId": "338e3c1e51f7416a8e1ccba4f81acea0.28_0", "marketingCloudVisitorId": "07327024324407615852294135870030620007" }, "context": { "channel": "web", "address": { "url": "http://adobe.com" }, "userAgent":
from delivery_api_client import Metric from delivery_api_client import MetricType from target_tools.utils import to_dict from target_tools.tests.helpers import expect_to_match_object from target_tools.tests.delivery_request_setup import create_delivery_request from target_decisioning_engine.notification_provider import NotificationProvider TARGET_REQUEST = create_delivery_request({ "context": { "channel": ChannelType.WEB, "address": { "url": "http://local-target-test:8080/" }, "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:73.0) Gecko/20100101 Firefox/73.0" }, "prefetch": { "mboxes": [{ "name": "mbox-something", "index": 1 }] } }) TARGET_REQUEST.id = VisitorId() class TestNotificationProvider(unittest.TestCase): """TestNotificationProvider""" def setUp(self): self.mock_notify = Mock()