コード例 #1
0
ファイル: zbase.py プロジェクト: cole-brown/veredi-code
class Test_Message_Base(ZestBase):
    def _define_vars(self) -> None:
        '''
        Defines any instance variables with type hinting, docstrs.
        Happens ASAP during unittest.setUp(), before ZestBase.set_up().
        '''
        super()._define_vars()

        # ------------------------------
        # Test ID Generators
        # ------------------------------

        self._msg_id_gen: MonotonicIdGenerator = MonotonicId.generator()
        '''ID generator for creating Mediator messages.'''

        self._user_id_gen: MockUserIdGenerator = UserId.generator(
            unit_testing=True)
        '''Generates repeatable user ids for use in testing.'''

        self._user_key_gen: MockUserKeyGenerator = UserKey.generator(
            unit_testing=True)
        '''Generates repeatable user keys for use in testing.'''

        # ------------------------------
        # Test User
        # ------------------------------

        self._uname: str = 'Tester Jeff'
        '''A user display name for UserID generation.'''

        self._uid: UserId = None
        '''A user id to use.'''

        # TODO: Eventually use a UserKey too.
        # self._ukeygen: str or int or idk = 'something' or 42 or w/e
        # '''An input for user key generation.'''

        self._ukey: Optional[UserKey] = None
        '''A user key to use.'''

        # ------------------------------
        # Serdes / Codec
        # ------------------------------

        self.serdes: JsonSerdes = JsonSerdes()
        '''Serdes to use for testing.'''

        self.codec = Codec()
        '''Codec to use for testing.'''

        # ------------------------------
        # Test Message
        # ------------------------------
        self.payload: Any = None
        '''Payload to use to create `self.message`.'''

        self.message: Message = None
        '''
        Message that should match `self.encoded` and `self.serialized`.
        '''

        self.encoded: Dict = None
        '''
        Encoded dictionary that should match `self.message` and
        `self.serialized`.
        '''

        self.serialized: str = None
        '''
        Serialized string that should match `self.encoded` and
        `self.message`.
        '''

    def set_up(self,
               message_payload: Any,
               message_encoded: Dict,
               message_serialized: str,
               user_id: Optional[str] = None,
               user_key: Optional[str] = None) -> None:
        '''
        Set up base class with class, encoded, and serialzed versions of the
        same message - for testing serialize/deserialize & encode/decode.
        '''
        # ------------------------------
        # User
        # ------------------------------
        if not user_id:
            # Some random UserId value from a run of this test.
            user_id = "147d7414-b7bd-5779-96cb-4cc72b19a514"

        self._user_id_gen.ut_set_up({
            self._uname:
            int(user_id.replace('-', ''), 16),
        })
        self._uid = self._user_id_gen.next(self._uname)

        # TODO: eventually: use a UserKey too.
        # if not user_key:
        #     # Some random UserKey value from a run of this test.
        #     user_key = "TODO: this value".replace('-', ''),

        # self._user_key_gen.ut_set_up({
        #     self._ukeygen: int(
        #         # Some random UserKey value from a run of this test.
        #         "TODO: this value".replace('-', ''),
        #         16),
        #     })
        # self._ukey = self._user_key_gen.next(self._ukey)

        # ------------------------------
        # Serdes/Codec
        # ------------------------------
        self.serdes = JsonSerdes()
        self.codec = Codec()

        # ------------------------------
        # Message
        # ------------------------------
        self.payload = message_payload
        self.encoded = message_encoded
        self.serialized = message_serialized

    def tear_down(self):
        self.serdes = None
        self.codec = None
        self.message = None
        self.payload = None
        self.encoded = None
        self.serialized = None
        self._msg_id_gen = None
        self._user_id_gen = None
        self._uname = None
        self._uid = None
        self._user_key_gen = None
        self._ukey = None

    # -------------------------------------------------------------------------
    # Helpers: Payload
    # -------------------------------------------------------------------------

    def make_payload(self, payload: Optional[str] = None) -> Any:
        '''
        Default base impl just returns 'self.payload'.

        Override if you need a dynamic payload.
        '''
        payload = payload or self.payload
        self.payload = payload
        self.assertTrue(self.payload)
        return self.payload

    # -------------------------------------------------------------------------
    # Helpers: Message
    # -------------------------------------------------------------------------

    def make_context(self, test_name: str) -> UnitTestContext:
        context = UnitTestContext(self, test_name=test_name)
        return context

    def fingerprint_string(self, string: str) -> int:
        '''
        Converts string into an integer for testing equality without caring
        about dictionary ordering.

        Sums up the int value of each character.
        '''
        value = 0
        for each in string:
            if each in (' ', '\t', '\n'):
                # Allow for our pretty-printed string vs serialized output's
                # compact string.
                continue
            # Turn the char into an int.
            value += ord(each)
        return value

    def make_message(self) -> Message:
        '''
        Create the Message object to use for serialization test.
        '''
        # ------------------------------
        # Unique Message ID
        # ------------------------------
        mid = self._msg_id_gen.next()

        # ------------------------------
        # Message Payload
        # ------------------------------
        self.make_payload()

        # ------------------------------
        # Create/Return Actual Message.
        # ------------------------------
        # Create message with the math payload.
        self.message = Message(mid,
                               MsgType.ENCODED,
                               payload=self.payload,
                               user_id=self._uid,
                               user_key=self._ukey,
                               subject=abac.Subject.BROADCAST)
        self.assertTrue(self.message)
        return self.message

    def assertPayloadEqual(self, expected: Any, payload: Any) -> None:
        '''
        Asserts payloads are equal.

        Base implemenation just asserts truthiness are equal. Override if you
        know more about your payloads.
        '''
        # Can't really do generic check, since payload can be anything.
        self.assertEqual(bool(expected), bool(payload))

    def assertMessageEqual(self, expected: Message, message: Message) -> None:
        '''
        Asserts message fields (except payload) to verify `decoded` matches
        `expected`.

        NOTE: Payloads compared via `self.assertPayloadEqual()`.
        '''
        # ------------------------------
        # Do they exist equally?
        # ------------------------------
        exists_expected = bool(expected)
        exists_message = bool(message)
        self.assertEqual(exists_expected, exists_message)

        if expected is None or message is None:
            # Getting here means both are none, since we've asserted they're
            # equal when boolean'd. So this is fine.
            return

        # ------------------------------
        # Do their contents match?
        # ------------------------------
        self.assertEqual(expected.msg_id, message.msg_id)
        self.assertEqual(expected.entity_id, message.entity_id)
        self.assertEqual(expected.user_id, message.user_id)
        self.assertEqual(expected.user_key, message.user_key)
        self.assertEqual(expected.security_subject, message.security_subject)

        # ------------------------------
        # Payload...
        # ------------------------------
        self.assertPayloadEqual(expected.payload, message.payload)

    # -------------------------------------------------------------------------
    # Tests
    # -------------------------------------------------------------------------

    def do_test_init(self) -> None:
        '''
        Just assert some stuff exists.
        '''
        self.assertTrue(self._uid)
        # TODO: eventually use a user key too.
        # self.assertTrue(self._ukey)

        self.assertTrue(self.serdes)
        self.assertTrue(self.codec)

        self.assertTrue(self.encoded)
        self.assertTrue(self.serialized)

        self.make_message()
        self.assertTrue(self.payload)
        self.assertTrue(self.message)

    def do_test_encode(self):
        '''
        Encode `self.message` and assert it is equal to our expected output:
        `self.encoded`.
        '''
        # ------------------------------
        # Create the message to be encoded.
        # ------------------------------
        self.make_message()

        # ------------------------------
        # Encode the message.
        # ------------------------------
        encoded = self.codec.encode(self.message)

        # ------------------------------
        # Compare encoded.
        # ------------------------------

        # Dict is pretty big; just remove max for this assert so the failure
        # message is more helpful.
        old_max = self.maxDiff
        self.maxDiff = None
        self.assertDictEqual(encoded, self.encoded)
        self.maxDiff = old_max

    def do_test_decode(self) -> None:
        '''
        Decode `self.encoded` and assert it is equal to our expected output:
        `self.message`.
        '''
        # ------------------------------
        # Create the message to compare against.
        # ------------------------------
        # Must exactly match what we will decode!
        expected = self.make_message()

        # ------------------------------
        # Decode message.
        # ------------------------------
        decoded = self.codec.decode(
            # We should be able to decode only with the data dictionary.
            None,
            self.encoded)

        self.assertTrue(decoded)

        # ------------------------------
        # Compare.
        # ------------------------------
        self.assertMessageEqual(expected, decoded)

    def do_test_serialize(self) -> None:
        '''
        Serialize `self.message` and assert it is equal to our expected output:
        `self.serialized`.
        '''
        # ------------------------------
        # Create the message to be serialized.
        # ------------------------------
        message = self.make_message()

        # ------------------------------
        # Serialize the message.
        # ------------------------------
        context = self.make_context('test_serialize')
        serialized_stream = self.serdes.serialize(message, self.codec, context)
        serialized = serialized_stream.getvalue()

        # Dict is pretty big; just remove max for this assert so the failure
        # message is more helpful.
        old_max = self.maxDiff
        self.maxDiff = None
        # This probably won't work. We don't order dicts before printing, I
        # don't think...
        self.assertEqual(self.fingerprint_string(serialized),
                         self.fingerprint_string(self.serialized))
        self.maxDiff = old_max

    def do_test_deserialize(self):
        '''
        Deserialize `self.serialized` and assert it is equal to our expected
        output: `self.message`.
        '''
        # ------------------------------
        # Create the message to compare against.
        # ------------------------------
        # Must exactly match what we will decode!
        expected = self.make_message()

        # ------------------------------
        # Deserialize message.
        # ------------------------------
        decoded = self.serdes.deserialize(
            self.serialized, self.codec, self.make_context('test_deserialize'))

        self.assertTrue(decoded)

        # ------------------------------
        # Compare.
        # ------------------------------
        self.assertMessageEqual(expected, decoded)
コード例 #2
0
    def _serialize_prep(self,
                        data:    SerializeTypes,
                        codec:   Codec,
                        context: 'VerediContext') -> Mapping[str, Any]:
        '''
        Tries to turn the various possibilities for data (list, dict, etc) into
        something ready for json to serialize.
        '''
        self._log_data_processing(self.dotted,
                                  "Serialize preparation...",
                                  context=context)
        serialized = None
        if null_or_none(data):
            self._log_data_processing(self.dotted,
                                      "No data to prep.",
                                      context=context)
            return serialized

        # Is it just an Encodable object?
        if isinstance(data, Encodable):
            self._log_data_processing(self.dotted,
                                      "Encoding `Encodable` data "
                                      "for serialization.",
                                      context=context)
            serialized = codec.encode(data)
            return serialized

        # Is it a simple type?
        if text.serialize_claim(data) or time.serialize_claim(data):
            # Let json handle it.
            serialized = data
            return serialized
        if paths.serialize_claim(data):
            serialized = paths.serialize(data)
            return serialized
        if numbers.serialize_claim(data):
            serialized = numbers.serialize(data)
            return serialized

        # Mapping?
        with contextlib.suppress(AttributeError, TypeError):
            # Do the thing that spawns the exception before
            # we log about doing the thing...
            keys = data.keys()
            self._log_data_processing(self.dotted,
                                      "Prepping `Mapping` of data "
                                      "for serialization.",
                                      context=context)
            serialized = {}
            for each in keys:
                # TODO [2020-07-29]: Change to non-recursive?
                serialized[str(each)] = self._serialize_prep(data[each],
                                                             codec,
                                                             context)
            return serialized

        # Iterable
        with contextlib.suppress(AttributeError, TypeError):
            # Do the thing that spawns the exception before
            # we log about doing the thing...
            iterable = iter(data)
            self._log_data_processing(self.dotted,
                                      "Prepping `Iterable` of data "
                                      "for serialization.",
                                      context=context)
            serialized = []
            for each in iterable:
                # TODO [2020-07-29]: Change to non-recursive?
                serialized.append(self._serialize_prep(each, codec, context))
            return serialized

        # Falling through to here is bad; raise Exception.
        msg = f"Don't know how to process '{type(data)}' data."
        self._log_data_processing(self.dotted,
                                  msg,
                                  context=context,
                                  success=False)
        error = exceptions.WriteError(msg,
                                      context=context,
                                      data={
                                          'data': data,
                                      })
        raise log.exception(error, msg,
                            context=context)