def test_get_offers_server_side(self): expected_result = deepcopy(EXPECTED_PREFETCH_RESULT) expected_result["response"]["edgeHost"] = "mboxedge28.tt.omtrdc.net" expected_result["target_location_hint_cookie"] = { "name": "mboxEdgeCluster", "value": "28", "maxAge": 1860 } expected_result["meta"] = { "decisioning_method": DecisioningMethod.SERVER_SIDE.value } get_offers_opts = { "request": PREFETCH_REQUEST, "session_id": "dummy_session" } client_opts = dict(CONFIG) client_opts["decisioning_method"] = DecisioningMethod.SERVER_SIDE.value with patch.object(DeliveryApi, "execute", return_value=create_delivery_response(DELIVERY_API_RESPONSE)): client = TargetClient.create(client_opts) result = client.get_offers(get_offers_opts) result_dict = to_dict(result) expect_to_match_object(result_dict, expected_result)
def test_send_notifications_display_type(self): event_token = "B8C2FP2IuBgmeJcDfXHjGpNWHtnQtQrJfmRrQugEa2qCnQ9Y9OaLL2gsdrWQTvE54PwSz67rmXWmSnkXpSSS2Q==" options = [ Option(content="<h1>it's firefox</h1>", type=OptionType.HTML, event_token=event_token) ] mbox = MboxResponse(options=options, metrics=[], name="browser-mbox") self.provider.add_notification(mbox) self.provider.send_notifications() time.sleep(1) self.assertEqual(self.mock_notify.call_count, 1) self.assertEqual( len(self.mock_notify.call_args[0][0]["request"].notifications), 1) received = to_dict( self.mock_notify.call_args[0][0]["request"].notifications[0]) expected = { "id": "expect.any(String)", "impressionId": "expect.any(String)", "timestamp": "expect.any(Number)", "type": "display", "mbox": { "name": "browser-mbox" }, "tokens": [ "B8C2FP2IuBgmeJcDfXHjGpNWHtnQtQrJfmRrQugEa2qCnQ9Y9OaLL2gsdrWQTvE54PwSz67rmXWmSnkXpSSS2Q==" ] } expect_to_match_object(received, expected)
def test_get_offers_on_device_decisioning_emits_notifications(self): get_offers_opts = { "request": EXECUTE_REQUEST, "session_id": "dummy_session" } client_opts = dict(CONFIG) client_opts["decisioning_method"] = DecisioningMethod.ON_DEVICE.value client_opts["target_location_hint"] = "28" # send_notifications call with patch.object(DeliveryApi, "execute", return_value=create_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(ARTIFACT_AB_SIMPLE)) 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.assertIsNotNone(result.get("response")) self.assertEqual(result.get("response").status, OK) self.assertIsNotNone(result.get("response").execute) time.sleep(1) # notifications sent async self.assertEqual(mock_delivery_api.call_count, 1) notification_request = to_dict(mock_delivery_api.call_args[0][2]) expect_to_match_object(notification_request, EXPECTED_NOTIFICATION_REQUEST)
def test_send_notifications_no_duplicates(self): event_token = "yYWdmhDasVXGPWlpX1TRZDSAQdPpz2XBromX4n+pX9jf5r+rP39VCIaiiZlXOAYq" content = [{ "type": "insertAfter", "selector": "HTML > BODY > DIV:nth-of-type(1) > H1:nth-of-type(1)", "cssSelector": "HTML > BODY > DIV:nth-of-type(1) > H1:nth-of-type(1)", "content": "<p id='action_insert_15882850825432970'>Better to remain silent and be thought a fool \ than to speak out and remove all doubt.</p>" }] options = [ Option(content=content, type=OptionType.ACTIONS, event_token=event_token) ] metrics = [ Metric(type=MetricType.CLICK, event_token="/GMYvcjhUsR6WVqQElppUw==", selector="#action_insert_15882853393943012") ] mbox = MboxResponse(options=options, metrics=metrics, name="target-global-mbox") self.provider.add_notification(mbox) self.provider.add_notification(mbox) # duplicate self.provider.send_notifications() time.sleep(1) self.assertEqual(self.mock_notify.call_count, 1) self.assertEqual( len(self.mock_notify.call_args[0][0]["request"].notifications), 1) received = to_dict( self.mock_notify.call_args[0][0]["request"].notifications[0]) expected = { "id": "expect.any(String)", "impressionId": "expect.any(String)", "timestamp": "expect.any(Number)", "type": "display", "mbox": { "name": "target-global-mbox" }, "tokens": [ "yYWdmhDasVXGPWlpX1TRZDSAQdPpz2XBromX4n+pX9jf5r+rP39VCIaiiZlXOAYq" ] } expect_to_match_object(received, expected)
def trace_request(self, mode, request_type, mbox_request, context): """Sets request details on self.request :param mode: ("execute"|"prefetch") mode :param request_type: ("mbox"|"view"|"pageLoad") request type :param mbox_request: (delivery_api_client.Model.request_details.RequestDetails| delivery_api_client.Model.mbox_request.MboxRequest) request details :param context: (target_decisioning_engine.types.decisioning_context.DecisioningContext) decisioning context """ self.request = { "pageURL": context.get("page", {}).get("url"), "host": context.get("page", {}).get("domain"), request_type: to_dict(mbox_request) if mbox_request else {} } self.request[request_type]["type"] = mode
def process_rule(self, rule, context, request_type, request_detail, post_processors, tracer): """Uses json logic to evaluate request context against the rules and returns an MboxResponse :param rule: (target_decisioning_engine.types.decisioning_artifact.Rule) rule :param context: (target_decisioning_engine.types.decisioning_context.DecisioningContext) context :param request_type: ( "mbox"|"view"|"pageLoad") request type :param request_detail: (delivery_api_client.Model.request_details.RequestDetails | delivery_api_client.Model.mbox_request.MboxRequest) request details :param post_processors: (list<callable>) post-processors used to process an mbox if needed :param tracer: (target_decisioning_engine.trace_provider.RequestTracer) request tracer :return: (delivery_api_client.Model.mbox_response.MboxResponse) """ rule_context = deepcopy(context) consequence = None page = rule_context.get("page") referring = rule_context.get("referring") if request_detail and request_detail.address: page = create_page_context(request_detail.address) or page referring = create_page_context(request_detail.address) or referring rule_context.update({ "page": to_dict(page), "referring": to_dict(referring), "mbox": to_dict(create_mbox_context(request_detail)), "allocation": compute_allocation(self.client_id, rule.get("meta", {}).get(ACTIVITY_ID), self.visitor_id) }) rule_satisfied = jsonLogic(rule.get("condition"), rule_context) tracer.trace_rule_evaluated(rule, rule_context, rule_satisfied) if rule_satisfied: consequence = create_mbox_response(deepcopy(rule.get("consequence"))) consequence.index = request_detail.index if hasattr(request_detail, "index") else None for post_process_func in post_processors: consequence = post_process_func(rule, consequence, request_type, request_detail, tracer) return consequence
def _fetch_artifact(self, artifact_url): """Fetch artifact from server""" self.perf_tool.time_start(TIMING_ARTIFACT_DOWNLOADED_TOTAL) headers = {} self.logger.debug("{} fetching artifact - {}".format(LOG_TAG, artifact_url)) if self.last_response_etag: headers["If-None-Match"] = self.last_response_etag try: self.perf_tool.time_start(TIMING_ARTIFACT_DOWNLOADED_FETCH) res = self.pool_manager.request(HTTP_GET, artifact_url, headers=headers, retries=self.http_retry) self.perf_tool.time_end(TIMING_ARTIFACT_DOWNLOADED_FETCH) self.logger.debug("{} artifact received - status={}".format(LOG_TAG, res.status)) if res.status == NOT_MODIFIED and self.last_response_data: return self.last_response_data if res.status == FORBIDDEN: raise Exception("Artifact request is not authorized. This is likely due to On-Device-Decisioning " "being disabled in Admin settings. Please enable and try again.") if res.status != OK: raise Exception("Non-200 status code response from artifact request: {}".format(res.status)) self.perf_tool.time_start(TIMING_ARTIFACT_READ_JSON) response_data = json.loads(res.data) self.perf_tool.time_end(TIMING_ARTIFACT_READ_JSON) etag = res.headers.get("Etag") if etag: self.last_response_data = response_data self.last_response_etag = etag geo = create_or_update_geo_object(geo_data=res.headers) self._emit_new_artifact(response_data, to_dict(geo)) self.perf_tool.time_end(TIMING_ARTIFACT_DOWNLOADED_TOTAL) return response_data except Exception as err: self.logger.error(MESSAGES.get("ARTIFACT_FETCH_ERROR")(str(err))) failure_event = { "artifact_location": artifact_url, "error": err } self.event_emitter(ARTIFACT_DOWNLOAD_FAILED, failure_event) return None
def test_get_offers_valid_on_device_decisioning_response(self): get_offers_opts = { "request": PREFETCH_REQUEST, "session_id": "dummy_session" } client_opts = dict(CONFIG) client_opts["decisioning_method"] = DecisioningMethod.ON_DEVICE.value client_opts["target_location_hint"] = "28" 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(ARTIFACT_AB_SIMPLE)) artifact_instance.request.return_value = artifact_response with patch.object(DeliveryApi, "execute", return_value=create_delivery_response(LOCATION_HINT_RESPONSE)): client = TargetClient.create(client_opts) result = client.get_offers(get_offers_opts) result_dict = to_dict(result) expect_to_match_object(result_dict, EXPECTED_PREFETCH_RESULT)
def __init__(self, config, target_options, artifact_trace): """ :param config: (target_decisioning_engine.types.decisioning_config.DecisioningConfig) config :param target_options: (target_decisioning_engine.types.target_delivery_request.TargetDeliveryRequest) options :param artifact_trace: (dict) artifact trace """ self.config = config self.target_options = target_options self.artifact_trace = artifact_trace self.client_code = config.client self.session_id = target_options.session_id self.request = target_options.request self.show_traces = self.request.trace is not None self.profile = None tnt_id_parts = self.request.id.tnt_id.split(".") if self.request.id and is_string(self.request.id.tnt_id) \ else [None, None] tnt_id = tnt_id_parts[0] profile_location = tnt_id_parts[1] if len(tnt_id_parts) >= 2 else None visitor_id = to_dict(self.request.id) visitor_id["tntId"] = tnt_id visitor_id["profileLocation"] = profile_location self.profile = {"visitorId": visitor_id}
def validate_metrics(result_metrics, expected_metrics): """Validate metrics""" result_metrics = to_dict(result_metrics) for index, expected_metric in enumerate(expected_metrics): expect_to_match_object(result_metrics[index], expected_metric)