# Path: sygnal/apnstruncate.py
# def json_encode(payload) -> bytes:
#     return json.dumps(payload, ensure_ascii=False).encode()
# 
# def truncate(payload: Dict[str, Any], max_length: int = 2048) -> Dict[str, Any]:
#     """
#     Truncate APNs fields to make the payload fit within the max length
#     specified.
#     Only truncates fields that are safe to do so.
# 
#     Args:
#         payload: nested dict that will be passed to APNs
#         max_length: Maximum length, in bytes, that the payload should occupy
#             when JSON-encoded.
# 
#     Returns:
#         Nested dict which should comply with the maximum length restriction.
# 
#     """
#     payload = payload.copy()
#     if "aps" not in payload:
#         if is_too_long(payload, max_length):
#             raise BodyTooLongException()
#         else:
#             return payload
#     aps = payload["aps"]
# 
#     # first ensure all our choppables are str objects.
#     # We need them to be for truncating to work and this
#     # makes more sense than checking every time.
#     for c in _choppables_for_aps(aps):
#         val = _choppable_get(aps, c)
#         if isinstance(val, bytes):
#             _choppable_put(aps, c, val.decode())
# 
#     # chop off whole unicode characters until it fits (or we run out of chars)
#     while is_too_long(payload, max_length):
#         longest = _longest_choppable(aps)
#         if longest is None:
#             raise BodyTooLongException()
# 
#         txt = _choppable_get(aps, longest)
#         # Note that python's support for this is actually broken on some OSes
#         # (see test_apnstruncate.py)
#         txt = txt[:-1]
#         _choppable_put(aps, longest, txt)
#         payload["aps"] = aps
# 
#     return payload


import string
import unittest
from sygnal.apnstruncate import json_encode, truncate
            OS X, for example, is broken, and counts the number of surrogate pairs.
            I have no great desire to manually parse UTF-8 to work around this since
            it works fine on Linux.
        """
        if len(u"\U0001F430") != 1:
            msg = (
                "Unicode support is broken in your Python binary. "
                + "Truncating messages with multibyte unicode characters will fail."
            )
            self.fail(msg)

    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.assertEqual(
            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.
        """

# Path: sygnal/apnstruncate.py
# def json_encode(payload) -> bytes:
#     return json.dumps(payload, ensure_ascii=False).encode()
# 
# def truncate(payload: Dict[str, Any], max_length: int = 2048) -> Dict[str, Any]:
#     """
#     Truncate APNs fields to make the payload fit within the max length
#     specified.
#     Only truncates fields that are safe to do so.
# 
#     Args:
#         payload: nested dict that will be passed to APNs
#         max_length: Maximum length, in bytes, that the payload should occupy
#             when JSON-encoded.
# 
#     Returns:
#         Nested dict which should comply with the maximum length restriction.
# 
#     """
#     payload = payload.copy()
#     if "aps" not in payload:
#         if is_too_long(payload, max_length):
#             raise BodyTooLongException()
#         else:
#             return payload
#     aps = payload["aps"]
# 
#     # first ensure all our choppables are str objects.
#     # We need them to be for truncating to work and this
#     # makes more sense than checking every time.
#     for c in _choppables_for_aps(aps):
#         val = _choppable_get(aps, c)
#         if isinstance(val, bytes):
#             _choppable_put(aps, c, val.decode())
# 
#     # chop off whole unicode characters until it fits (or we run out of chars)
#     while is_too_long(payload, max_length):
#         longest = _longest_choppable(aps)
#         if longest is None:
#             raise BodyTooLongException()
# 
#         txt = _choppable_get(aps, longest)
#         # Note that python's support for this is actually broken on some OSes
#         # (see test_apnstruncate.py)
#         txt = txt[:-1]
#         _choppable_put(aps, longest, txt)
#         payload["aps"] = aps
# 
#     return payload


# Path: tests/test_apnstruncate.py
import string
import unittest
from sygnal.apnstruncate import json_encode, truncate

            OS X, for example, is broken, and counts the number of surrogate pairs.
            I have no great desire to manually parse UTF-8 to work around this since
            it works fine on Linux.
        """
        if len(u"\U0001F430") != 1:
            msg = (
                "Unicode support is broken in your Python binary. "
                + "Truncating messages with multibyte unicode characters will fail."
            )
            self.fail(msg)

    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.assertEqual(
            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": ""})))