Exemple #1
0
    def _decode(self, data: _DeserializeMidTypes,
                codec: Codec) -> DeserializeTypes:
        '''
        Decode `data` after it has been deserialized.

        Final step before returning to caller.
        '''
        # # Don't need to do anything more for simpler collections.
        # if isinstance(data, (dict, list)):
        #     return decoded

        # Try to decode...
        # If it can't be decoded, use data as decoded. Could be just the
        # metadata document or something which is (currently) just a dict.
        decoded = codec.decode(None, data, error_squelch=True, fallback=data)
        return decoded
Exemple #2
0
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)