def test_dont_truncate(self): """ Tests that truncation is not performed if unnecessary. """ # This shouldn't need to be truncated txt = simplestring(20) aps = {"alert": txt} self.assertEqual(txt, truncate(payload_for_aps(aps), 256)["aps"]["alert"])
def test_truncate_alert(self): """ Tests that the 'alert' string field will be truncated when needed. """ overhead = len(json_encode(payload_for_aps({"alert": ""}))) txt = simplestring(10) aps = {"alert": txt} self.assertEquals( txt[:5], truncate(payload_for_aps(aps), overhead + 5)["aps"]["alert"])
def test_truncate_loc_arg(self): """ Tests that the 'alert' 'loc-args' field will be truncated when needed. (Tests with one loc arg) """ overhead = len(json_encode(payload_for_aps({"alert": {"loc-args": [""]}}))) txt = simplestring(10) aps = {"alert": {"loc-args": [txt]}} self.assertEqual( txt[:5], truncate(payload_for_aps(aps), overhead + 5)["aps"]["alert"]["loc-args"][0], )
def test_truncate_string_with_multibyte(self): """ Tests that truncation works as expected on strings containing one multibyte character. """ overhead = len(json_encode(payload_for_aps({"alert": ""}))) txt = u"\U0001F430" + simplestring(30) aps = {"alert": txt} # NB. The number of characters of the string we get is dependent # on the json encoding used. self.assertEquals( txt[:17], truncate(payload_for_aps(aps), overhead + 20)["aps"]["alert"])
def test_truncate_multibyte(self): """ Tests that truncation works as expected on strings containing only multibyte characters. """ overhead = len(json_encode(payload_for_aps({"alert": ""}))) txt = sillystring(30) aps = {"alert": txt} trunc = truncate(payload_for_aps(aps), overhead + 30) # The string is all 4 byte characters so the trunctaed UTF-8 string # should be a multiple of 4 bytes long self.assertEquals(len(trunc["aps"]["alert"].encode()) % 4, 0) # NB. The number of characters of the string we get is dependent # on the json encoding used. self.assertEquals(txt[:7], trunc["aps"]["alert"])
async def _dispatch_notification_unlimited( self, n: Notification, device: Device, context: NotificationContext) -> List[str]: log = NotificationLoggerAdapter(logger, {"request_id": context.request_id}) # The pushkey is kind of secret because you can use it to send push # to someone. # span_tags = {"pushkey": device.pushkey} span_tags: Dict[str, int] = {} with self.sygnal.tracer.start_span( "apns_dispatch", tags=span_tags, child_of=context.opentracing_span) as span_parent: # Before we build the payload, check that the default_payload is not # malformed and reject the pushkey if it is default_payload = {} if device.data: default_payload = device.data.get("default_payload", {}) if not isinstance(default_payload, dict): log.error( "default_payload is malformed, this value must be a dict." ) return [device.pushkey] if n.event_id and not n.type: payload: Optional[Dict[str, Any]] = self._get_payload_event_id_only( n, default_payload) else: payload = self._get_payload_full(n, device, log) if payload is None: # Nothing to do span_parent.log_kv({logs.EVENT: "apns_no_payload"}) return [] prio = 10 if n.prio == "low": prio = 5 shaved_payload = apnstruncate.truncate( payload, max_length=self.MAX_JSON_BODY_SIZE) for retry_number in range(self.MAX_TRIES): try: span_tags = {"retry_num": retry_number} with self.sygnal.tracer.start_span( "apns_dispatch_try", tags=span_tags, child_of=span_parent) as span: assert shaved_payload is not None return await self._dispatch_request( log, span, device, shaved_payload, prio) except TemporaryNotificationDispatchException as exc: retry_delay = self.RETRY_DELAY_BASE * (2**retry_number) if exc.custom_retry_delay is not None: retry_delay = exc.custom_retry_delay log.warning( "Temporary failure, will retry in %d seconds", retry_delay, exc_info=True, ) span_parent.log_kv({ "event": "temporary_fail", "retrying_in": retry_delay }) if retry_number < self.MAX_TRIES - 1: await twisted_sleep( retry_delay, twisted_reactor=self.sygnal.reactor) raise NotificationDispatchException("Retried too many times.")
async def _dispatch_notification_unlimited(self, n, device, context): log = NotificationLoggerAdapter(logger, {"request_id": context.request_id}) # The pushkey is kind of secret because you can use it to send push # to someone. # span_tags = {"pushkey": device.pushkey} span_tags: Dict[str, int] = {} with self.sygnal.tracer.start_span( "apns_dispatch", tags=span_tags, child_of=context.opentracing_span) as span_parent: if n.event_id and not n.type: payload = self._get_payload_event_id_only(n, device) else: payload = self._get_payload_full(n, device, log) if payload is None: # Nothing to do span_parent.log_kv({logs.EVENT: "apns_no_payload"}) return prio = 10 if n.prio == "low": prio = 5 shaved_payload = apnstruncate.truncate( payload, max_length=self.MAX_JSON_BODY_SIZE) for retry_number in range(self.MAX_TRIES): try: log.debug("Trying") span_tags = {"retry_num": retry_number} with self.sygnal.tracer.start_span( "apns_dispatch_try", tags=span_tags, child_of=span_parent) as span: return await self._dispatch_request( log, span, device, shaved_payload, prio) except TemporaryNotificationDispatchException as exc: retry_delay = self.RETRY_DELAY_BASE * (2**retry_number) if exc.custom_retry_delay is not None: retry_delay = exc.custom_retry_delay log.warning( "Temporary failure, will retry in %d seconds", retry_delay, exc_info=True, ) span_parent.log_kv({ "event": "temporary_fail", "retrying_in": retry_delay }) if retry_number == self.MAX_TRIES - 1: raise NotificationDispatchException( "Retried too many times.") from exc else: await twisted_sleep( retry_delay, twisted_reactor=self.sygnal.reactor)