def test_extract_schema_type_component(self): expected_mapping = { "component1Prop": "boolean", "testComponent": "boolean", "component1PropReadonly": "boolean", "component1Prop2": "boolean", } template = Template( 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 create_device_template( cmd, app_id: str, device_template_id: str, payload: dict, token: str, central_dns_suffix=CENTRAL_ENDPOINT, ) -> Template: """ 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) response = requests.put(url, headers=headers, json=payload) return Template(_utility.try_extract_result(response))
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 = Template( 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 get_device_template( cmd, app_id: str, device_template_id: str, token: str, central_dns_suffix=CENTRAL_ENDPOINT, ) -> Template: """ 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) response = requests.get(url, headers=headers) return Template(_utility.try_extract_result(response))
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: Template( 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 list_device_templates( cmd, app_id: str, token: str, central_dns_suffix=CENTRAL_ENDPOINT, ) -> List[Template]: """ 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) response = requests.get(url, headers=headers) result = _utility.try_extract_result(response) if "value" not in result: raise CLIError("Value is not present in body: {}".format(result)) return [Template(item) for item in result["value"]]
def test_template_component_list(self): expected_component_list = [ "_rpgcmdpo", "RS40OccupancySensorV36fy", ] template = Template( load_json(FileNames.central_property_validation_template_file)) assert collections.Counter( template.components.keys()) == collections.Counter( expected_component_list)
def test_validate_properties_name_miss( self, mock_device_svc, mock_device_template_svc ): # setup mock_device_template_svc.get_device_template.return_value = Template( 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_interfaces( 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) '{'groupOne_g4': ['Model', 'Version', 'TotalStorage'], " "'groupTwo_ed': ['Model', 'Manufacturer'], 'groupThree_ed': ['Manufacturer', " "'Version', 'Model', 'OsName']}'. " ) # invalid and valid property with valid interface property_collection = { "Model": "test_model", "Manufacturer": "test_manufacturer", "OsName": "test_osName", } issues = monitor._validate_payload_against_interfaces( property_collection, "groupOne_g4", 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 " "'['Manufacturer', 'OsName']'. Following capabilities have been defined in the device template " "(grouped by interface) '{'groupOne_g4': ['Model', 'Version', 'TotalStorage'], " "'groupTwo_ed': ['Model', 'Manufacturer'], 'groupThree_ed': ['Manufacturer', " "'Version', 'Model', 'OsName']}'. " )
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 = Template(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 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 = Template( 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 = Template( 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 _validate_payload_against_interfaces( self, payload: dict, template: Template, ): name_miss = [] for telemetry_name, telemetry in payload.items(): schema = template.get_schema(name=telemetry_name, interface_name=self.interface_name) if not schema: name_miss.append(telemetry_name) else: self._process_telemetry(telemetry_name, schema, telemetry) if name_miss: details = strings.invalid_field_name_mismatch_template( name_miss, template.schema_names) self._add_central_issue(severity=Severity.warning, details=details)
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 = Template( 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 test_validate_properties_severity_level( self, mock_device_svc, mock_device_template_svc ): # setup mock_device_template_svc.get_device_template.return_value = Template( self._duplicate_property_template ) monitor = PropertyMonitor( cmd=None, app_id=app_id, device_id=device_id, token=None, central_dns_suffix=None, ) # severity level info definition = {"definition": "test_definition"} issues = monitor._validate_payload_against_interfaces( definition, list(definition.keys())[0], Severity.info, ) 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) '{'groupOne_g4': ['Model', 'Version', 'TotalStorage'], " "'groupTwo_ed': ['Model', 'Manufacturer'], 'groupThree_ed': ['Manufacturer', " "'Version', 'Model', 'OsName']}'. " ) # severity level error issues = monitor._validate_payload_against_interfaces( definition, list(definition.keys())[0], Severity.error, ) assert len(issues) == 0
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 = Template( 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 _validate_payload(self, payload: dict, template: Template, is_component: bool): name_miss = [] for telemetry_name, telemetry in payload.items(): schema = template.get_schema( name=telemetry_name, identifier=self.component_name, is_component=is_component, ) if not schema: name_miss.append(telemetry_name) else: self._process_telemetry(telemetry_name, schema, telemetry) if name_miss: if is_component: details = strings.invalid_field_name_component_mismatch_template( name_miss, template.component_schema_names) else: details = strings.invalid_field_name_mismatch_template( name_miss, template.schema_names, ) self._add_central_issue(severity=Severity.warning, details=details)
def test_object_deep(self, value, expected_result): template = Template( 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 = Template(load_json(FileNames.central_device_template_file)) schema = template.get_schema("StringEnum") assert validate(schema, value) == expected_result
def _get_template(self): return Template(load_json(FileNames.central_device_template_file))