def test_validate_properties_name_miss_under_interface( self, mock_device_svc, mock_device_template_svc): # setup mock_device_template_svc.get_device_template.return_value = central_models.TemplateV1( self._duplicate_property_template) monitor = PropertyMonitor( cmd=None, app_id=app_id, device_id=device_id, token=None, central_dns_suffix=None, ) # invalid interface / property definition = {"definition": "test_definition"} issues = monitor._validate_payload_against_entities( definition, list(definition.keys())[0], Severity.warning, ) assert ( issues[0].details == "Device is sending data that has not been defined in the device template." " Following capabilities have NOT been defined in the device template '['definition']'." " Following capabilities have been defined in the device template (grouped by interface)" " '{'urn:sampleApp:groupOne_bz:2': ['addRootProperty', 'addRootPropertyReadOnly', 'addRootProperty2']," " 'urn:sampleApp:groupOne_bz:_rpgcmdpo:1': ['Model', 'Version', 'TotalStorage']," " 'urn:sampleApp:groupTwo_bz:myxqftpsr:2': ['Model', 'Manufacturer']," " 'urn:sampleApp:groupThree_bz:myxqftpsr:2': ['Manufacturer', 'Version', 'Model', 'OsName']}'. " )
def test_validate_against_bad_template_should_not_throw(self): # setup device_template = "an_unparseable_template" properties = MessageProperties(content_encoding=self.encoding, content_type=self.content_type) message = Message( body=json.dumps(self.bad_dcm_payload).encode(), properties=properties, annotations={ common_parser.DEVICE_ID_IDENTIFIER: self.device_id.encode() }, application_properties=_encode_app_props(self.app_properties), ) args = CommonParserArguments(properties=["all"]) parser = self._create_parser(device_template=device_template, message=message, args=args) # haven't found a better way to force the error to occur within parser parser._central_template_provider.get_device_template = lambda x: central_models.TemplateV1( device_template) # act parsed_msg = parser.parse_message() # verify assert parsed_msg["event"]["payload"] == self.bad_dcm_payload assert parsed_msg["event"]["origin"] == self.device_id expected_details = strings.device_template_not_found( "Could not parse iot central device template.") _validate_issues(parser, Severity.error, 1, 1, [expected_details])
def test_template_component_list(self): expected_component_list = [ "_rpgcmdpo", "RS40OccupancySensorV36fy", ] template = central_models.TemplateV1( load_json(FileNames.central_property_validation_template_file) ) assert collections.Counter(template.components.keys()) == collections.Counter( expected_component_list )
def test_extract_schema_type_component(self): expected_mapping = { "component1Prop": "boolean", "testComponent": "boolean", "component1PropReadonly": "boolean", "component1Prop2": "boolean", } template = central_models.TemplateV1( load_json(FileNames.central_property_validation_template_file) ) for key, val in expected_mapping.items(): schema = template.get_schema(key, is_component=True) schema_type = extract_schema_type(schema) assert schema_type == val
def test_template_interface_list(self): expected_interface_list = [ "urn:sampleApp:groupOne_bz:_rpgcmdpo:1", "urn:sampleApp:groupTwo_bz:myxqftpsr:2", "urn:sampleApp:groupThree_bz:myxqftpsr:2", "urn:sampleApp:groupOne_bz:2", ] template = central_models.TemplateV1( load_json(FileNames.central_property_validation_template_file) ) assert collections.Counter(template.interfaces.keys()) == collections.Counter( expected_interface_list )
def test_validate_invalid_telmetry_component_template_should_fail(self): # setup device_template = central_models.TemplateV1( load_json(FileNames.central_property_validation_template_file)) properties = MessageProperties(content_encoding=self.encoding, content_type=self.content_type) message = Message( body=json.dumps(self.bad_dcm_payload).encode(), properties=properties, annotations={ common_parser.DEVICE_ID_IDENTIFIER: self.device_id.encode(), common_parser.COMPONENT_NAME_IDENTIFIER: list(device_template.components.keys())[1].encode(), }, application_properties=_encode_app_props(self.app_properties), ) args = CommonParserArguments(properties=["all"]) parser = self._create_parser(device_template=device_template, message=message, args=args) # act parsed_msg = parser.parse_message() # verify assert parsed_msg["event"]["payload"] == self.bad_dcm_payload assert parsed_msg["event"]["origin"] == self.device_id device_identifier = str(common_parser.DEVICE_ID_IDENTIFIER, "utf8") assert parsed_msg["event"]["annotations"][ device_identifier] == self.device_id component_identifier = str(common_parser.COMPONENT_NAME_IDENTIFIER, "utf8") assert ( parsed_msg["event"]["annotations"][component_identifier] == list( device_template.components.keys())[1]) properties = parsed_msg["event"]["properties"] assert properties["system"]["content_encoding"] == self.encoding assert properties["system"]["content_type"] == self.content_type assert properties["application"] == self.app_properties expected_details = strings.invalid_field_name_component_mismatch_template( list(self.bad_dcm_payload.keys()), device_template.component_schema_names, ) _validate_issues(parser, Severity.warning, 1, 1, [expected_details])
def create_device_template( cmd, app_id: str, device_template_id: str, payload: dict, token: str, central_dns_suffix=CENTRAL_ENDPOINT, api_version=ApiVersion.v1.value, ) -> Union[central_models.TemplatePreview, central_models.TemplateV1]: """ Create a device template in IoTC Args: cmd: command passed into az app_id: name of app (used for forming request URL) device_template_id: case sensitive device template id, payload: see example payload available in <repo-root>/azext_iot/tests/central/json/device_template_int_test.json or check here for more information https://docs.microsoft.com/en-us/rest/api/iotcentral/devicetemplates token: (OPTIONAL) authorization token to fetch device details from IoTC. MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') central_dns_suffix: {centralDnsSuffixInPath} as found in docs Returns: device: dict """ url = "https://{}.{}/{}/{}".format(app_id, central_dns_suffix, BASE_PATH, device_template_id) headers = _utility.get_headers(token, cmd, has_json_payload=True) # Construct parameters query_parameters = {} query_parameters["api-version"] = api_version response = requests.put(url, headers=headers, json=payload, params=query_parameters) if api_version == ApiVersion.preview.value: return central_models.TemplatePreview( _utility.try_extract_result(response)) else: return central_models.TemplateV1(_utility.try_extract_result(response))
def list_device_templates( cmd, app_id: str, token: str, central_dns_suffix=CENTRAL_ENDPOINT, api_version=ApiVersion.v1.value, ) -> List[Union[central_models.TemplatePreview, central_models.TemplateV1]]: """ Get a list of all device templates in IoTC Args: cmd: command passed into az app_id: name of app (used for forming request URL) token: (OPTIONAL) authorization token to fetch device details from IoTC. MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') central_dns_suffix: {centralDnsSuffixInPath} as found in docs Returns: device: dict """ url = "https://{}.{}/{}".format(app_id, central_dns_suffix, BASE_PATH) headers = _utility.get_headers(token, cmd) # Construct parameters query_parameters = {} query_parameters["api-version"] = api_version response = requests.get(url, headers=headers, params=query_parameters) result = _utility.try_extract_result(response) if "value" not in result: raise CLIError("Value is not present in body: {}".format(result)) if api_version == ApiVersion.preview.value: return [ central_models.TemplatePreview(item) for item in result["value"] ] else: return [central_models.TemplateV1(item) for item in result["value"]]
def test_validate_properties_declared_multiple_interfaces( self, mock_device_svc, mock_device_template_svc): # setup mock_device_template_svc.get_device_template.return_value = central_models.TemplateV1( self._duplicate_property_template) monitor = PropertyMonitor( cmd=None, app_id=app_id, device_id=device_id, token=None, central_dns_suffix=None, ) model = {"Model": "test_model"} issues = monitor._validate_payload_against_entities( model, list(model.keys())[0], Severity.warning, ) assert ( issues[0].details == "Duplicate property: 'Model' found under following " "interfaces ['urn:sampleApp:groupOne_bz:_rpgcmdpo:1', 'urn:sampleApp:groupTwo_bz:myxqftpsr:2', " "'urn:sampleApp:groupThree_bz:myxqftpsr:2'] " "in the device model. Either provide the interface name as part " "of the device payload or make the propery name unique in the device model" ) version = {"OsName": "test_osName"} issues = monitor._validate_payload_against_entities( version, list(version.keys())[0], Severity.warning, ) assert len(issues) == 0
def get_device_template( cmd, app_id: str, device_template_id: str, token: str, central_dns_suffix=CENTRAL_ENDPOINT, api_version=ApiVersion.v1.value, ) -> Union[central_models.TemplatePreview, central_models.TemplateV1]: """ Get a specific device template from IoTC Args: cmd: command passed into az device_template_id: case sensitive device template id, app_id: name of app (used for forming request URL) token: (OPTIONAL) authorization token to fetch device details from IoTC. MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') central_dns_suffix: {centralDnsSuffixInPath} as found in docs Returns: device: dict """ url = "https://{}.{}/{}/{}".format(app_id, central_dns_suffix, BASE_PATH, device_template_id) headers = _utility.get_headers(token, cmd) # Construct parameters query_parameters = {} query_parameters["api-version"] = api_version response = requests.get(url, headers=headers, params=query_parameters) if api_version == ApiVersion.preview.value: return central_models.TemplatePreview( _utility.try_extract_result(response)) else: return central_models.TemplateV1(_utility.try_extract_result(response))
def test_validate_properties_name_miss_under_component( self, mock_device_svc, mock_device_template_svc): # setup mock_device_template_svc.get_device_template.return_value = central_models.TemplateV1( self._duplicate_property_template) monitor = PropertyMonitor( cmd=None, app_id=app_id, device_id=device_id, token=None, central_dns_suffix=None, ) # invalid component property definition = { PNP_DTDLV2_COMPONENT_MARKER: "c", "data": { "definition": "test_definition" }, } issues = monitor._validate_payload_against_entities( definition, list(definition.keys())[0], Severity.warning, ) assert ( issues[0].details == "Device is sending data that has not been defined in the device template. " "Following capabilities have NOT been defined in the device template '['data']'. " "Following capabilities have been defined in the device template (grouped by components) " "'{'_rpgcmdpo': ['component1Prop', 'testComponent', 'component1PropReadonly', 'component1Prop2'], " "'RS40OccupancySensorV36fy': ['component2prop', 'testComponent', 'component2PropReadonly', " "'component2Prop2', 'component1Telemetry']}'. ")
def test_extract_schema_type(self): expected_mapping = { "Bool": "boolean", "Date": "date", "DateTime": "dateTime", "Double": "double", "Duration": "duration", "IntEnum": "Enum", "StringEnum": "Enum", "Float": "float", "Geopoint": "geopoint", "Long": "long", "Object": "Object", "String": "string", "Time": "time", "Vector": "vector", } template = central_models.TemplateV1( load_json(FileNames.central_device_template_file) ) for key, val in expected_mapping.items(): schema = template.get_schema(key) schema_type = extract_schema_type(schema) assert schema_type == val
def _get_template(self): return central_models.TemplateV1( load_json(FileNames.central_device_template_file))
def test_object_deep(self, value, expected_result): template = central_models.TemplateV1( load_json(FileNames.central_deeply_nested_device_template_file) ) schema = template.get_schema("RidiculousObject") assert validate(schema, value) == expected_result
def test_str_enum(self, value, expected_result): template = central_models.TemplateV1( load_json(FileNames.central_device_template_file) ) schema = template.get_schema("StringEnum") assert validate(schema, value) == expected_result