class testPLEXRegistration(unittest.TestCase):
    """
    Tests the registration process of the Scaling Executive to the broker
    and the plugin manager, and the heartbeat process.
    """

    def setUp(self):
        #a new Scaling Executive in another process for each test
        self.plex_proc = Process(target=ScalingExecutive)
        self.plex_proc.daemon = True

        if 'broker_man_host' in os.environ:
            self.man_host = os.environ['broker_man_host']
        else:
            self.man_host = 'http://localhost:15672'

        if 'sm_broker_host' in os.environ:
            self.sm_host = os.environ['sm_broker_host']
        else:
            self.sm_host = 'http://localhost:15672'
        url_user = "******".format(self.man_host)
        url_create = '{0}/api/vhosts/fsm-1234'.format(self.man_host)
        url_permission = '{0}/api/permissions/fsm-1234/specific-management'.format(self.man_host)
        self.headers = {'content-type': 'application/json'}
        data1 = '{"password":"******","tags":"son-sm"}'
        data2 = '{"configure":".*","write":".*","read":".*"}'
        res = requests.put(url=url_user, headers=self.headers, data=data1, auth=('guest', 'guest'))
        LOG.info(res.content)
        res1 = requests.put(url=url_create, headers=self.headers, auth=('guest', 'guest'))
        LOG.info(res1.content)
        res2= requests.put(url=url_permission, headers=self.headers, data=data2, auth=('guest', 'guest'))
        LOG.info(res2.content)

        #make a new connection with the broker before each test
        url = "{0}/fsm-1234".format(self.sm_host)
        self.manoconn = ManoBrokerRequestResponseConnection('son-plugin.SonPluginManager')
        self.sm_connection = ManoBrokerRequestResponseConnection('son-plugin.FSM', url=url)

        #Some threading events that can be used during the tests
        self.wait_for_event1 = threading.Event()
        self.wait_for_event1.clear()

        self.wait_for_event2 = threading.Event()
        self.wait_for_event2.clear()

    def tearDown(self):
        #Killing the scaling Executive
        if self.plex_proc is not None:
            self.plex_proc.terminate()
        del self.plex_proc

        #Killing the connection with the broker
        try:
            self.manoconn.stop_connection()
            self.sm_connection.stop_connection()
        except Exception as e:
            LOG.exception("Stop connection exception.")

        #Clearing the threading helpers
        del self.wait_for_event1
        del self.wait_for_event2

        url_user = "******".format(self.man_host)
        url_vhost = "{0}/api/vhosts/fsm-1234".format(self.man_host)
        requests.delete(url=url_user, headers=self.headers, auth=('guest', 'guest'))
        requests.delete(url=url_vhost, headers=self.headers, auth=('guest', 'guest'))

    #Method that terminates the timer that waits for an event
    def eventFinished1(self):
        self.wait_for_event1.set()

    def eventFinished2(self):
        self.wait_for_event2.set()

    #Method that starts a timer, waiting for an event
    def waitForEvent1(self, timeout=5, msg="Event timed out."):
        if not self.wait_for_event1.wait(timeout):
            self.assertEqual(True, False, msg=msg)

    def waitForEvent2(self, timeout=5, msg="Event timed out."):
        if not self.wait_for_event2.wait(timeout):
            self.assertEqual(True, False, msg=msg)


    def test_1_SCEX_Registration(self):
        """
        TEST: This test verifies whether the Scaling Executive is sending out a message,
        and whether it contains all the needed info on the
        platform.management.plugin.register topic to register to the plugin
        manager.
        """

        #STEP3a: When receiving the message, we need to check whether all fields present.
        def on_register_receive(ch, method, properties, message):

            msg = yaml.load(message)
            #CHECK: The message should be a dictionary.
            self.assertTrue(isinstance(msg, dict), msg='message is not a dictionary')
            #CHECK: The dictionary should have a key 'name'.
            self.assertIn('name', msg.keys(), msg='No name provided in message.')
            if isinstance(msg['name'], str):
                #CHECK: The value of 'name' should not be an empty string.
                self.assertTrue(len(msg['name']) > 0, msg='empty name provided.')
            else:
                #CHECK: The value of 'name' should be a string
                self.assertEqual(True, False, msg='name is not a string')
            #CHECK: The dictionary should have a key 'version'.
            self.assertIn('version', msg.keys(), msg='No version provided in message.')
            if isinstance(msg['version'], str):
                #CHECK: The value of 'version' should not be an empty string.
                self.assertTrue(len(msg['version']) > 0, msg='empty version provided.')
            else:
                #CHECK: The value of 'version' should be a string
                self.assertEqual(True, False, msg='version is not a string')
            #CHECK: The dictionary should have a key 'description'
            self.assertIn('description', msg.keys(), msg='No description provided in message.')
            if isinstance(msg['description'], str):
                #CHECK: The value of 'description' should not be an empty string.
                self.assertTrue(len(msg['description']) > 0, msg='empty description provided.')
            else:
                #CHECK: The value of 'description' should be a string
                self.assertEqual(True, False, msg='description is not a string')

            # stop waiting
            self.eventFinished1()

        #STEP1: Listen to the platform.management.plugin.register topic
        self.manoconn.subscribe(on_register_receive, 'platform.management.plugin.register')

        #STEP2: Start the Scaling Executive
        self.plex_proc.start()

        #STEP3b: When not receiving the message, the test failed
        self.waitForEvent1(timeout=5, msg="message not received.")

    def test_2_SCEX_request_response(self):

        def on_request_send(ch, method, properties, message):

            if properties.app_id == "son-plugin.ScalingExecutive":
                msg = yaml.load(message)

                self.assertTrue(isinstance(msg, dict), msg='message is not a dictionary')

                self.assertIn('uuid', msg.keys(), msg='No uuid provided in message.')
                if isinstance(msg['uuid'], str):
                    self.assertTrue(msg['uuid'] == '1234', msg='empty uuid provided.')

                self.assertNotIn('scale', msg.keys(), msg='wrong message!')

                res_payload = yaml.dump({'uuid': '1234', 'scale': '2'})

                self.eventFinished1()
                return res_payload

        def on_response_send(ch, method, properties, message):
            if properties.app_id == "son-plugin.ScalingExecutive":
                msg = yaml.load(message)

                self.assertTrue(isinstance(msg, dict), msg='message is not a dictionary')

                self.assertIn('uuid', msg.keys(), msg='No uuid provided in message.')
                if isinstance(msg['uuid'], str):
                    self.assertTrue(msg['uuid'] == '1234', msg='empty uuid provided.')

                self.assertIn('scale', msg.keys(), msg='No scale provided in message.')
                if isinstance(msg['scale'], str):
                    self.assertTrue(msg['scale'] == '2', msg='empty uuid provided.')

                self.eventFinished2()

        self.plex_proc.start()

        time.sleep(2)

        self.manoconn.subscribe(on_response_send, 'scaling.executive.request')
        self.sm_connection.register_async_endpoint(on_request_send, 'scaling.fsm.1234')

        req_payload = yaml.dump({'uuid': '1234'})
        self.manoconn.publish("scaling.executive.request", message=req_payload)

        self.waitForEvent1(timeout=5, msg="response message not received.")
        self.waitForEvent2(timeout=5, msg="request message not received.")
Beispiel #2
0
class TestManoBrokerRequestResponseConnection(BaseTestCase):
    """
    Test async. request/response and notification functionality.
    """
    def setUp(self):
        super().setUp()
        self.m = ManoBrokerRequestResponseConnection(
            "test-request-response-broker-connection")

    #@unittest.skip("disabled")
    def test_broker_connection(self):
        """
        Test broker connection.
        """
        self.m.notify("test.topic2", "simplemessage")

    #@unittest.skip("disabled")
    def test_request_response(self):
        """
        Test request/response messaging pattern.
        """
        self.m.register_async_endpoint(self._simple_request_echo_cbf,
                                       "test.request")
        time.sleep(0.5)  # give broker some time to register subscriptions
        self.m.call_async(self._simple_subscribe_cbf1, "test.request",
                          "ping-pong")
        self.assertEqual(self.wait_for_messages()[0], "ping-pong")

    #@unittest.skip("disabled")
    def test_request_response_sync(self):
        """
        Test request/response messaging pattern (synchronous).
        """
        self.m.register_async_endpoint(self._simple_request_echo_cbf,
                                       "test.request.sync")
        time.sleep(0.5)  # give broker some time to register subscriptions
        result = self.m.call_sync("test.request.sync", "ping-pong")
        self.assertTrue(len(result) == 4)
        self.assertEqual(str(result[3]), "ping-pong")

    #@unittest.skip("disabled")
    def test_notification(self):
        """
        Test notification messaging pattern.
        """
        self.m.register_notification_endpoint(self._simple_subscribe_cbf1,
                                              "test.notification")
        time.sleep(0.5)  # give broker some time to register subscriptions
        self.m.notify("test.notification", "my-notification")
        self.assertTrue(self.wait_for_particular_messages("my-notification"))

    #@unittest.skip("disabled")
    def test_notification_pub_sub_mix(self):
        """
        Test notification messaging pattern mixed with basic pub/sub calls.
        """
        self.m.register_notification_endpoint(self._simple_subscribe_cbf1,
                                              "test.notification1")
        self.m.subscribe(self._simple_subscribe_cbf1, "test.notification2")
        time.sleep(0.5)  # give broker some time to register subscriptions
        # send publish to notify endpoint
        self.m.publish("test.notification1", "my-notification1")
        self.assertEqual(self.wait_for_messages()[0], "my-notification1")
        # send notify to subscribe endpoint
        self.m.notify("test.notification2", "my-notification2")
        #res = self.wait_for_messages(n_messages=2)
        self.assertTrue(self.wait_for_particular_messages("my-notification1"))
        self.assertTrue(self.wait_for_particular_messages("my-notification2"))

    #@unittest.skip("disabled")
    def test_double_subscriptions(self):
        """
        Ensure that messages are delivered to all subscriptions of a topic.
        (e.g. identifies queue setup problems)
        :return:
        """
        self.m.subscribe(self._simple_subscribe_cbf1, "test.interleave")
        self.m.subscribe(self._simple_subscribe_cbf2, "test.interleave")
        time.sleep(0.5)
        # send publish to notify endpoint
        self.m.publish("test.interleave", "my-notification1")
        # enusre that it is received by each subscription
        self.assertTrue(
            self.wait_for_particular_messages("my-notification1", buffer=0))
        self.assertTrue(
            self.wait_for_particular_messages("my-notification1", buffer=1))

    #@unittest.skip("disabled")
    def test_interleaved_subscriptions(self):
        """
        Ensure that interleaved subscriptions to the same topic do not lead to problems.
        :return:
        """
        self.m.subscribe(self._simple_subscribe_cbf2, "test.interleave2")
        time.sleep(0.5)
        # do a async call on the same topic
        self.m.register_async_endpoint(self._simple_request_echo_cbf,
                                       "test.interleave2")
        time.sleep(0.5)  # give broker some time to register subscriptions
        self.m.call_async(self._simple_subscribe_cbf1, "test.interleave2",
                          "ping-pong")
        self.assertTrue(self.wait_for_particular_messages("ping-pong"))
        # send publish to notify endpoint
        self.m.publish("test.interleave2", "my-notification1")
        time.sleep(0.5)
        # ensure that the subcriber still gets the message (and also sees the one from async_call)
        self.assertTrue(self.wait_for_particular_messages("ping-pong"))
        self.assertTrue(
            self.wait_for_particular_messages("my-notification1", buffer=1))
class testPLEXRegistration(unittest.TestCase):
    """
    Tests the registration process of the Scaling Executive to the broker
    and the plugin manager, and the heartbeat process.
    """
    def setUp(self):
        #a new Scaling Executive in another process for each test
        self.plex_proc = Process(target=ScalingExecutive)
        self.plex_proc.daemon = True

        if 'broker_man_host' in os.environ:
            self.man_host = os.environ['broker_man_host']
        else:
            self.man_host = 'http://localhost:15672'

        if 'sm_broker_host' in os.environ:
            self.sm_host = os.environ['sm_broker_host']
        else:
            self.sm_host = 'http://localhost:15672'
        url_user = "******".format(self.man_host)
        url_create = '{0}/api/vhosts/fsm-1234'.format(self.man_host)
        url_permission = '{0}/api/permissions/fsm-1234/specific-management'.format(
            self.man_host)
        self.headers = {'content-type': 'application/json'}
        data1 = '{"password":"******","tags":"son-sm"}'
        data2 = '{"configure":".*","write":".*","read":".*"}'
        res = requests.put(url=url_user,
                           headers=self.headers,
                           data=data1,
                           auth=('guest', 'guest'))
        LOG.info(res.content)
        res1 = requests.put(url=url_create,
                            headers=self.headers,
                            auth=('guest', 'guest'))
        LOG.info(res1.content)
        res2 = requests.put(url=url_permission,
                            headers=self.headers,
                            data=data2,
                            auth=('guest', 'guest'))
        LOG.info(res2.content)

        #make a new connection with the broker before each test
        url = "{0}/fsm-1234".format(self.sm_host)
        self.manoconn = ManoBrokerRequestResponseConnection(
            'son-plugin.SonPluginManager')
        self.sm_connection = ManoBrokerRequestResponseConnection(
            'son-plugin.FSM', url=url)

        #Some threading events that can be used during the tests
        self.wait_for_event1 = threading.Event()
        self.wait_for_event1.clear()

        self.wait_for_event2 = threading.Event()
        self.wait_for_event2.clear()

    def tearDown(self):
        #Killing the scaling Executive
        if self.plex_proc is not None:
            self.plex_proc.terminate()
        del self.plex_proc

        #Killing the connection with the broker
        try:
            self.manoconn.stop_connection()
            self.sm_connection.stop_connection()
        except Exception as e:
            LOG.exception("Stop connection exception.")

        #Clearing the threading helpers
        del self.wait_for_event1
        del self.wait_for_event2

        url_user = "******".format(self.man_host)
        url_vhost = "{0}/api/vhosts/fsm-1234".format(self.man_host)
        requests.delete(url=url_user,
                        headers=self.headers,
                        auth=('guest', 'guest'))
        requests.delete(url=url_vhost,
                        headers=self.headers,
                        auth=('guest', 'guest'))

    #Method that terminates the timer that waits for an event
    def eventFinished1(self):
        self.wait_for_event1.set()

    def eventFinished2(self):
        self.wait_for_event2.set()

    #Method that starts a timer, waiting for an event
    def waitForEvent1(self, timeout=5, msg="Event timed out."):
        if not self.wait_for_event1.wait(timeout):
            self.assertEqual(True, False, msg=msg)

    def waitForEvent2(self, timeout=5, msg="Event timed out."):
        if not self.wait_for_event2.wait(timeout):
            self.assertEqual(True, False, msg=msg)

    def test_1_SCEX_Registration(self):
        """
        TEST: This test verifies whether the Scaling Executive is sending out a message,
        and whether it contains all the needed info on the
        platform.management.plugin.register topic to register to the plugin
        manager.
        """

        #STEP3a: When receiving the message, we need to check whether all fields present.
        def on_register_receive(ch, method, properties, message):

            msg = yaml.load(message)
            #CHECK: The message should be a dictionary.
            self.assertTrue(isinstance(msg, dict),
                            msg='message is not a dictionary')
            #CHECK: The dictionary should have a key 'name'.
            self.assertIn('name',
                          msg.keys(),
                          msg='No name provided in message.')
            if isinstance(msg['name'], str):
                #CHECK: The value of 'name' should not be an empty string.
                self.assertTrue(len(msg['name']) > 0,
                                msg='empty name provided.')
            else:
                #CHECK: The value of 'name' should be a string
                self.assertEqual(True, False, msg='name is not a string')
            #CHECK: The dictionary should have a key 'version'.
            self.assertIn('version',
                          msg.keys(),
                          msg='No version provided in message.')
            if isinstance(msg['version'], str):
                #CHECK: The value of 'version' should not be an empty string.
                self.assertTrue(len(msg['version']) > 0,
                                msg='empty version provided.')
            else:
                #CHECK: The value of 'version' should be a string
                self.assertEqual(True, False, msg='version is not a string')
            #CHECK: The dictionary should have a key 'description'
            self.assertIn('description',
                          msg.keys(),
                          msg='No description provided in message.')
            if isinstance(msg['description'], str):
                #CHECK: The value of 'description' should not be an empty string.
                self.assertTrue(len(msg['description']) > 0,
                                msg='empty description provided.')
            else:
                #CHECK: The value of 'description' should be a string
                self.assertEqual(True,
                                 False,
                                 msg='description is not a string')

            # stop waiting
            self.eventFinished1()

        #STEP1: Listen to the platform.management.plugin.register topic
        self.manoconn.subscribe(on_register_receive,
                                'platform.management.plugin.register')

        #STEP2: Start the Scaling Executive
        self.plex_proc.start()

        #STEP3b: When not receiving the message, the test failed
        self.waitForEvent1(timeout=5, msg="message not received.")

    def test_2_SCEX_request_response(self):
        def on_request_send(ch, method, properties, message):

            if properties.app_id == "son-plugin.ScalingExecutive":
                msg = yaml.load(message)

                self.assertTrue(isinstance(msg, dict),
                                msg='message is not a dictionary')

                self.assertIn('uuid',
                              msg.keys(),
                              msg='No uuid provided in message.')
                if isinstance(msg['uuid'], str):
                    self.assertTrue(msg['uuid'] == '1234',
                                    msg='empty uuid provided.')

                self.assertNotIn('scale', msg.keys(), msg='wrong message!')

                res_payload = yaml.dump({'uuid': '1234', 'scale': '2'})

                self.eventFinished1()
                return res_payload

        def on_response_send(ch, method, properties, message):
            if properties.app_id == "son-plugin.ScalingExecutive":
                msg = yaml.load(message)

                self.assertTrue(isinstance(msg, dict),
                                msg='message is not a dictionary')

                self.assertIn('uuid',
                              msg.keys(),
                              msg='No uuid provided in message.')
                if isinstance(msg['uuid'], str):
                    self.assertTrue(msg['uuid'] == '1234',
                                    msg='empty uuid provided.')

                self.assertIn('scale',
                              msg.keys(),
                              msg='No scale provided in message.')
                if isinstance(msg['scale'], str):
                    self.assertTrue(msg['scale'] == '2',
                                    msg='empty uuid provided.')

                self.eventFinished2()

        self.plex_proc.start()

        time.sleep(2)

        self.manoconn.subscribe(on_response_send, 'scaling.executive.request')
        self.sm_connection.register_async_endpoint(on_request_send,
                                                   'scaling.fsm.1234')

        req_payload = yaml.dump({'uuid': '1234'})
        self.manoconn.publish("scaling.executive.request", message=req_payload)

        self.waitForEvent1(timeout=5, msg="response message not received.")
        self.waitForEvent2(timeout=5, msg="request message not received.")
class TestManoBrokerRequestResponseConnection(BaseTestCase):
    """
    Test async. request/response and notification functionality.
    """

    def setUp(self):
        super().setUp()
        self.m = ManoBrokerRequestResponseConnection("test-request-response-broker-connection")

    #@unittest.skip("disabled")
    def test_broker_connection(self):
        """
        Test broker connection.
        """
        self.m.notify("test.topic2", "simplemessage")

    #@unittest.skip("disabled")
    def test_request_response(self):
        """
        Test request/response messaging pattern.
        """
        self.m.register_async_endpoint(self._simple_request_echo_cbf, "test.request")
        time.sleep(0.5)  # give broker some time to register subscriptions
        self.m.call_async(self._simple_subscribe_cbf1, "test.request", "ping-pong")
        self.assertEqual(self.wait_for_messages()[0], "ping-pong")

    #@unittest.skip("disabled")
    def test_request_response_sync(self):
        """
        Test request/response messaging pattern (synchronous).
        """
        self.m.register_async_endpoint(self._simple_request_echo_cbf, "test.request.sync")
        time.sleep(0.5)  # give broker some time to register subscriptions
        result = self.m.call_sync("test.request.sync", "ping-pong")
        self.assertTrue(len(result) == 4)
        self.assertEqual(str(result[3]), "ping-pong")

    #@unittest.skip("disabled")
    def test_notification(self):
        """
        Test notification messaging pattern.
        """
        self.m.register_notification_endpoint(self._simple_subscribe_cbf1, "test.notification")
        time.sleep(0.5)  # give broker some time to register subscriptions
        self.m.notify("test.notification", "my-notification")
        self.assertTrue(self.wait_for_particular_messages("my-notification"))

    #@unittest.skip("disabled")
    def test_notification_pub_sub_mix(self):
        """
        Test notification messaging pattern mixed with basic pub/sub calls.
        """
        self.m.register_notification_endpoint(self._simple_subscribe_cbf1, "test.notification1")
        self.m.subscribe(self._simple_subscribe_cbf1, "test.notification2")
        time.sleep(0.5)  # give broker some time to register subscriptions
        # send publish to notify endpoint
        self.m.publish("test.notification1", "my-notification1")
        self.assertEqual(self.wait_for_messages()[0], "my-notification1")
        # send notify to subscribe endpoint
        self.m.notify("test.notification2", "my-notification2")
        #res = self.wait_for_messages(n_messages=2)
        self.assertTrue(self.wait_for_particular_messages("my-notification1"))
        self.assertTrue(self.wait_for_particular_messages("my-notification2"))

    #@unittest.skip("disabled")
    def test_double_subscriptions(self):
        """
        Ensure that messages are delivered to all subscriptions of a topic.
        (e.g. identifies queue setup problems)
        :return:
        """
        self.m.subscribe(self._simple_subscribe_cbf1, "test.interleave")
        self.m.subscribe(self._simple_subscribe_cbf2, "test.interleave")
        time.sleep(0.5)
        # send publish to notify endpoint
        self.m.publish("test.interleave", "my-notification1")
        # enusre that it is received by each subscription
        self.assertTrue(self.wait_for_particular_messages("my-notification1", buffer=0))
        self.assertTrue(self.wait_for_particular_messages("my-notification1", buffer=1))

    #@unittest.skip("disabled")
    def test_interleaved_subscriptions(self):
        """
        Ensure that interleaved subscriptions to the same topic do not lead to problems.
        :return:
        """
        self.m.subscribe(self._simple_subscribe_cbf2, "test.interleave2")
        time.sleep(0.5)
        # do a async call on the same topic
        self.m.register_async_endpoint(self._simple_request_echo_cbf, "test.interleave2")
        time.sleep(0.5)  # give broker some time to register subscriptions
        self.m.call_async(self._simple_subscribe_cbf1, "test.interleave2", "ping-pong")
        self.assertTrue(self.wait_for_particular_messages("ping-pong"))
        # send publish to notify endpoint
        self.m.publish("test.interleave2", "my-notification1")
        time.sleep(0.5)
        # ensure that the subcriber still gets the message (and also sees the one from async_call)
        self.assertTrue(self.wait_for_particular_messages("ping-pong"))
        self.assertTrue(self.wait_for_particular_messages("my-notification1", buffer=1))
Beispiel #5
0
class TestManoBrokerRequestResponseConnection(unittest.TestCase):
    """
    Test async. request/response and notification functionality.
    """
    def setUp(self):
        self._last_message = None
        self.m = ManoBrokerRequestResponseConnection("test")

    def tearDown(self):
        del self.m

    def _simple_request_echo_cbf(self, ch, method, properties, message):
        """
        Simple echo function.
        """
        assert (properties.correlation_id is not None)
        assert (properties.reply_to is not None)
        assert (properties.content_type == "application/json")
        assert ("key" in properties.headers)
        return str(message, "utf-8")

    def _simple_message_cbf(self, ch, method, properties, message):
        assert (properties.content_type == "application/json")
        self._last_message = str(message, "utf-8")

    def wait_for_message(self, timeout=2):
        """
        Helper to deal with async messaging system.
        Waits until a response is available in self._last_message
        or until a timeout is reached.
        :param timeout: seconds to wait
        :return:
        """
        waiting = 0
        while self._last_message is None and waiting < timeout:
            time.sleep(0.01)
            waiting += 0.01
        if not waiting < timeout:
            raise Exception("Message lost. Subscription timeout reached.")
        m = self._last_message
        self._last_message = None
        return m

    def test_broker_connection(self):
        """
        Test broker connection.
        """
        self.m.notify("test.topic", "simplemessage")

    def test_request_response(self):
        """
        Test request/response messaging pattern.
        """
        self.m.register_async_endpoint(self._simple_request_echo_cbf,
                                       "test.request")
        time.sleep(0.5)  # give broker some time to register subscriptions
        self.m.call_async(self._simple_message_cbf, "test.request",
                          "ping-pong")
        self.assertEqual(self.wait_for_message(), "ping-pong")

    def test_request_response_sync(self):
        """
        Test request/response messaging pattern (synchronous).
        """
        self.m.register_async_endpoint(self._simple_request_echo_cbf,
                                       "test.request.sync")
        time.sleep(0.5)  # give broker some time to register subscriptions
        result = self.m.call_sync("test.request.sync", "ping-pong")
        self.assertTrue(len(result) == 4)
        self.assertEqual(str(result[3], "utf-8"), "ping-pong")

    def test_notification(self):
        """
        Test notification messaging pattern.
        """
        self.m.register_notification_endpoint(self._simple_message_cbf,
                                              "test.notification")
        time.sleep(0.5)  # give broker some time to register subscriptions
        self.m.notify("test.notification", "my-notification")
        self.assertEqual(self.wait_for_message(), "my-notification")

    def test_notification_pub_sub_mix(self):
        """
        Test notification messaging pattern mixed with basic pub/sub calls.
        """
        self.m.register_notification_endpoint(self._simple_message_cbf,
                                              "test.notification1")
        self.m.subscribe(self._simple_message_cbf, "test.notification2")
        time.sleep(0.5)  # give broker some time to register subscriptions
        # send publish to notify endpoint
        self.m.publish("test.notification1", "my-notification1")
        self.assertEqual(self.wait_for_message(), "my-notification1")
        # send notify to subscribe endpoint
        self.m.notify("test.notification2", "my-notification2")
        self.assertEqual(self.wait_for_message(), "my-notification2")