From b9b57047b79f7b93cd67db372f9849cb2ce8313f Mon Sep 17 00:00:00 2001 From: sanket-kulkarni-vmware Date: Tue, 28 Mar 2017 10:54:13 +0530 Subject: [PATCH 1/3] Unit test cases for Mqtt transport and AwsIoT DCC. (#8) * Added unit test cases for Mqtt transport and AwsIoT DCC. * Added dependency of mock module into requirement.txt --- requirements.txt | 3 +- tests/unit/dccs/__init__.py | 31 ++ tests/unit/dccs/test_aws_iot.py | 508 ++++++++++++++++++ tests/unit/lib/__init__.py | 31 ++ tests/unit/lib/transport/__init__.py | 31 ++ tests/unit/lib/transport/test_mqtt.py | 708 ++++++++++++++++++++++++++ 6 files changed, 1311 insertions(+), 1 deletion(-) create mode 100644 tests/unit/dccs/__init__.py create mode 100644 tests/unit/dccs/test_aws_iot.py create mode 100644 tests/unit/lib/__init__.py create mode 100644 tests/unit/lib/transport/__init__.py create mode 100644 tests/unit/lib/transport/test_mqtt.py diff --git a/requirements.txt b/requirements.txt index 130c31ef..32c7cd64 100755 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,5 @@ linux-metrics==0.1.4 pint==0.7.2 paho-mqtt==1.2 aenum==1.4.5 -CoAPthon==4.0.2 \ No newline at end of file +CoAPthon==4.0.2 +mock==2.0.0 diff --git a/tests/unit/dccs/__init__.py b/tests/unit/dccs/__init__.py new file mode 100644 index 00000000..4badd4a2 --- /dev/null +++ b/tests/unit/dccs/__init__.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------# +# Copyright © 2015-2016 VMware, Inc. All Rights Reserved. # +# # +# Licensed under the BSD 2-Clause License (the “License”); you may not use # +# this file except in compliance with the License. # +# # +# The BSD 2-Clause License # +# # +# Redistribution and use in source and binary forms, with or without # +# modification, are permitted provided that the following conditions are met:# +# # +# - Redistributions of source code must retain the above copyright notice, # +# this list of conditions and the following disclaimer. # +# # +# - Redistributions in binary form must reproduce the above copyright # +# notice, this list of conditions and the following disclaimer in the # +# documentation and/or other materials provided with the distribution. # +# # +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"# +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF # +# THE POSSIBILITY OF SUCH DAMAGE. # +# ----------------------------------------------------------------------------# diff --git a/tests/unit/dccs/test_aws_iot.py b/tests/unit/dccs/test_aws_iot.py new file mode 100644 index 00000000..ddcbaeac --- /dev/null +++ b/tests/unit/dccs/test_aws_iot.py @@ -0,0 +1,508 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------# +# Copyright © 2015-2016 VMware, Inc. All Rights Reserved. # +# # +# Licensed under the BSD 2-Clause License (the “License”); you may not use # +# this file except in compliance with the License. # +# # +# The BSD 2-Clause License # +# # +# Redistribution and use in source and binary forms, with or without # +# modification, are permitted provided that the following conditions are met:# +# # +# - Redistributions of source code must retain the above copyright notice, # +# this list of conditions and the following disclaimer. # +# # +# - Redistributions in binary form must reproduce the above copyright # +# notice, this list of conditions and the following disclaimer in the # +# documentation and/or other materials provided with the distribution. # +# # +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"# +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF # +# THE POSSIBILITY OF SUCH DAMAGE. # +# ----------------------------------------------------------------------------# + +import unittest +import json + +import pint + +from liota.lib.utilities.si_unit import parse_unit +from liota.dccs.aws_iot import AWSIoT +from liota.dcc_comms.mqtt_dcc_comms import MqttDccComms +from liota.entities.metrics.metric import Metric +from liota.entities.devices.simulated_device import SimulatedDevice +from liota.entities.edge_systems.dell5k_edge_system import Dell5KEdgeSystem +from liota.entities.registered_entity import RegisteredEntity +from liota.entities.metrics.registered_metric import RegisteredMetric +from liota.lib.utilities.utility import getUTCmillis + + +# Create a pint unit registry +ureg = pint.UnitRegistry() + + +# Monkey patched init method of MqttDccComms +def mocked_init_mqtt_dcc_comms(self, *args, **kwargs): + pass + + +# Sampling function +def sampling_function(): + pass + + +def validate_json(obj): + """ + Method to sort the provided json and returns back the sorted list representation of the json. + :param obj: json object + :return: sorted list of json + """ + if isinstance(obj, dict): + return sorted((k, validate_json(v)) for k, v in obj.items()) + if isinstance(obj, list): + return sorted(validate_json(x) for x in obj) + else: + return obj + + +class AWSIoTTest(unittest.TestCase): + """ + AWSIoT unit test cases + """ + + def setUp(self): + """ + Method to initialise the AWSIoT parameters. + :return: None + """ + + # EdgeSystem name + self.edge_system = Dell5KEdgeSystem("TestEdgeSystem") + + # Monkey patch the constructor of MqttDccComms + MqttDccComms.__init__ = mocked_init_mqtt_dcc_comms + + self.mocked_mqtt_dcc_comms = MqttDccComms() + + self.aws = AWSIoT(self.mocked_mqtt_dcc_comms, enclose_metadata=True) + + def tearDown(self): + """ + Method to cleanup the resource created during the execution of test case. + :return: None + """ + self.edge_system = None + self.mocked_mqtt_dcc_comms = None + self.aws = None + + def test_validation_of_comms_parameter(self): + """ + Test case to check the validation of AWSIoT class for invalid connections object. + :return: None + """ + # Checking whether implementation raising the TypeError Exception for invalid comms object + with self.assertRaises(TypeError): + AWSIoT("Invalid object", enclose_metadata=True) + + def test_implementation_register_entity(self): + """ + Test case to check the implementation of register method of AWSIoT class for entity registration. + :return: None + """ + + # Register the edge + registered_entity = self.aws.register(self.edge_system) + + # Check the returned object is of the class RegisteredEntity + self.assertIsInstance(registered_entity, RegisteredEntity) + + def test_implementation_register_metric(self): + """ + Test case to check the implementation of register method of AWSIoT for metric registration. + :return: None + """ + + # Creating test Metric + test_metric = Metric( + name="Test_Metric", + unit=None, + interval=10, + aggregation_size=2, + sampling_function=sampling_function + ) + + registered_metric = self.aws.register(test_metric) + + # Check the returned object is of the class RegisteredMetric + self.assertIsInstance(registered_metric, RegisteredMetric) + + def test_implementation_create_relationship(self): + """ + Test case to test RegisteredEntity as Parent and RegisteredMetric as child. + RegisteredEdgeSystem->RegisteredMetric + :return: None + """ + + # Register the edge + registered_entity = self.aws.register(self.edge_system) + + # Creating test Metric + test_metric = Metric( + name="Test_Metric", + unit=None, + interval=10, + aggregation_size=2, + sampling_function=sampling_function + ) + + registered_metric = self.aws.register(test_metric) + + # Creating the parent-child relationship + self.aws.create_relationship(registered_entity, registered_metric) + + self.assertEqual(registered_metric.parent, registered_entity, "Check the implementation of create_relationship") + + def test_validation_create_relationship_metric_device(self): + """ + Test case to test validation for RegisteredMetric as Parent and RegisteredEntity as child. + RegisteredMetric->RegisteredMetric + :return: None + """ + + # Register the edge + registered_entity = self.aws.register(self.edge_system) + + # Creating test Metric + test_metric = Metric( + name="Test_Metric", + unit=None, + interval=10, + aggregation_size=2, + sampling_function=sampling_function + ) + + registered_metric = self.aws.register(test_metric) + + with self.assertRaises(TypeError): + # Test case to check validation for RegisteredMetric as Parent and RegisteredEntity as child. + self.aws.create_relationship(registered_metric, registered_entity) + + def test_validation_create_relationship_child_entity(self): + """ + Test case to check validation for RegisteredEntity as Parent and Child. + RegisteredEdgeSystem->RegisteredEdgeSystem. + :return: None + """ + + # Register the edge + registered_entity = self.aws.register(self.edge_system) + + with self.assertRaises(TypeError): + # Creating the parent-child relationship between Edge-System and Edge-System + self.aws.create_relationship(registered_entity, registered_entity) + + def test_implementation_get_entity_hierarchy(self): + """ + Test case to check get_entity_entity_hierarchy() for RegisteredEdgeSystem->RegisteredMetric + :return: None + """ + + # Register the edge + registered_entity = self.aws.register(self.edge_system) + + # Creating test Metric + test_metric = Metric( + name="Test_Metric", + unit=None, + interval=10, + aggregation_size=2, + sampling_function=sampling_function + ) + + registered_metric = self.aws.register(test_metric) + + # Creating the parent-child relationship + self.aws.create_relationship(registered_entity, registered_metric) + + # Getting the parent child relationship array from registered metric + entity_hierarchy = self.aws._get_entity_hierarchy(registered_metric) + + self.assertSequenceEqual([registered_entity.ref_entity.name, registered_metric.ref_entity.name], + entity_hierarchy, "Check the implementation of _get_entity_hierarchy for " + "RegisteredEdgeSystem->RegisteredMetric") + + def test_implementation_get_entity_hierarchy_device_metric(self): + """ + Test case to check get_entity_entity_hierarchy() for RegisteredEdgeSystem->RegisteredDevice->RegisteredMetric + :return: None + """ + + # Register the edge + registered_entity = self.aws.register(self.edge_system) + + # Creating Simulated Device + test_sensor = SimulatedDevice("TestSensor") + + # Registering Device and creating Parent-Child relationship + reg_test_sensor = self.aws.register(test_sensor) + + self.aws.create_relationship(registered_entity, reg_test_sensor) + + # Creating test Metric + test_metric = Metric( + name="Test_Metric", + unit=ureg.degC, + interval=10, + aggregation_size=2, + sampling_function=sampling_function + ) + + registered_metric = self.aws.register(test_metric) + + # Creating the parent-child relationship + self.aws.create_relationship(reg_test_sensor, registered_metric) + + # Getting the parent child relationship array from registered metric + entity_hierarchy = self.aws._get_entity_hierarchy(registered_metric) + + self.assertSequenceEqual([registered_entity.ref_entity.name, reg_test_sensor.ref_entity.name, + registered_metric.ref_entity.name], + entity_hierarchy, "Check the implementation of _get_entity_hierarchy for " + "RegisteredEdgeSystem->RegisteredDevice->RegisteredMetric") + + def test_validation_get_entity_hierarchy(self): + """ + Test case to check the validation of _get_entity_hierarchy method for Metric object. + :return: None + """ + + # Creating test Metric + test_metric = Metric( + name="Test_Metric", + unit=None, + interval=10, + aggregation_size=2, + sampling_function=sampling_function + ) + + # Checking whether implementation raising the TypeError for invalid input + with self.assertRaises(TypeError): + self.aws._get_entity_hierarchy(test_metric) + + def test_implementation_format_data_no_data(self): + """ + Test case to check the implementation of _format_data for empty metric data. + :return: None + """ + + self.aws = AWSIoT(self.mocked_mqtt_dcc_comms, enclose_metadata=False) + + # Register the edge + registered_entity = self.aws.register(self.edge_system) + + # Creating test Metric + test_metric = Metric( + name="Test_Metric", + unit=None, + interval=10, + aggregation_size=2, + sampling_function=sampling_function + ) + + registered_metric = self.aws.register(test_metric) + + # Creating the parent-child relationship + self.aws.create_relationship(registered_entity, registered_metric) + + # Getting the parent child relationship array from registered metric + formatted_data = self.aws._format_data(registered_metric) + + # Check two dicts are equal or not + self.assertEqual(None, formatted_data, "Check implementation of _format_data") + + def test_implementation_format_data_with_enclose_metadata(self): + """ + Test case to check the implementation of _format_data method with enclose_metadata option of AWSIoT class. + RegisteredEdgeSystem->RegisteredMetric + :return: None + """ + + # Register the edge + registered_entity = self.aws.register(self.edge_system) + + # Creating test Metric + test_metric = Metric( + name="Test_Metric", + unit=None, + interval=10, + aggregation_size=2, + sampling_function=sampling_function + ) + + registered_metric = self.aws.register(test_metric) + + # Creating the parent-child relationship + self.aws.create_relationship(registered_entity, registered_metric) + + timestamp = getUTCmillis() + + registered_metric.values.put((timestamp, 10)) + + expected_output = { + "edge_system_name": registered_entity.ref_entity.name, + "metric_name": registered_metric.ref_entity.name, + "metric_data": [{ + "value": 10, + "timestamp": timestamp + }], + "unit": "null" + } + + # Getting the parent child relationship array from registered metric + formatted_data = self.aws._format_data(registered_metric) + + formatted_json_data = json.loads(formatted_data) + + # Check two dicts are equal or not + self.assertEqual(validate_json(formatted_json_data) == validate_json(expected_output), True, + "Check implementation of _format_data") + + def test_implementation_format_data_without_enclose_metatadata(self): + """ + Test case to check the output given by _format_data method without enclosed meta_data option. + RegisteredEdgeSystem->RegisteredMetric + :return: None + """ + + self.aws = AWSIoT(self.mocked_mqtt_dcc_comms, enclose_metadata=False) + + # Register the edge + registered_entity = self.aws.register(self.edge_system) + + # Creating test Metric + test_metric = Metric( + name="Test_Metric", + unit=None, + interval=10, + aggregation_size=2, + sampling_function=sampling_function + ) + + registered_metric = self.aws.register(test_metric) + + # Creating the parent-child relationship + self.aws.create_relationship(registered_entity, registered_metric) + + # Get current timestamp + timestamp = getUTCmillis() + + registered_metric.values.put((timestamp, 10)) + + # Expected output without enclosed metadata + expected_output = { + "metric_name": registered_metric.ref_entity.name, + "metric_data": [{ + "value": 10, + "timestamp": timestamp + }], + "unit": "null" + } + + # Getting the parent child relationship array from registered metric + formatted_data = self.aws._format_data(registered_metric) + + # Convert json string to dict for the comparision + formatted_json_data = json.loads(formatted_data) + + # Check two dicts are equal or not + self.assertEqual(validate_json(formatted_json_data) == validate_json(expected_output), True, + "Check implementation of _format_data") + + def test_implementation_format_data_with_enclose_metadata_device(self): + """ + Test case to test the implementation of _format_data method with enclose_metadata option. + RegisteredEdgeSystem->RegisteredDevice->RegisteredMetric + :return: None + """ + + # Register the edge + registered_entity = self.aws.register(self.edge_system) + + # Creating Simulated Device + test_sensor = SimulatedDevice("TestSensor") + + # Registering Device and creating Parent-Child relationship + reg_test_sensor = self.aws.register(test_sensor) + + self.aws.create_relationship(registered_entity, reg_test_sensor) + + # Creating test Metric + test_metric = Metric( + name="Test_Metric", + unit=ureg.degC, + interval=10, + aggregation_size=2, + sampling_function=sampling_function + ) + + registered_metric = self.aws.register(test_metric) + + # Creating the parent-child relationship + self.aws.create_relationship(reg_test_sensor, registered_metric) + + # Get current timestamp + timestamp = getUTCmillis() + + registered_metric.values.put((timestamp, 10)) + + # Expected output without enclosed metadata + expected_output = { + "edge_system_name": registered_entity.ref_entity.name, + "metric_name": registered_metric.ref_entity.name, + "device_name": reg_test_sensor.ref_entity.name, + "metric_data": [{ + "value": 10, + "timestamp": timestamp + }], + "unit": "null" + } + + unit_tuple = parse_unit(ureg.degC) + + if unit_tuple[0] is None: + # Base and Derived Units + expected_output['unit'] = unit_tuple[1] + else: + # Prefixed or non-SI Units + expected_output['unit'] = unit_tuple[0] + unit_tuple[1] + + # Getting the parent child relationship array from registered metric + formatted_data = self.aws._format_data(registered_metric) + + # Convert json string to dict for the comparision + formatted_json_data = json.loads(formatted_data) + + # Check two dicts are equal or not + self.assertEqual(validate_json(formatted_json_data) == validate_json(expected_output), True, + "Check implementation of _format_data") + + def test_set_properties(self): + """ + Test case to test the implementation of set_properties method of AWSIoT class. + :return: None + """ + # Check method raising the NotImplementedError exception. + self.assertRaises(NotImplementedError, self.aws.set_properties, None, None) + + +if __name__ == '__main__': + unittest.main(verbosity=1) diff --git a/tests/unit/lib/__init__.py b/tests/unit/lib/__init__.py new file mode 100644 index 00000000..4badd4a2 --- /dev/null +++ b/tests/unit/lib/__init__.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------# +# Copyright © 2015-2016 VMware, Inc. All Rights Reserved. # +# # +# Licensed under the BSD 2-Clause License (the “License”); you may not use # +# this file except in compliance with the License. # +# # +# The BSD 2-Clause License # +# # +# Redistribution and use in source and binary forms, with or without # +# modification, are permitted provided that the following conditions are met:# +# # +# - Redistributions of source code must retain the above copyright notice, # +# this list of conditions and the following disclaimer. # +# # +# - Redistributions in binary form must reproduce the above copyright # +# notice, this list of conditions and the following disclaimer in the # +# documentation and/or other materials provided with the distribution. # +# # +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"# +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF # +# THE POSSIBILITY OF SUCH DAMAGE. # +# ----------------------------------------------------------------------------# diff --git a/tests/unit/lib/transport/__init__.py b/tests/unit/lib/transport/__init__.py new file mode 100644 index 00000000..4badd4a2 --- /dev/null +++ b/tests/unit/lib/transport/__init__.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------# +# Copyright © 2015-2016 VMware, Inc. All Rights Reserved. # +# # +# Licensed under the BSD 2-Clause License (the “License”); you may not use # +# this file except in compliance with the License. # +# # +# The BSD 2-Clause License # +# # +# Redistribution and use in source and binary forms, with or without # +# modification, are permitted provided that the following conditions are met:# +# # +# - Redistributions of source code must retain the above copyright notice, # +# this list of conditions and the following disclaimer. # +# # +# - Redistributions in binary form must reproduce the above copyright # +# notice, this list of conditions and the following disclaimer in the # +# documentation and/or other materials provided with the distribution. # +# # +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"# +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF # +# THE POSSIBILITY OF SUCH DAMAGE. # +# ----------------------------------------------------------------------------# diff --git a/tests/unit/lib/transport/test_mqtt.py b/tests/unit/lib/transport/test_mqtt.py new file mode 100644 index 00000000..c1d72f2c --- /dev/null +++ b/tests/unit/lib/transport/test_mqtt.py @@ -0,0 +1,708 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------# +# Copyright © 2015-2016 VMware, Inc. All Rights Reserved. # +# # +# Licensed under the BSD 2-Clause License (the “License”); you may not use # +# this file except in compliance with the License. # +# # +# The BSD 2-Clause License # +# # +# Redistribution and use in source and binary forms, with or without # +# modification, are permitted provided that the following conditions are met:# +# # +# - Redistributions of source code must retain the above copyright notice, # +# this list of conditions and the following disclaimer. # +# # +# - Redistributions in binary form must reproduce the above copyright # +# notice, this list of conditions and the following disclaimer in the # +# documentation and/or other materials provided with the distribution. # +# # +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"# +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF # +# THE POSSIBILITY OF SUCH DAMAGE. # +# ----------------------------------------------------------------------------# + +import unittest +import sys + +import mock +from paho.mqtt.client import Client + +from liota.lib.transports.mqtt import Mqtt +from liota.lib.transports.mqtt import MqttMessagingAttributes, QoSDetails +from liota.entities.edge_systems.dell5k_edge_system import Dell5KEdgeSystem +from liota.lib.utilities.identity import Identity +from liota.lib.utilities.tls_conf import TLSConf +from liota.lib.utilities.utility import systemUUID + + +# MQTT configurations +config = {} +connect_rc = 0 +disconnect_rc = 0 + + +# Monkey patched connect method of Paho client +def mocked_connect(self, *args, **kwargs): + # Call on_connect method with connection established options connect_rc + self.on_connect(self._client_id, self._userdata, None, connect_rc) + + +# Monkey patched disconnect method of Paho client +def mocked_disconnect(self): + self.on_disconnect(config["client_id"], None, disconnect_rc) + + +# Monkey patched loop_start method of Paho client +def mocked_loop_start(self, *args, **kwargs): + pass + + +# Monkey patched loop_start method of Paho client +def mocked_loop_stop(self, *args, **kwargs): + pass + + +# Callback method to use in subscribe function +def topic_subscribe_callback(self, *args, **kwargs): + pass + + +class MQTTTest(unittest.TestCase): + """ + MQTT transport unit test cases + """ + + def setUp(self): + """ + Method to initialise the MQTT parameters. + :return: None + """ + + # Broker details + self.url = "127.0.0.1" + self.port = 8883 + self.mqtt_username = "test" + self.mqtt_password = "test" + self.enable_authentication = True + self.client_clean_session = True + self.protocol = "MQTTv311" + self.transport = "tcp" + self.connection_disconnect_timeout = 2 + self.user_data = None + self.client_id = "test-client" + config["client_id"] = self.client_id + + # Message QoS and connection details + self.QoSlevel = 2 + self.inflight = 20 + self.queue_size = 0 + self.retry = 5 + self.keep_alive = 60 + + # EdgeSystem name + self.edge_system = Dell5KEdgeSystem("TestEdgeSystem") + + # TLS configurations + self.root_ca_cert = "/etc/liota/mqtt/conf/ca.crt" + self.client_cert_file = "/etc/liota/mqtt/conf/client.crt" + self.client_key_file = "/etc/liota/mqtt/conf/client.key" + self.cert_required = "CERT_REQUIRED" + self.tls_version = "PROTOCOL_TLSv1" + self.cipher = None + + # Encapsulate the authentication details + self.identity = Identity(self.root_ca_cert, self.mqtt_username, self.mqtt_password, + self.client_cert_file, self.client_key_file) + + # Encapsulate TLS parameters + self.tls_conf = TLSConf(self.cert_required, self.tls_version, self.cipher) + + # Encapsulate QoS related parameters + self.qos_details = QoSDetails(self.inflight, self.queue_size, self.retry) + + def tearDown(self): + """ + Method to cleanup the resource created during the execution of test case. + :return: None + """ + # Broker details + self.url = None + self.port = None + self.mqtt_username = None + self.mqtt_password = None + self.enable_authentication = None + self.client_clean_session = None + self.protocol = None + self.transport = None + self.connection_disconnect_timeout = None + self.user_data = None + self.client_id = None + config["client_id"] = None + + # Message QoS and connection details + self.QoSlevel = None + self.inflight = None + self.queue_size = None + self.retry = None + self.keep_alive = None + + # EdgeSystem name + self.edge_system = None + + # TLS configurations + self.root_ca_cert = None + self.client_cert_file = None + self.client_key_file = None + self.cert_required = None + self.tls_version = None + self.cipher = None + + # Identity + self.identity = None + + # TLS configurations + self.tls_conf = None + + # QoS configurations + self.qos_details = None + + @mock.patch.object(Mqtt, 'connect_soc') + def test_mqtt_init(self, mock_connect): + """ + Test case to check the implementation of Mqtt class with client clean session flag True. + :param mock_connect: Mocked connect_soc method + :return: None + """ + + # Mocked connect_soc method + mock_connect.returnvalue = None + + mqtt_client = Mqtt(self.url, self.port, self.identity, self.tls_conf, self.qos_details, self.client_id, + self.client_clean_session, self.user_data, self.protocol, self.transport, self.keep_alive, + self.enable_authentication, self.connection_disconnect_timeout) + + # Check we are able to generate Mqtt class object + self.assertIsInstance(mqtt_client, Mqtt, "Invalid Mqtt class implementation") + + @mock.patch.object(Mqtt, 'connect_soc') + def test_mqtt_init_clean_session_false(self, mock_connect): + """ + Test case to test the implementation of Mqtt class for client clean session flag False. + :param mock_connect: Mocked connect_soc method + :return: None + """ + + # Mocked connect_soc method + mock_connect.returnvalue = None + + # Clean session flag as False + self.client_clean_session = False + + mqtt_client = Mqtt(self.url, self.port, self.identity, self.tls_conf, self.qos_details, self.client_id, + self.client_clean_session, self.user_data, self.protocol, self.transport, self.keep_alive, + self.enable_authentication, self.connection_disconnect_timeout) + + # Check we are able to generate Mqtt class object + self.assertIsInstance(mqtt_client, Mqtt, "Invalid Mqtt class implementation") + + def test_connect_soc_invalid_root_ca(self): + """ + Test case to test validation for invalid root ca validation. + :return: None + """ + # Setting invalid root ca path + self.root_ca_cert = "Invalid Root CA Path" + + # Encapsulate the authentication details + self.identity = Identity(self.root_ca_cert, self.mqtt_username, self.mqtt_password, + self.client_cert_file, self.client_key_file) + + # Checking whether implementation raising the ValueError for invalid root ca_certs + with self.assertRaises(ValueError): + Mqtt(self.url, self.port, self.identity, self.tls_conf, self.qos_details, self.client_id, + self.client_clean_session, self.user_data, self.protocol, self.transport, + self.keep_alive, self.enable_authentication, self.connection_disconnect_timeout) + + def test_connect_soc_empty_root_ca(self): + """ + Test case to test validation for empty root ca validation. + :return: None + """ + # Setting invalid root ca path + self.root_ca_cert = "" + + # Encapsulate the authentication details + self.identity = Identity(self.root_ca_cert, self.mqtt_username, self.mqtt_password, + self.client_cert_file, self.client_key_file) + + # Checking whether implementation raising the ValueError for invalid root ca_certs + with self.assertRaises(ValueError): + Mqtt(self.url, self.port, self.identity, self.tls_conf, self.qos_details, self.client_id, + self.client_clean_session, self.user_data, self.protocol, self.transport, + self.keep_alive, self.enable_authentication, self.connection_disconnect_timeout) + + def test_connect_soc_invalid_client_ca(self): + """ + Test case to test validation for invalid client ca validation. + :return: None + """ + # Setting invalid client ca path + self.client_cert_file = "Invalid Client CA Path" + + # Encapsulate the authentication details + self.identity = Identity(self.root_ca_cert, self.mqtt_username, self.mqtt_password, + self.client_cert_file, self.client_key_file) + + # Checking whether implementation raising the ValueError for invalid client ca_certs + with self.assertRaises(ValueError): + Mqtt(self.url, self.port, self.identity, self.tls_conf, self.qos_details, self.client_id, + self.client_clean_session, self.user_data, self.protocol, self.transport, + self.keep_alive, self.enable_authentication, self.connection_disconnect_timeout) + + def test_connect_soc_empty_client_ca(self): + """ + Test case to test validation for empty client certificate. + :return: None + """ + # Setting invalid client ca path + self.client_cert_file = "" + + # Encapsulate the authentication details + self.identity = Identity(self.root_ca_cert, self.mqtt_username, self.mqtt_password, + self.client_cert_file, self.client_key_file) + + # Checking whether implementation raising the ValueError for invalid client ca_certs + with self.assertRaises(ValueError): + Mqtt(self.url, self.port, self.identity, self.tls_conf, self.qos_details, self.client_id, + self.client_clean_session, self.user_data, self.protocol, self.transport, + self.keep_alive, self.enable_authentication, self.connection_disconnect_timeout) + + def test_connect_soc_invalid_client_key(self): + """ + Test case to test the validation for invalid client key. + :return: None + """ + # Setting invalid client key path + self.client_key_file = "Invalid Client Key Path" + + # Encapsulate the authentication details + self.identity = Identity(self.root_ca_cert, self.mqtt_username, self.mqtt_password, + self.client_cert_file, self.client_key_file) + + # Checking whether implementation raising the ValueError for invalid client key + with self.assertRaises(ValueError): + Mqtt(self.url, self.port, self.identity, self.tls_conf, self.qos_details, self.client_id, + self.client_clean_session, self.user_data, self.protocol, self.transport, + self.keep_alive, self.enable_authentication, self.connection_disconnect_timeout) + + def test_connect_soc_empty_client_key(self): + """ + Test case to test validation for empty client key. + :return: None + """ + # Setting invalid client cert path + self.client_key_file = "" + + # Encapsulate the authentication details + self.identity = Identity(self.root_ca_cert, self.mqtt_username, self.mqtt_password, + self.client_cert_file, self.client_key_file) + + # Checking whether implementation raising the ValueError for invalid client cert + with self.assertRaises(ValueError): + Mqtt(self.url, self.port, self.identity, self.tls_conf, self.qos_details, self.client_id, + self.client_clean_session, self.user_data, self.protocol, self.transport, + self.keep_alive, self.enable_authentication, self.connection_disconnect_timeout) + + def test_connect_soc_for_empty_username(self): + """ + Test case to test validation for empty username. + :return: None + """ + + # Encapsulate the authentication details + self.identity = Identity(self.root_ca_cert, "", self.mqtt_password, + self.client_cert_file, self.client_key_file) + + # Checking whether implementation raising the ValueError for invalid username + with self.assertRaises(ValueError): + Mqtt(self.url, self.port, self.identity, self.tls_conf, self.qos_details, self.client_id, + self.client_clean_session, self.user_data, self.protocol, self.transport, + self.keep_alive, self.enable_authentication, self.connection_disconnect_timeout) + + def test_connect_soc_for_empty_password(self): + """ + Test case to test validation for empty password. + :return: None + """ + + # Encapsulate the authentication details + self.identity = Identity(self.root_ca_cert, self.mqtt_username, "", + self.client_cert_file, self.client_key_file) + + # Checking whether implementation raising the ValueError for empty password + with self.assertRaises(ValueError): + Mqtt(self.url, self.port, self.identity, self.tls_conf, self.qos_details, self.client_id, + self.client_clean_session, self.user_data, self.protocol, self.transport, + self.keep_alive, self.enable_authentication, self.connection_disconnect_timeout) + + def test_connect_soc_connection_setup(self): + """ + Test case to test connection setup with connect_soc. + :return: None + """ + global connect_rc + + # Setting connection accepted flag + connect_rc = 0 + + # Mocked the connect and loop_start method of Paho library + Client.connect = mocked_connect + Client.loop_start = mocked_loop_start + + mqtt_client = Mqtt(self.url, self.port, self.identity, self.tls_conf, self.qos_details, self.client_id, + self.client_clean_session, self.user_data, self.protocol, self.transport, + self.keep_alive, self.enable_authentication, self.connection_disconnect_timeout) + + # Check we are able to generate Mqtt class object + self.assertIsInstance(mqtt_client, Mqtt, "Invalid Mqtt class implementation") + + def test_connect_soc_connection_timeout(self): + """ + Test case to test connection setup timeout with connect_soc. + :return: None + """ + global connect_rc + + # Setting connection timeout flag + connect_rc = sys.maxsize + + # Mocked the connect and loop_start method of Paho library + Client.connect = mocked_connect + Client.loop_start = mocked_loop_start + Client.loop_stop = mocked_loop_stop + + # Checking whether implementation raising the Exception for broker timeout + with self.assertRaises(Exception): + Mqtt(self.url, self.port, self.identity, self.tls_conf, self.qos_details, self.client_id, + self.client_clean_session, self.user_data, self.protocol, self.transport, + self.keep_alive, self.enable_authentication, self.connection_disconnect_timeout) + + def test_connect_soc_connection_refused(self): + """ + Test case to test broker connection refused with connect_soc. + :return: None + """ + global connect_rc + + # Setting connection refused flag + connect_rc = 1 + + # Mocked the connect and loop_start method of Paho library + Client.connect = mocked_connect + Client.loop_start = mocked_loop_start + Client.loop_stop = mocked_loop_stop + + # Checking whether implementation raising the Exception for broker connection refused + with self.assertRaises(Exception): + Mqtt(self.url, self.port, self.identity, self.tls_conf, self.qos_details, self.client_id, + self.client_clean_session, self.user_data, self.protocol, self.transport, + self.keep_alive, self.enable_authentication, self.connection_disconnect_timeout) + + def test_mqtt_connection_over_only_root_ca_cert(self): + """ + Test case to test the implementation of connection_soc method for root_ca only. + :return: None + """ + global connect_rc + + # Encapsulate the authentication details + self.identity = Identity(self.root_ca_cert, self.mqtt_username, self.mqtt_password, None, None) + + # Setting connection accepted flag + connect_rc = 0 + + # Mocked the connect and loop_start method of Paho library + Client.connect = mocked_connect + Client.loop_start = mocked_loop_start + + mqtt_client = Mqtt(self.url, self.port, self.identity, self.tls_conf, self.qos_details, self.client_id, + self.client_clean_session, self.user_data, self.protocol, self.transport, self.keep_alive, + self.enable_authentication, self.connection_disconnect_timeout) + # Check we are able to generate Mqtt class object + self.assertIsInstance(mqtt_client, Mqtt, "Invalid Mqtt class implementation") + + @mock.patch.object(Mqtt, 'connect_soc') + def test_client_clean_session_and_client_id_implementation(self, mock_connect): + """ + Test case to test the implementation of get_client_id method. + :param mock_connect: Mocked connect_soc method + :return: None + """ + + # Mocked connect_soc method + mock_connect.returnvalue = None + + mqtt_client = Mqtt(self.url, self.port, self.identity, self.tls_conf, self.qos_details, self.client_id, + self.client_clean_session, self.user_data, self.protocol, self.transport, self.keep_alive, + self.enable_authentication, self.connection_disconnect_timeout) + + # Check we are able to generate Mqtt class object + self.assertIsInstance(mqtt_client, Mqtt, "Invalid Mqtt class implementation") + + client_id = mqtt_client.get_client_id() + + # Checking the client_id implementation + self.assertEqual(self.client_id, client_id, "Received invalid client-id, check the implementation.") + + def test_clean_disconnect_connection(self): + """ + Test case to test clean-connection disconnect. + :return: None + """ + global connect_rc, disconnect_rc + + # Setting connection accepted flag + connect_rc = 0 + # Setting connection disconnect flag + disconnect_rc = 0 + + # Monkey patched connect, disconnect, loop_start and loop_stop methods of Paho library + Client.connect = mocked_connect + Client.disconnect = mocked_disconnect + Client.loop_start = mocked_loop_start + Client.loop_stop = mocked_loop_stop + + mqtt_client = Mqtt(self.url, self.port, self.identity, self.tls_conf, self.qos_details, self.client_id, + self.client_clean_session, self.user_data, self.protocol, self.transport, + self.keep_alive, self.enable_authentication, self.connection_disconnect_timeout) + + self.assertEqual(mqtt_client.disconnect(), None) + + def test_timeout_disconnect_connection(self): + """ + Test case to test timeout-connection disconnect. + :return: None + """ + global connect_rc, disconnect_rc + + # Setting connection accepted flag + connect_rc = 0 + # Setting connection disconnect flag + disconnect_rc = sys.maxsize + + # Monkey patched connect, disconnect, loop_start and loop_stop methods of Paho library + Client.connect = mocked_connect + Client.disconnect = mocked_disconnect + Client.loop_start = mocked_loop_start + Client.loop_stop = mocked_loop_stop + + # Checking whether implementation raising the Exception for broker disconnect timeout + with self.assertRaises(Exception): + mqtt_client = Mqtt(self.url, self.port, self.identity, self.tls_conf, self.qos_details, self.client_id, + self.client_clean_session, self.user_data, self.protocol, self.transport, + self.keep_alive, self.enable_authentication, self.connection_disconnect_timeout) + + mqtt_client.disconnect() + + def test_invalid_disconnect_connection(self): + """ + Test case to test invalid-connection disconnect. + :return: None + """ + global connect_rc, disconnect_rc + + # Setting connection accepted flag + connect_rc = 0 + # Setting connection disconnect flag + disconnect_rc = 2 + + # Monkey patched connect, disconnect, loop_start and loop_stop methods of Paho library + Client.connect = mocked_connect + Client.disconnect = mocked_disconnect + Client.loop_start = mocked_loop_start + Client.loop_stop = mocked_loop_stop + + # Checking whether implementation raising the Exception for broker disconnect + with self.assertRaises(Exception): + mqtt_client = Mqtt(self.url, self.port, self.identity, self.tls_conf, self.qos_details, self.client_id, + self.client_clean_session, self.user_data, self.protocol, self.transport, + self.keep_alive, self.enable_authentication, self.connection_disconnect_timeout) + + mqtt_client.disconnect() + + @mock.patch.object(Mqtt, 'connect_soc') + @mock.patch.object(Client, 'publish') + def test_publish(self, mocked_publish, connect_soc): + """ + Test case to test publish method of Mqtt class. + :return: None + """ + + mqtt_client = Mqtt(self.url, self.port, self.identity, self.tls_conf, self.qos_details, self.client_id, + self.client_clean_session, self.user_data, self.protocol, self.transport, + self.keep_alive, self.enable_authentication, self.connection_disconnect_timeout) + + # Publishing the message + mqtt_client.publish("test/publish", "Testing publish", 1, False) + + # Check underline publish is called with correct arguments + mocked_publish.assert_called_with("test/publish", "Testing publish", 1, False) + + @mock.patch.object(Mqtt, 'connect_soc') + @mock.patch.object(Client, 'subscribe') + @mock.patch.object(Client, "message_callback_add") + def test_subscribe(self, mocked_callback_add, mocked_subscribe, connect_soc): + """ + Test case to test the subscribe method. + :return: None + """ + + mqtt_client = Mqtt(self.url, self.port, self.identity, self.tls_conf, self.qos_details, self.client_id, + self.client_clean_session, self.user_data, self.protocol, self.transport, + self.keep_alive, self.enable_authentication, self.connection_disconnect_timeout) + + # Subscribing to the topic + mqtt_client.subscribe("test/subscribe", 1, topic_subscribe_callback) + + # Check underline subscribe is called with correct parameters + mocked_subscribe.assert_called_with("test/subscribe", 1) + + # Check underline message_callback_add is called with correct parameters + mocked_callback_add.assert_called_with("test/subscribe", topic_subscribe_callback) + + +class MqttMessagingAttributesTest(unittest.TestCase): + def setUp(self): + """ + Method to initialise the MqttMessagingAttributes class parameters. + :return: None + """ + # EdgeSystem name + self.edge_system = Dell5KEdgeSystem("TestEdgeSystem") + + def tearDown(self): + """ + Method to cleanup the resource created during the execution of test case. + :return: None + """ + self.edge_system = None + + def test_mqtt_messaging_attributes_implementation_for_edge_system_name(self): + """ + Test case to test the implementation of MqttMessagingAttributes class for automatic topic generation for publish + and subscribe. + :return: None + """ + + # Create MqttMessagingAttributes class object + mqtt_messaging_attribute = MqttMessagingAttributes(self.edge_system.name) + + # Check whether correct topic generated for publish + self.assertEqual(mqtt_messaging_attribute.pub_topic, 'liota/' + systemUUID().get_uuid(self.edge_system.name) + + "/request") + + # Check correct topic generated for subscribe + self.assertEqual(mqtt_messaging_attribute.sub_topic, 'liota/' + + systemUUID().get_uuid(self.edge_system.name) + "/response") + + def test_mqtt_messaging_attributes_implementation_without_edge_system_name(self): + """ + Test case to test the implementation of MqttMessagingAttributes class for provided sub/pub topics. + :return: None + """ + + # Create MqttMessagingAttributes class object + mqtt_messaging_attribute = MqttMessagingAttributes(pub_topic="test/pub/topic", sub_topic="test/sub/topic") + + # Check whether provided topic used for publish + self.assertEqual(mqtt_messaging_attribute.pub_topic, "test/pub/topic") + + # Check whether provided topic used for subscribe + self.assertEqual(mqtt_messaging_attribute.sub_topic, "test/sub/topic") + + def test_validation_of_sub_qos_mqtt_messaging_attributes(self): + """ + Test case to test validation of MqttMessagingAttributes class subscribe qos levels. + :return: None + """ + self.assertRaises(ValueError, MqttMessagingAttributes, self.edge_system.name, None, None, 1, -1) + + def test_validation_of_pub_qos_mqtt_messaging_attributes(self): + """ + Test case to test validation of MqttMessagingAttributes class publish qos levels. + :return: None + """ + self.assertRaises(ValueError, MqttMessagingAttributes, self.edge_system.name, None, None, -1, 1) + + def test_validation_of_retain_flag_mqtt_messaging_attributes(self): + """ + Test case to test validation of MqttMessagingAttributes class retain flag. + :return: None + """ + self.assertRaises(ValueError, MqttMessagingAttributes, self.edge_system.name, None, None, pub_retain="") + + def test_validation_of_sub_callback_mqtt_messaging_attributes(self): + """ + Test case to test validation of MqttMessagingAttributes sub_callback argument. + :return: None + """ + self.assertRaises(ValueError, MqttMessagingAttributes, self.edge_system.name, + sub_callback="") + + def test_validation_of_sub_and_pub_topic_attributes(self): + """ + Test case to test validation of subscribe, publish and sub_callback attributes. + :return: None + """ + self.assertRaises(ValueError, MqttMessagingAttributes) + + +class QoSDetailsTest(unittest.TestCase): + def setUp(self): + """ + Method to initialise the QoSDetails class parameters. + :return: None + """ + # QoS details + self.retry = 5 + self.inflight = 20 + self.queue_size = 0 + + def tearDown(self): + """ + Method to cleanup the resource created during the execution of test case. + :return: None + """ + # QoS details + self.retry = None + self.inflight = None + self.queue_size = None + + def test_QoS_class_implementation(self): + """ + Test case to test the implementation of QoSDetails class. + :return: None + """ + qos_details = QoSDetails(self.inflight, self.queue_size, self.retry) + + # Check value of retry + self.assertEqual(qos_details.retry, self.retry, "Invalid implementation for QoSDetails class") + + # Check value of inflight + self.assertEqual(qos_details.in_flight, self.inflight, "Invalid implementation for QoSDetails class") + + # Check value of queue_size + self.assertEqual(qos_details.queue_size, self.queue_size, "Invalid implementation for QoSDetails class") + + +if __name__ == '__main__': + unittest.main(verbosity=1) From 54c1df8da400c252e9540077e837efab6534cc5e Mon Sep 17 00:00:00 2001 From: sanket-kulkarni-vmware Date: Wed, 21 Jun 2017 00:16:47 +0530 Subject: [PATCH 2/3] Unit Test Cases Mqtt DCC Comms and MQTT Device Comms (#10) * Added unit test cases for Mqtt Device and DCC comms. * Updated unit test cases for Mqtt transport as per latest implementation. * Added validation for file check in tearDown and other changes suggested on PR. * Improved comments from Mqtt transport unit test cases. * Changed comment from Mqtt unit test file. --- tests/unit/__init__.py | 31 ++ tests/unit/dcc_comms/__init__.py | 31 ++ tests/unit/dcc_comms/test_mqtt_dcc_comms.py | 429 ++++++++++++++++++ tests/unit/device_comms/__init__.py | 31 ++ .../device_comms/test_mqtt_device_comms.py | 255 +++++++++++ tests/unit/lib/transport/test_mqtt.py | 171 ++++--- 6 files changed, 888 insertions(+), 60 deletions(-) create mode 100644 tests/unit/__init__.py create mode 100644 tests/unit/dcc_comms/__init__.py create mode 100644 tests/unit/dcc_comms/test_mqtt_dcc_comms.py create mode 100644 tests/unit/device_comms/__init__.py create mode 100644 tests/unit/device_comms/test_mqtt_device_comms.py diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 00000000..4badd4a2 --- /dev/null +++ b/tests/unit/__init__.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------# +# Copyright © 2015-2016 VMware, Inc. All Rights Reserved. # +# # +# Licensed under the BSD 2-Clause License (the “License”); you may not use # +# this file except in compliance with the License. # +# # +# The BSD 2-Clause License # +# # +# Redistribution and use in source and binary forms, with or without # +# modification, are permitted provided that the following conditions are met:# +# # +# - Redistributions of source code must retain the above copyright notice, # +# this list of conditions and the following disclaimer. # +# # +# - Redistributions in binary form must reproduce the above copyright # +# notice, this list of conditions and the following disclaimer in the # +# documentation and/or other materials provided with the distribution. # +# # +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"# +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF # +# THE POSSIBILITY OF SUCH DAMAGE. # +# ----------------------------------------------------------------------------# diff --git a/tests/unit/dcc_comms/__init__.py b/tests/unit/dcc_comms/__init__.py new file mode 100644 index 00000000..4badd4a2 --- /dev/null +++ b/tests/unit/dcc_comms/__init__.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------# +# Copyright © 2015-2016 VMware, Inc. All Rights Reserved. # +# # +# Licensed under the BSD 2-Clause License (the “License”); you may not use # +# this file except in compliance with the License. # +# # +# The BSD 2-Clause License # +# # +# Redistribution and use in source and binary forms, with or without # +# modification, are permitted provided that the following conditions are met:# +# # +# - Redistributions of source code must retain the above copyright notice, # +# this list of conditions and the following disclaimer. # +# # +# - Redistributions in binary form must reproduce the above copyright # +# notice, this list of conditions and the following disclaimer in the # +# documentation and/or other materials provided with the distribution. # +# # +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"# +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF # +# THE POSSIBILITY OF SUCH DAMAGE. # +# ----------------------------------------------------------------------------# diff --git a/tests/unit/dcc_comms/test_mqtt_dcc_comms.py b/tests/unit/dcc_comms/test_mqtt_dcc_comms.py new file mode 100644 index 00000000..d786e676 --- /dev/null +++ b/tests/unit/dcc_comms/test_mqtt_dcc_comms.py @@ -0,0 +1,429 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------# +# Copyright © 2015-2016 VMware, Inc. All Rights Reserved. # +# # +# Licensed under the BSD 2-Clause License (the “License”); you may not use # +# this file except in compliance with the License. # +# # +# The BSD 2-Clause License # +# # +# Redistribution and use in source and binary forms, with or without # +# modification, are permitted provided that the following conditions are met:# +# # +# - Redistributions of source code must retain the above copyright notice, # +# this list of conditions and the following disclaimer. # +# # +# - Redistributions in binary form must reproduce the above copyright # +# notice, this list of conditions and the following disclaimer in the # +# documentation and/or other materials provided with the distribution. # +# # +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"# +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF # +# THE POSSIBILITY OF SUCH DAMAGE. # +# ----------------------------------------------------------------------------# + +import Queue +import logging +import os +import unittest +from ConfigParser import ConfigParser + +import mock +import paho.mqtt.client as paho + +from liota.dcc_comms.mqtt_dcc_comms import MqttDccComms +from liota.entities.edge_systems.dell5k_edge_system import Dell5KEdgeSystem +from liota.lib.transports.mqtt import Mqtt, MqttMessagingAttributes, QoSDetails +from liota.lib.utilities.identity import Identity +from liota.lib.utilities.tls_conf import TLSConf +from liota.lib.utilities.utility import read_liota_config +from liota.lib.utilities.utility import systemUUID + +log = logging.getLogger(__name__) + + +# Callback function for subscribe and publish +def callback_function(*args, **kwargs): + pass + + +class MqttDccCommsTest(unittest.TestCase): + """ + Unit test cases for MQTT DccComms + """ + + @mock.patch.object(Mqtt, 'connect_soc') + def setUp(self, mock_connect): + """ + Setup all required parameters for MQTT DCC communication tests + :param mock_connect: Mocked MQTT connect method + :return: None + """ + # ConfigParser to parse ini file + self.config = ConfigParser() + self.uuid_file = read_liota_config('UUID_PATH', 'uuid_path') + + # Broker details + self.url = "Broker-IP" + self.port = "Broker-Port" + self.mqtt_username = "test" + self.mqtt_password = "test" + self.enable_authentication = True + self.clean_session = True + self.protocol = "MQTTv311" + self.transport = "tcp" + self.connection_disconnect_timeout = 2 + self.client_id = "test-client" + + # Message QoS and connection details + self.QoSlevel = 2 + self.inflight = 20 + self.queue_size = 0 + self.retry = 5 + self.keep_alive = 60 + + # EdgeSystem name + self.edge_system = Dell5KEdgeSystem("TestGateway") + + # TLS configurations + self.root_ca_cert = "/etc/liota/mqtt/conf/ca.crt" + self.client_cert_file = "/etc/liota/mqtt/conf/client.crt" + self.client_key_file = "/etc/liota/mqtt/conf/client.key" + self.cert_required = "CERT_REQUIRED" + self.tls_version = "PROTOCOL_TLSv1" + self.cipher = None + + # Encapsulate the authentication details + self.identity = Identity(self.root_ca_cert, self.mqtt_username, self.mqtt_password, + self.client_cert_file, self.client_key_file) + + # Encapsulate TLS parameters + self.tls_conf = TLSConf(self.cert_required, self.tls_version, self.cipher) + + # Encapsulate QoS related parameters + self.qos_details = QoSDetails(self.inflight, self.queue_size, self.retry) + + self.publish_message = "test-message" + + # Creating messaging attributes + self.mqtt_msg_attr = MqttMessagingAttributes(pub_topic="publish-topic", sub_topic="subscribe-topic", + pub_retain=False, sub_callback=callback_function) + + # Instantiate client for DCC communication + self.client = MqttDccComms(edge_system_name=self.edge_system.name, + url=self.url, port=self.port, clean_session=self.clean_session, + mqtt_msg_attr=self.mqtt_msg_attr) + + def tearDown(self): + """ + Clean up all parameters used in test cases + :return: None + """ + + # Check path exists + if os.path.exists(self.uuid_file): + try: + # Remove the file + os.remove(self.uuid_file) + except OSError as e: + log.error("Unable to remove UUID file" + str(e)) + + self.config = None + self.uuid_file = None + self.edge_system = None + self.url = None + self.port = None + self.mqtt_username = None + self.mqtt_password = None + self.enable_authentication = None + self.clean_session = None + self.protocol = None + self.transport = None + self.connection_disconnect_timeout = None + self.client_id = None + + # Message QoS and connection details + self.QoSlevel = None + self.inflight = None + self.queue_size = None + self.retry = None + self.keep_alive = None + + # EdgeSystem name + self.edge_system = None + + # TLS configurations + self.root_ca_cert = None + self.client_cert_file = None + self.client_key_file = None + self.cert_required = None + self.tls_version = None + self.cipher = None + + # Identity + self.identity = None + + # TLS configurations + self.tls_conf = None + + # QoS configurations + self.qos_details = None + + self.publish_message = None + self.mqtt_msg_attr = None + self.client = None + + @mock.patch.object(Mqtt, 'connect_soc') + def test_init_no_msgattr_no_client_id(self, mock_connect): + """ + Test MqttDccComms initialisation without messaging attributes and no client ID + :param mock_connect: Mocked MQTT connect method + :return: None + """ + + # Instantiate client for DCC communication + MqttDccComms(edge_system_name=self.edge_system.name, url=self.url, port=self.port, mqtt_msg_attr=None, + client_id="", clean_session=self.clean_session) + + # Read uuid.ini + self.config.read(self.uuid_file) + + edge_system_name = self.config.get('GATEWAY', 'name') + local_uuid = self.config.get('GATEWAY', 'local-uuid') + + # Compare stored edge system name + self.assertEquals(edge_system_name, self.edge_system.name) + + # Compare stored client-id + self.assertEquals(local_uuid, systemUUID().get_uuid(self.edge_system.name)) + + # Check _connect method call has been made + mock_connect.assert_called() + + @mock.patch.object(Mqtt, 'connect_soc') + def test_init_no_msgattr_with_client_id(self, mock_connect): + """ + Test MqttDccComms initialisation without messaging attributes and non-empty client ID + :param mock_connect: Mocked MQTT connect method + :return: None + """ + MqttDccComms(edge_system_name=self.edge_system.name, url=self.url, port=self.port, mqtt_msg_attr=None, + client_id=self.client_id, clean_session=self.clean_session) + + # Read uuid.ini + self.config.read(self.uuid_file) + + edge_system_name = self.config.get('GATEWAY', 'name') + local_uuid = self.config.get('GATEWAY', 'local-uuid') + + # Validate stored edge system name + self.assertEquals(edge_system_name, self.edge_system.name) + + # Validate stored client-id + self.assertEquals(local_uuid, self.client_id) + + # Check _connect method call has been made + mock_connect.assert_called() + + @mock.patch.object(Mqtt, 'connect_soc') + def test_init_with_valid_msgattr_with_client_id(self, mock_connect): + """ + Test MqttDccComms initialisation with valid messaging attributes and non-empty client id + :param mock_connect: Mocked MQTT connect method + :return: None + """ + + # Test with valid messaging attributes + client = MqttDccComms(edge_system_name=self.edge_system.name, url=self.url, port=self.port, + client_id=self.client_id, clean_session=self.clean_session, + mqtt_msg_attr=self.mqtt_msg_attr) + + # Validate client id + self.assertEquals(client.client_id, self.client_id) + + # Check connect_soc call has been made + mock_connect.assert_called() + + @mock.patch.object(Mqtt, 'connect_soc') + def test_init_with_valid_msg_attr_empty_client_id(self, mock_connect): + """ + Test MqttDccComms initialisation with valid messaging attributes with empty client id to check implementation + creating the ini file. + :param mock_connect: Mocked MQTT connect method + :return: None + """ + + # Test with valid messaging attributes + mqtt_dcc_client = MqttDccComms(edge_system_name=self.edge_system.name, + url=self.url, port=self.port, client_id="", clean_session=self.clean_session, + mqtt_msg_attr=self.mqtt_msg_attr) + + # Check uuid file get created or not + self.assertFalse(os.path.exists(self.uuid_file)) + + # Validate the client id + self.assertEqual(mqtt_dcc_client.client_id, "") + + # Check connect_soc call has been made + mock_connect.assert_called() + + def test_init_with_invalid_msg_attr(self): + """ + Test initialisation with invalid messaging attributes + :return: None + """ + # Pass invalid mqtt messaging attribute + self.assertRaises(TypeError, lambda: MqttDccComms(edge_system_name=self.edge_system.name, url=self.url, + port=self.port, clean_session=self.clean_session, + mqtt_msg_attr="")) + + @mock.patch.object(Mqtt, '__init__') + def test_connect(self, mock_init): + """ + Test MqttDccComms _connect method. + :param mock_init: Mocked MQTT init method + :return: None + """ + mock_init.return_value = None + + # Create MqttDCCComms object + mqtt_dcc_comms = MqttDccComms(edge_system_name=self.edge_system.name, url=self.url, port=self.port, + identity=self.identity, tls_conf=self.tls_conf, qos_details=self.qos_details, + client_id=self.client_id, clean_session=self.clean_session, + protocol=self.protocol, transport=self.transport, keep_alive=self.keep_alive, + mqtt_msg_attr=self.mqtt_msg_attr, + enable_authentication=self.enable_authentication, + conn_disconn_timeout=self.connection_disconnect_timeout) + + # Validate Mqtt __init__ called with following parameters + mock_init.assert_called_with(self.url, self.port, self.identity, self.tls_conf, + self.qos_details, self.client_id, self.clean_session, mqtt_dcc_comms.userdata, + self.protocol, self.transport, self.keep_alive, self.enable_authentication, + self.connection_disconnect_timeout) + + @mock.patch.object(Mqtt, 'disconnect') + def test_disconnect(self, mock_disconnect): + """ + Test MqttDccComms _disconnect method + :param mock_disconnect: Mocked MQTT disconnect method + :return: None + """ + # Call MqttDccComms _disconnect + self.client._disconnect() + + # Check call made to the Mqtt disconnect method + mock_disconnect.assert_called() + + @mock.patch.object(Mqtt, 'publish') + def test_send_with_msg_attr(self, mock_publish): + """ + Test MqttDccComms send method with messaging attributes + :param mock_publish: Mocked MQTT publish method + :return: None + """ + + # Create publish MqttMessagingAttributes + mqtt_msg_attr = MqttMessagingAttributes(pub_topic="test/publish_topic", pub_qos=2, pub_retain=True) + + # Call MqttDccComms send method + self.client.send(message=self.publish_message, msg_attr=mqtt_msg_attr) + + # Check implementation calling Mqtt publish method with following params + mock_publish.assert_called_with(mqtt_msg_attr.pub_topic, self.publish_message, + mqtt_msg_attr.pub_qos, mqtt_msg_attr.pub_retain) + + @mock.patch.object(Mqtt, 'publish') + def test_send_without_msg_attr(self, mock_publish): + """ + Test MqttDccComms send method without messaging attributes + :param mock_publish: Mocked MQTT publish method + :return: None + """ + # Call MqttDccComms send method + self.client.send(message=self.publish_message, msg_attr=None) + + # Check implementation calling Mqtt publish method with following params + mock_publish.assert_called_with(self.client.msg_attr.pub_topic, self.publish_message, + self.client.msg_attr.pub_qos, self.client.msg_attr.pub_retain) + + @mock.patch.object(Mqtt, 'subscribe') + def test_receive(self, mocked_subscribe): + """ + Test MqttDccComms receive method implementation with msg_attr. + :return: None + """ + # Create subscribe MqttMessagingAttributes messaging attribute + mqtt_msg_attr = MqttMessagingAttributes(self.edge_system.name, sub_topic="test/subscribe_topic", sub_qos=2, + sub_callback=callback_function) + + # Call receive with mqtt_msg_attr + self.client.receive(mqtt_msg_attr) + + # Check underline subscribe called with following parameters + mocked_subscribe.assert_called_with(mqtt_msg_attr.sub_topic, mqtt_msg_attr.sub_qos, + mqtt_msg_attr.sub_callback) + + @mock.patch.object(Mqtt, 'subscribe') + def test_receive_without_msg_attr(self, mocked_subscribe): + """ + Test MqttDccComms receive method implementation without msg_attr. + :return: None + """ + + # Call receive without mqtt_msg_attr + self.client.receive() + + # Check underline subscribe called with following parameters + mocked_subscribe.assert_called_with(self.mqtt_msg_attr.sub_topic, self.mqtt_msg_attr.sub_qos, + self.client.receive_message) + + @mock.patch.object(Mqtt, 'subscribe') + @mock.patch.object(Mqtt, '__init__') + def test_receive_with_msg_attr(self, init, mocked_subscribe): + """ + Test MqttDccComms receive method implementation with msg_attr. + :return: None + """ + # Assign mocked method return value + init.return_value = None + + # Instantiate client for DCC communication + self.client = MqttDccComms(edge_system_name=self.edge_system.name, url=self.url, port=self.port, + mqtt_msg_attr=None, client_id="", clean_session=self.clean_session) + + # Call receive with mqtt_msg_attr + self.client.receive(self.mqtt_msg_attr) + + # Check underline subscribe called with following parameters + mocked_subscribe.assert_called_with(self.mqtt_msg_attr.sub_topic, self.mqtt_msg_attr.sub_qos, + self.mqtt_msg_attr.sub_callback) + + @mock.patch.object(Queue.Queue, "put") + def test_receive_message(self, mocked_put): + """ + Test MqttDccComms receive_message method implementation. + :return: None + """ + # Creating the MQTTMessage + message = paho.MQTTMessage(topic="test/subscribe") + + # Create sample payload data + message.payload = "test-payload" + + # Call receive_message method + self.client.receive_message(self.client_id, self.client.userdata, msg=message) + + # Check put method called with following parameters + mocked_put.assert_called_with(message.payload) + + +if __name__ == '__main__': + unittest.main(verbosity=1) diff --git a/tests/unit/device_comms/__init__.py b/tests/unit/device_comms/__init__.py new file mode 100644 index 00000000..4badd4a2 --- /dev/null +++ b/tests/unit/device_comms/__init__.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------# +# Copyright © 2015-2016 VMware, Inc. All Rights Reserved. # +# # +# Licensed under the BSD 2-Clause License (the “License”); you may not use # +# this file except in compliance with the License. # +# # +# The BSD 2-Clause License # +# # +# Redistribution and use in source and binary forms, with or without # +# modification, are permitted provided that the following conditions are met:# +# # +# - Redistributions of source code must retain the above copyright notice, # +# this list of conditions and the following disclaimer. # +# # +# - Redistributions in binary form must reproduce the above copyright # +# notice, this list of conditions and the following disclaimer in the # +# documentation and/or other materials provided with the distribution. # +# # +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"# +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF # +# THE POSSIBILITY OF SUCH DAMAGE. # +# ----------------------------------------------------------------------------# diff --git a/tests/unit/device_comms/test_mqtt_device_comms.py b/tests/unit/device_comms/test_mqtt_device_comms.py new file mode 100644 index 00000000..6bd92a77 --- /dev/null +++ b/tests/unit/device_comms/test_mqtt_device_comms.py @@ -0,0 +1,255 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------# +# Copyright © 2015-2016 VMware, Inc. All Rights Reserved. # +# # +# Licensed under the BSD 2-Clause License (the “License”); you may not use # +# this file except in compliance with the License. # +# # +# The BSD 2-Clause License # +# # +# Redistribution and use in source and binary forms, with or without # +# modification, are permitted provided that the following conditions are met:# +# # +# - Redistributions of source code must retain the above copyright notice, # +# this list of conditions and the following disclaimer. # +# # +# - Redistributions in binary form must reproduce the above copyright # +# notice, this list of conditions and the following disclaimer in the # +# documentation and/or other materials provided with the distribution. # +# # +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"# +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF # +# THE POSSIBILITY OF SUCH DAMAGE. # +# ----------------------------------------------------------------------------# + +import unittest + +import mock + +from liota.device_comms.mqtt_device_comms import MqttDeviceComms +from liota.lib.transports.mqtt import Mqtt, QoSDetails +from liota.lib.utilities.identity import Identity +from liota.lib.utilities.tls_conf import TLSConf + + +# Callback function for subscribe and publish +def callback_function(): + pass + + +class MqttDeviceCommsTest(unittest.TestCase): + """ + Unit test cases for MQTT DeviceComms + """ + + @mock.patch.object(Mqtt, 'connect_soc') + def setUp(self, mock_connect): + """ + Setup all required parameters for MQTT device communication tests + :param mock_connect: Mocked MQTT connect method + :return: + """ + + # Broker details + self.url = "Broker-IP" + self.port = "Broker-Port" + self.mqtt_username = "test" + self.mqtt_password = "test" + self.enable_authentication = True + self.clean_session = True + self.protocol = "MQTTv311" + self.transport = "tcp" + self.connection_disconnect_timeout = 2 + self.user_data = None + self.client_id = "test-client" + + # Message QoS and connection details + self.QoSlevel = 2 + self.inflight = 20 + self.queue_size = 0 + self.retry = 5 + self.keep_alive = 60 + + # TLS configurations + self.root_ca_cert = "/etc/liota/mqtt/conf/ca.crt" + self.client_cert_file = "/etc/liota/mqtt/conf/client.crt" + self.client_key_file = "/etc/liota/mqtt/conf/client.key" + self.cert_required = "CERT_REQUIRED" + self.tls_version = "PROTOCOL_TLSv1" + self.cipher = None + + # Encapsulate the authentication details + self.identity = Identity(self.root_ca_cert, self.mqtt_username, self.mqtt_password, + self.client_cert_file, self.client_key_file) + + # Encapsulate TLS parameters + self.tls_conf = TLSConf(self.cert_required, self.tls_version, self.cipher) + + # Encapsulate QoS related parameters + self.qos_details = QoSDetails(self.inflight, self.queue_size, self.retry) + + self.publish_message = "test-message" + self.publish_topic = "publish-topic" + self.subscribe_topic = "subscribe-topic" + self.publish_message = "test-message" + + # Instantiate client for DCC communication + self.client = MqttDeviceComms(url=self.url, port=self.port, clean_session=self.clean_session) + + def tearDown(self): + """ + Clean up all parameters used in test cases + :return: + """ + self.url = None + self.port = None + self.mqtt_username = None + self.mqtt_password = None + self.enable_authentication = None + self.clean_session = None + self.protocol = None + self.transport = None + self.connection_disconnect_timeout = None + self.user_data = None + self.client_id = None + + # Message QoS and connection details + self.QoSlevel = None + self.inflight = None + self.queue_size = None + self.retry = None + self.keep_alive = None + + # EdgeSystem name + self.edge_system = None + + # TLS configurations + self.root_ca_cert = None + self.client_cert_file = None + self.client_key_file = None + self.cert_required = None + self.tls_version = None + self.cipher = None + + # Identity + self.identity = None + + # TLS configurations + self.tls_conf = None + + # QoS configurations + self.qos_details = None + + self.publish_message = None + self.mqtt_msg_attr = None + self.client = None + + self.publish_topic = None + self.subscribe_topic = None + self.publish_message = None + self.client = None + + @mock.patch.object(Mqtt, 'connect_soc') + def test_init(self, mock_connect): + """ + Test MqttDeviceComms initialisation. + :param mock_connect: Mocked MQTT connect method + :return: None + """ + # Create MqttDeviceComms class object + MqttDeviceComms(url=self.url, port=self.port, clean_session=self.clean_session) + + # Check implementation calling _connect method + mock_connect.assert_called() + + @mock.patch.object(Mqtt, '__init__') + def test_connect(self, mock_init): + """ + Test MqttDeviceComms _connect method implementation. + :param mock_init: Mocked MQTT init method + :return: + """ + mock_init.return_value = None + + # Create MqttDeviceComms class object + MqttDeviceComms(url=self.url, port=self.port, identity=self.identity, tls_conf=self.tls_conf, + qos_details=self.qos_details, client_id=self.client_id, + clean_session=self.clean_session, userdata=self.user_data, protocol=self.protocol, + transport=self.transport, keep_alive=self.keep_alive, + enable_authentication=self.enable_authentication, + conn_disconn_timeout=self.connection_disconnect_timeout) + + # Check Mqtt __init__ called with following params + mock_init.assert_called_with(self.url, self.port, self.identity, self.tls_conf, self.qos_details, + self.client_id, self.clean_session, self.user_data, self.protocol, + self.transport, self.keep_alive, self.enable_authentication, + self.connection_disconnect_timeout) + + @mock.patch.object(Mqtt, 'disconnect') + def test_disconnect(self, mock_disconnect): + """ + Test MqttDeviceComms _disconnect method. + :param mock_disconnect: Mocked MQTT disconnect method + :return: None + """ + # Call MqttDeviceComms _disconnect method + self.client._disconnect() + + # Check implementation calling Mqtt disconnect + mock_disconnect.assert_called() + + @mock.patch.object(Mqtt, 'publish') + def test_publish(self, mock_publish): + """ + Test MqttDeviceComms publish method implementation. + :param mock_publish: Mocked MQTT publish method + :return: None + """ + # Call MqttDeviceComms publish method + self.client.publish(topic=self.publish_topic, message=self.publish_message, qos=1, retain=False) + + # Check implementation calling the Mqtt publish method with following params + mock_publish.assert_called_with(self.publish_topic, self.publish_message, 1, False) + + @mock.patch.object(Mqtt, 'subscribe') + def test_subscribe(self, mock_subscribe): + """ + Tests MqttDeviceComms subscribe method implementation. + :param mock_subscribe: Mocked MQTT subscribe method + :return: None + """ + # Call MqttDeviceComms subscribe method + self.client.subscribe(topic=self.subscribe_topic, qos=1, callback=callback_function) + + # Check implementation calling the Mqtt subscribe with following params + mock_subscribe.assert_called_with(self.subscribe_topic, 1, callback_function) + + def test_send(self): + """ + Tests MqttDeviceComms send method implementation. + :return: None + """ + + # Check implementation raising the NotImplementedError exception or not + self.assertRaises(NotImplementedError, lambda: self.client.send(self.publish_message)) + + def test_receive(self): + """ + Tests MqttDeviceComms receive method implementation. + :return: None + """ + + # Check implementation raising the NotImplementedError exception or not + self.assertRaises(NotImplementedError, lambda: self.client.receive()) + + +if __name__ == '__main__': + unittest.main(verbosity=1) diff --git a/tests/unit/lib/transport/test_mqtt.py b/tests/unit/lib/transport/test_mqtt.py index c1d72f2c..c8cefc53 100644 --- a/tests/unit/lib/transport/test_mqtt.py +++ b/tests/unit/lib/transport/test_mqtt.py @@ -30,20 +30,21 @@ # THE POSSIBILITY OF SUCH DAMAGE. # # ----------------------------------------------------------------------------# -import unittest +import os +import ssl import sys +import unittest import mock from paho.mqtt.client import Client +from liota.entities.edge_systems.dell5k_edge_system import Dell5KEdgeSystem from liota.lib.transports.mqtt import Mqtt from liota.lib.transports.mqtt import MqttMessagingAttributes, QoSDetails -from liota.entities.edge_systems.dell5k_edge_system import Dell5KEdgeSystem from liota.lib.utilities.identity import Identity from liota.lib.utilities.tls_conf import TLSConf from liota.lib.utilities.utility import systemUUID - # MQTT configurations config = {} connect_rc = 0 @@ -250,29 +251,19 @@ def test_connect_soc_empty_root_ca(self): self.client_clean_session, self.user_data, self.protocol, self.transport, self.keep_alive, self.enable_authentication, self.connection_disconnect_timeout) - def test_connect_soc_invalid_client_ca(self): + @mock.patch.object(Client, "tls_set") + @mock.patch.object(os.path, "exists") + def test_connect_soc_empty_client_ca(self, exists, tls_set): """ - Test case to test validation for invalid client ca validation. + Test case to test validation for empty client certificate. :return: None """ - # Setting invalid client ca path - self.client_cert_file = "Invalid Client CA Path" - - # Encapsulate the authentication details - self.identity = Identity(self.root_ca_cert, self.mqtt_username, self.mqtt_password, - self.client_cert_file, self.client_key_file) - # Checking whether implementation raising the ValueError for invalid client ca_certs - with self.assertRaises(ValueError): - Mqtt(self.url, self.port, self.identity, self.tls_conf, self.qos_details, self.client_id, - self.client_clean_session, self.user_data, self.protocol, self.transport, - self.keep_alive, self.enable_authentication, self.connection_disconnect_timeout) + # Assigning the return value for mocked methods. + # To ensure os.path.exists returns True for the root_ca_cert file. + exists.return_value = True + tls_set.return_value = None - def test_connect_soc_empty_client_ca(self): - """ - Test case to test validation for empty client certificate. - :return: None - """ # Setting invalid client ca path self.client_cert_file = "" @@ -286,29 +277,19 @@ def test_connect_soc_empty_client_ca(self): self.client_clean_session, self.user_data, self.protocol, self.transport, self.keep_alive, self.enable_authentication, self.connection_disconnect_timeout) - def test_connect_soc_invalid_client_key(self): + @mock.patch.object(Client, "tls_set") + @mock.patch.object(os.path, "exists") + def test_connect_soc_empty_client_key(self, exists, tls_set): """ - Test case to test the validation for invalid client key. + Test case to test validation for empty client key. :return: None """ - # Setting invalid client key path - self.client_key_file = "Invalid Client Key Path" - - # Encapsulate the authentication details - self.identity = Identity(self.root_ca_cert, self.mqtt_username, self.mqtt_password, - self.client_cert_file, self.client_key_file) - # Checking whether implementation raising the ValueError for invalid client key - with self.assertRaises(ValueError): - Mqtt(self.url, self.port, self.identity, self.tls_conf, self.qos_details, self.client_id, - self.client_clean_session, self.user_data, self.protocol, self.transport, - self.keep_alive, self.enable_authentication, self.connection_disconnect_timeout) + # Assigning the return value for mocked methods + # To ensure os.path.exists returns True for the root_ca_cert file. + exists.return_value = True + tls_set.return_value = None - def test_connect_soc_empty_client_key(self): - """ - Test case to test validation for empty client key. - :return: None - """ # Setting invalid client cert path self.client_key_file = "" @@ -322,12 +303,18 @@ def test_connect_soc_empty_client_key(self): self.client_clean_session, self.user_data, self.protocol, self.transport, self.keep_alive, self.enable_authentication, self.connection_disconnect_timeout) - def test_connect_soc_for_empty_username(self): + @mock.patch.object(Client, "tls_set") + @mock.patch.object(os.path, "exists") + def test_connect_soc_for_empty_username(self, exists, tls_set): """ Test case to test validation for empty username. :return: None """ + # Assigning the return value for mocked methods + exists.return_value = True + tls_set.return_value = None + # Encapsulate the authentication details self.identity = Identity(self.root_ca_cert, "", self.mqtt_password, self.client_cert_file, self.client_key_file) @@ -338,12 +325,18 @@ def test_connect_soc_for_empty_username(self): self.client_clean_session, self.user_data, self.protocol, self.transport, self.keep_alive, self.enable_authentication, self.connection_disconnect_timeout) - def test_connect_soc_for_empty_password(self): + @mock.patch.object(Client, "tls_set") + @mock.patch.object(os.path, "exists") + def test_connect_soc_for_empty_password(self, exists, tls_set): """ Test case to test validation for empty password. :return: None """ + # Assigning the return value for mocked methods + exists.return_value = True + tls_set.return_value = None + # Encapsulate the authentication details self.identity = Identity(self.root_ca_cert, self.mqtt_username, "", self.client_cert_file, self.client_key_file) @@ -354,13 +347,19 @@ def test_connect_soc_for_empty_password(self): self.client_clean_session, self.user_data, self.protocol, self.transport, self.keep_alive, self.enable_authentication, self.connection_disconnect_timeout) - def test_connect_soc_connection_setup(self): + @mock.patch.object(Client, "tls_set") + @mock.patch.object(os.path, 'exists') + def test_connect_soc_connection_setup(self, exists, tls_set): """ Test case to test connection setup with connect_soc. :return: None """ global connect_rc + # Assigning the return value for mocked methods + exists.return_value = True + tls_set.return_value = None + # Setting connection accepted flag connect_rc = 0 @@ -375,13 +374,24 @@ def test_connect_soc_connection_setup(self): # Check we are able to generate Mqtt class object self.assertIsInstance(mqtt_client, Mqtt, "Invalid Mqtt class implementation") - def test_connect_soc_connection_timeout(self): + # Check mocked method call has been made with following params + tls_set.assert_called_with(self.identity.root_ca_cert, self.identity.cert_file, self.identity.key_file, + cert_reqs=getattr(ssl, self.tls_conf.cert_required), + tls_version=getattr(ssl, self.tls_conf.tls_version), ciphers=self.tls_conf.cipher) + + @mock.patch.object(Client, "tls_set") + @mock.patch.object(os.path, "exists") + def test_connect_soc_connection_timeout(self, exists, tls_set): """ Test case to test connection setup timeout with connect_soc. :return: None """ global connect_rc + # Assigning the return value for mocked methods + exists.return_value = True + tls_set.return_value = None + # Setting connection timeout flag connect_rc = sys.maxsize @@ -396,13 +406,19 @@ def test_connect_soc_connection_timeout(self): self.client_clean_session, self.user_data, self.protocol, self.transport, self.keep_alive, self.enable_authentication, self.connection_disconnect_timeout) - def test_connect_soc_connection_refused(self): + @mock.patch.object(Client, "tls_set") + @mock.patch.object(os.path, "exists") + def test_connect_soc_connection_refused(self, exists, tls_set): """ Test case to test broker connection refused with connect_soc. :return: None """ global connect_rc + # Assigning the return value for mocked methods + exists.return_value = True + tls_set.return_value = None + # Setting connection refused flag connect_rc = 1 @@ -417,13 +433,22 @@ def test_connect_soc_connection_refused(self): self.client_clean_session, self.user_data, self.protocol, self.transport, self.keep_alive, self.enable_authentication, self.connection_disconnect_timeout) - def test_mqtt_connection_over_only_root_ca_cert(self): + @mock.patch.object(Client, "tls_set") + @mock.patch.object(os.path, 'exists') + @mock.patch.object(Client, "loop_start") + def test_mqtt_connection_over_only_root_ca_cert(self, loop_start, exists, tls_set): """ Test case to test the implementation of connection_soc method for root_ca only. :return: None """ + global connect_rc + # Assigning the return value for mocked methods + exists.return_value = True + tls_set.return_value = None + loop_start.return_value = None + # Encapsulate the authentication details self.identity = Identity(self.root_ca_cert, self.mqtt_username, self.mqtt_password, None, None) @@ -432,14 +457,21 @@ def test_mqtt_connection_over_only_root_ca_cert(self): # Mocked the connect and loop_start method of Paho library Client.connect = mocked_connect - Client.loop_start = mocked_loop_start mqtt_client = Mqtt(self.url, self.port, self.identity, self.tls_conf, self.qos_details, self.client_id, self.client_clean_session, self.user_data, self.protocol, self.transport, self.keep_alive, self.enable_authentication, self.connection_disconnect_timeout) + # Check we are able to generate Mqtt class object self.assertIsInstance(mqtt_client, Mqtt, "Invalid Mqtt class implementation") + # Check mocked method call has been made + tls_set.assert_called_with(self.identity.root_ca_cert, cert_reqs=getattr(ssl, self.tls_conf.cert_required), + tls_version=getattr(ssl, self.tls_conf.tls_version), ciphers=self.tls_conf.cipher) + + # Check implementation calling the loop start method + loop_start.assert_called() + @mock.patch.object(Mqtt, 'connect_soc') def test_client_clean_session_and_client_id_implementation(self, mock_connect): """ @@ -449,7 +481,7 @@ def test_client_clean_session_and_client_id_implementation(self, mock_connect): """ # Mocked connect_soc method - mock_connect.returnvalue = None + mock_connect.return_value = None mqtt_client = Mqtt(self.url, self.port, self.identity, self.tls_conf, self.qos_details, self.client_id, self.client_clean_session, self.user_data, self.protocol, self.transport, self.keep_alive, @@ -463,13 +495,21 @@ def test_client_clean_session_and_client_id_implementation(self, mock_connect): # Checking the client_id implementation self.assertEqual(self.client_id, client_id, "Received invalid client-id, check the implementation.") - def test_clean_disconnect_connection(self): + @mock.patch.object(Client, "tls_set") + @mock.patch.object(os.path, "exists") + @mock.patch.object(Client, "loop_stop") + def test_clean_disconnect_connection(self, loop_stop, exists, tls_set): """ Test case to test clean-connection disconnect. :return: None """ global connect_rc, disconnect_rc + # Assigning the return value for mocked methods + loop_stop.return_value = None + exists.return_value = True + tls_set.return_value = None + # Setting connection accepted flag connect_rc = 0 # Setting connection disconnect flag @@ -479,21 +519,32 @@ def test_clean_disconnect_connection(self): Client.connect = mocked_connect Client.disconnect = mocked_disconnect Client.loop_start = mocked_loop_start - Client.loop_stop = mocked_loop_stop mqtt_client = Mqtt(self.url, self.port, self.identity, self.tls_conf, self.qos_details, self.client_id, self.client_clean_session, self.user_data, self.protocol, self.transport, self.keep_alive, self.enable_authentication, self.connection_disconnect_timeout) - self.assertEqual(mqtt_client.disconnect(), None) + # Call disconnect method + mqtt_client.disconnect() - def test_timeout_disconnect_connection(self): + # Check loop_start method call has been made + loop_stop.assert_called() + + @mock.patch.object(Client, "tls_set") + @mock.patch.object(os.path, "exists") + @mock.patch.object(Client, "loop_stop") + def test_timeout_disconnect_connection(self, loop_stop, exists, tls_set): """ Test case to test timeout-connection disconnect. :return: None """ global connect_rc, disconnect_rc + # Assigning the return value for mocked methods + exists.return_value = True + tls_set.return_value = None + loop_stop.return_value = None + # Setting connection accepted flag connect_rc = 0 # Setting connection disconnect flag @@ -503,7 +554,7 @@ def test_timeout_disconnect_connection(self): Client.connect = mocked_connect Client.disconnect = mocked_disconnect Client.loop_start = mocked_loop_start - Client.loop_stop = mocked_loop_stop + # Checking whether implementation raising the Exception for broker disconnect timeout with self.assertRaises(Exception): @@ -511,15 +562,22 @@ def test_timeout_disconnect_connection(self): self.client_clean_session, self.user_data, self.protocol, self.transport, self.keep_alive, self.enable_authentication, self.connection_disconnect_timeout) + # Call Mqtt disconnect method mqtt_client.disconnect() - def test_invalid_disconnect_connection(self): + @mock.patch.object(Client, "tls_set") + @mock.patch.object(os.path, "exists") + def test_invalid_disconnect_connection(self, exists, tls_set): """ Test case to test invalid-connection disconnect. :return: None """ global connect_rc, disconnect_rc + # Assigning the return value for mocked methods + exists.return_value = True + tls_set.return_value = None + # Setting connection accepted flag connect_rc = 0 # Setting connection disconnect flag @@ -658,13 +716,6 @@ def test_validation_of_sub_callback_mqtt_messaging_attributes(self): self.assertRaises(ValueError, MqttMessagingAttributes, self.edge_system.name, sub_callback="") - def test_validation_of_sub_and_pub_topic_attributes(self): - """ - Test case to test validation of subscribe, publish and sub_callback attributes. - :return: None - """ - self.assertRaises(ValueError, MqttMessagingAttributes) - class QoSDetailsTest(unittest.TestCase): def setUp(self): From 00d502f610661b6b083d8eef6618fc1a7338371f Mon Sep 17 00:00:00 2001 From: Venkat2811 Date: Wed, 5 Jul 2017 12:01:53 +0530 Subject: [PATCH 3/3] correction in copyright header, requirements --- requirements.txt | 2 -- tests/unit/dcc_comms/__init__.py | 2 +- tests/unit/dcc_comms/test_mqtt_dcc_comms.py | 2 +- tests/unit/dccs/__init__.py | 2 +- tests/unit/dccs/test_aws_iot.py | 2 +- tests/unit/device_comms/__init__.py | 2 +- tests/unit/device_comms/test_mqtt_device_comms.py | 2 +- tests/unit/lib/transport/__init__.py | 2 +- tests/unit/lib/transport/test_mqtt.py | 2 +- 9 files changed, 8 insertions(+), 10 deletions(-) diff --git a/requirements.txt b/requirements.txt index 50b4f056..7ee68b76 100755 --- a/requirements.txt +++ b/requirements.txt @@ -5,5 +5,3 @@ mock==2.0.0 paho-mqtt==1.2 pint==0.7.2 websocket-client==0.37.0 - - diff --git a/tests/unit/dcc_comms/__init__.py b/tests/unit/dcc_comms/__init__.py index 4badd4a2..456d3c7a 100644 --- a/tests/unit/dcc_comms/__init__.py +++ b/tests/unit/dcc_comms/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # ----------------------------------------------------------------------------# -# Copyright © 2015-2016 VMware, Inc. All Rights Reserved. # +# Copyright © 2015-2017 VMware, Inc. All Rights Reserved. # # # # Licensed under the BSD 2-Clause License (the “License”); you may not use # # this file except in compliance with the License. # diff --git a/tests/unit/dcc_comms/test_mqtt_dcc_comms.py b/tests/unit/dcc_comms/test_mqtt_dcc_comms.py index d786e676..033068f2 100644 --- a/tests/unit/dcc_comms/test_mqtt_dcc_comms.py +++ b/tests/unit/dcc_comms/test_mqtt_dcc_comms.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # ----------------------------------------------------------------------------# -# Copyright © 2015-2016 VMware, Inc. All Rights Reserved. # +# Copyright © 2015-2017 VMware, Inc. All Rights Reserved. # # # # Licensed under the BSD 2-Clause License (the “License”); you may not use # # this file except in compliance with the License. # diff --git a/tests/unit/dccs/__init__.py b/tests/unit/dccs/__init__.py index 4badd4a2..456d3c7a 100644 --- a/tests/unit/dccs/__init__.py +++ b/tests/unit/dccs/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # ----------------------------------------------------------------------------# -# Copyright © 2015-2016 VMware, Inc. All Rights Reserved. # +# Copyright © 2015-2017 VMware, Inc. All Rights Reserved. # # # # Licensed under the BSD 2-Clause License (the “License”); you may not use # # this file except in compliance with the License. # diff --git a/tests/unit/dccs/test_aws_iot.py b/tests/unit/dccs/test_aws_iot.py index ddcbaeac..33358a10 100644 --- a/tests/unit/dccs/test_aws_iot.py +++ b/tests/unit/dccs/test_aws_iot.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # ----------------------------------------------------------------------------# -# Copyright © 2015-2016 VMware, Inc. All Rights Reserved. # +# Copyright © 2015-2017 VMware, Inc. All Rights Reserved. # # # # Licensed under the BSD 2-Clause License (the “License”); you may not use # # this file except in compliance with the License. # diff --git a/tests/unit/device_comms/__init__.py b/tests/unit/device_comms/__init__.py index 4badd4a2..456d3c7a 100644 --- a/tests/unit/device_comms/__init__.py +++ b/tests/unit/device_comms/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # ----------------------------------------------------------------------------# -# Copyright © 2015-2016 VMware, Inc. All Rights Reserved. # +# Copyright © 2015-2017 VMware, Inc. All Rights Reserved. # # # # Licensed under the BSD 2-Clause License (the “License”); you may not use # # this file except in compliance with the License. # diff --git a/tests/unit/device_comms/test_mqtt_device_comms.py b/tests/unit/device_comms/test_mqtt_device_comms.py index 6bd92a77..c9791e37 100644 --- a/tests/unit/device_comms/test_mqtt_device_comms.py +++ b/tests/unit/device_comms/test_mqtt_device_comms.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # ----------------------------------------------------------------------------# -# Copyright © 2015-2016 VMware, Inc. All Rights Reserved. # +# Copyright © 2015-2017 VMware, Inc. All Rights Reserved. # # # # Licensed under the BSD 2-Clause License (the “License”); you may not use # # this file except in compliance with the License. # diff --git a/tests/unit/lib/transport/__init__.py b/tests/unit/lib/transport/__init__.py index 4badd4a2..456d3c7a 100644 --- a/tests/unit/lib/transport/__init__.py +++ b/tests/unit/lib/transport/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # ----------------------------------------------------------------------------# -# Copyright © 2015-2016 VMware, Inc. All Rights Reserved. # +# Copyright © 2015-2017 VMware, Inc. All Rights Reserved. # # # # Licensed under the BSD 2-Clause License (the “License”); you may not use # # this file except in compliance with the License. # diff --git a/tests/unit/lib/transport/test_mqtt.py b/tests/unit/lib/transport/test_mqtt.py index c8cefc53..a75e9bdc 100644 --- a/tests/unit/lib/transport/test_mqtt.py +++ b/tests/unit/lib/transport/test_mqtt.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # ----------------------------------------------------------------------------# -# Copyright © 2015-2016 VMware, Inc. All Rights Reserved. # +# Copyright © 2015-2017 VMware, Inc. All Rights Reserved. # # # # Licensed under the BSD 2-Clause License (the “License”); you may not use # # this file except in compliance with the License. #