From 87a4b7979d49d6b69f329b909f2022f00a1102be Mon Sep 17 00:00:00 2001 From: Luca Druda Date: Wed, 21 Jul 2021 22:01:12 +0200 Subject: [PATCH] Fill gaps with IoT Central REST apis (#390) * add device groups and roles commands * include role_id in help * add device group tests * add central roles unit tests * update version * fix formatting * remove unused * Update mock import. * add preview tag * add int tests for new commands Co-authored-by: Paymaun Heidari Co-authored-by: Paymaun --- HISTORY.rst | 8 ++ azext_iot/central/_help.py | 59 +++++++++ azext_iot/central/command_map.py | 19 +++ azext_iot/central/commands_device_group.py | 21 +++ azext_iot/central/commands_role.py | 35 +++++ azext_iot/central/models/__init__.py | 4 + .../central/models/deviceGroupPreview.py | 11 ++ azext_iot/central/models/rolePreview.py | 11 ++ azext_iot/central/params.py | 7 + .../central/providers/preview/__init__.py | 9 ++ .../preview/device_group_provider_preview.py | 48 +++++++ .../preview/role_provider_preview.py | 71 ++++++++++ azext_iot/central/services/__init__.py | 4 +- azext_iot/central/services/device_group.py | 73 +++++++++++ azext_iot/central/services/role.py | 121 ++++++++++++++++++ azext_iot/constants.py | 2 +- .../tests/central/json/device_group.json | 18 +++ azext_iot/tests/central/json/role.json | 14 ++ .../tests/central/test_iot_central_int.py | 22 ++++ .../tests/central/test_iot_central_unit.py | 57 +++++++++ azext_iot/tests/test_constants.py | 2 + 21 files changed, 613 insertions(+), 3 deletions(-) create mode 100644 azext_iot/central/commands_device_group.py create mode 100644 azext_iot/central/commands_role.py create mode 100644 azext_iot/central/models/deviceGroupPreview.py create mode 100644 azext_iot/central/models/rolePreview.py create mode 100644 azext_iot/central/providers/preview/device_group_provider_preview.py create mode 100644 azext_iot/central/providers/preview/role_provider_preview.py create mode 100644 azext_iot/central/services/device_group.py create mode 100644 azext_iot/central/services/role.py create mode 100644 azext_iot/tests/central/json/device_group.json create mode 100644 azext_iot/tests/central/json/role.json diff --git a/HISTORY.rst b/HISTORY.rst index 6a1cb17df..1ae89c7a3 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,14 @@ Release History =============== +0.10.15 ++++++++++++++++ + +**IoT Central updates** + +* Adds support for listing device groups +* Adds support for listing roles and get role by id + 0.10.14 +++++++++++++++ diff --git a/azext_iot/central/_help.py b/azext_iot/central/_help.py index 35c6d18e1..82b8449d6 100644 --- a/azext_iot/central/_help.py +++ b/azext_iot/central/_help.py @@ -36,6 +36,8 @@ def load_central_help(): _load_central_users_help() _load_central_api_token_help() _load_central_device_templates_help() + _load_central_device_groups_help() + _load_central_roles_help() _load_central_monitors_help() _load_central_command_help() _load_central_compute_device_key() @@ -429,6 +431,63 @@ def _load_central_device_templates_help(): """ +def _load_central_device_groups_help(): + helps[ + "iot central device-group" + ] = """ + type: group + short-summary: Manage and configure IoT Central device groups + """ + + helps[ + "iot central device-group list" + ] = """ + type: command + short-summary: Get the list of device groups for an IoT Central application. + + examples: + - name: List device groups in an application + text: > + az iot central device-group list + --app-id {appid} + """ + + +def _load_central_roles_help(): + helps[ + "iot central role" + ] = """ + type: group + short-summary: Manage and configure IoT Central roles + """ + + helps[ + "iot central role list" + ] = """ + type: command + short-summary: Get the list of roles for an IoT Central application. + + examples: + - name: List roles in an application + text: > + az iot central role list + --app-id {appid} + """ + + helps[ + "iot central role show" + ] = """ + type: command + short-summary: Get the details of a role by ID + examples: + - name: Get details of role + text: > + az iot central role show + --app-id {appid} + --role-id {roleId} + """ + + def _load_central_monitors_help(): helps[ diff --git a/azext_iot/central/command_map.py b/azext_iot/central/command_map.py index 0c97daca9..1d8b185eb 100644 --- a/azext_iot/central/command_map.py +++ b/azext_iot/central/command_map.py @@ -17,6 +17,14 @@ operations_tmpl="azext_iot.central.commands_device_template#{}" ) +central_device_groups_ops = CliCommandType( + operations_tmpl="azext_iot.central.commands_device_group#{}" +) + +central_roles_ops = CliCommandType( + operations_tmpl="azext_iot.central.commands_role#{}" +) + central_device_twin_ops = CliCommandType( operations_tmpl="azext_iot.central.commands_device_twin#{}" ) @@ -103,6 +111,17 @@ def load_central_commands(self, _): cmd_group.command("create", "create_device_template") cmd_group.command("delete", "delete_device_template") + with self.command_group( + "iot central device-group", command_type=central_device_groups_ops, is_preview=True + ) as cmd_group: + cmd_group.command("list", "list_device_groups") + + with self.command_group( + "iot central role", command_type=central_roles_ops, is_preview=True + ) as cmd_group: + cmd_group.show_command("show", "get_role") + cmd_group.command("list", "list_roles") + with self.command_group( "iot central device twin", command_type=central_device_twin_ops, ) as cmd_group: diff --git a/azext_iot/central/commands_device_group.py b/azext_iot/central/commands_device_group.py new file mode 100644 index 000000000..f080c964d --- /dev/null +++ b/azext_iot/central/commands_device_group.py @@ -0,0 +1,21 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +# Dev note - think of this as a controller + +from azext_iot.constants import CENTRAL_ENDPOINT +from azext_iot.central.providers.preview import CentralDeviceGroupProviderPreview +from azext_iot.central.models.enum import ApiVersion + + +def list_device_groups( + cmd, + app_id: str, + token=None, + central_dns_suffix=CENTRAL_ENDPOINT, + api_version=ApiVersion.preview.value, +): + provider = CentralDeviceGroupProviderPreview(cmd=cmd, app_id=app_id, token=token) + return provider.list_device_groups(central_dns_suffix=central_dns_suffix) diff --git a/azext_iot/central/commands_role.py b/azext_iot/central/commands_role.py new file mode 100644 index 000000000..c6830ac0b --- /dev/null +++ b/azext_iot/central/commands_role.py @@ -0,0 +1,35 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +# Dev note - think of this as a controller + +from azext_iot.constants import CENTRAL_ENDPOINT +from azext_iot.central.providers.preview import CentralRoleProviderPreview +from azext_iot.central.models.enum import ApiVersion + + +def get_role( + cmd, + app_id: str, + role_id: str, + token=None, + central_dns_suffix=CENTRAL_ENDPOINT, + api_version=ApiVersion.preview.value, +): + provider = CentralRoleProviderPreview(cmd=cmd, app_id=app_id, token=token) + + return provider.get_role(role_id=role_id, central_dns_suffix=central_dns_suffix) + + +def list_roles( + cmd, + app_id: str, + token=None, + central_dns_suffix=CENTRAL_ENDPOINT, + api_version=ApiVersion.preview.value, +): + provider = CentralRoleProviderPreview(cmd=cmd, app_id=app_id, token=token) + + return provider.list_roles(central_dns_suffix=central_dns_suffix) diff --git a/azext_iot/central/models/__init__.py b/azext_iot/central/models/__init__.py index cc1abd64c..475d899bf 100644 --- a/azext_iot/central/models/__init__.py +++ b/azext_iot/central/models/__init__.py @@ -8,12 +8,16 @@ from azext_iot.central.models.devicetwin import DeviceTwin from azext_iot.central.models.templatepreview import TemplatePreview from azext_iot.central.models.templatev1 import TemplateV1 +from azext_iot.central.models.deviceGroupPreview import DeviceGroupPreview +from azext_iot.central.models.rolePreview import RolePreview __all__ = [ "DevicePreview", + "DeviceGroupPreview", "DeviceV1", "DeviceTwin", "TemplatePreview", "TemplateV1", + "RolePreview" ] diff --git a/azext_iot/central/models/deviceGroupPreview.py b/azext_iot/central/models/deviceGroupPreview.py new file mode 100644 index 000000000..395366a25 --- /dev/null +++ b/azext_iot/central/models/deviceGroupPreview.py @@ -0,0 +1,11 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +class DeviceGroupPreview: + def __init__(self, group: dict): + self.display_name = group.get("displayName") + self.id = group.get("id") + pass diff --git a/azext_iot/central/models/rolePreview.py b/azext_iot/central/models/rolePreview.py new file mode 100644 index 000000000..a84396091 --- /dev/null +++ b/azext_iot/central/models/rolePreview.py @@ -0,0 +1,11 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +class RolePreview: + def __init__(self, device: dict): + self.display_name = device.get("displayName") + self.id = device.get("id") + pass diff --git a/azext_iot/central/params.py b/azext_iot/central/params.py index d6f5d7e56..e865d07a8 100644 --- a/azext_iot/central/params.py +++ b/azext_iot/central/params.py @@ -215,3 +215,10 @@ def load_central_arguments(self, _): options_list=["--module-id", "-m"], help="Provide IoT Edge Module ID if the device type is IoT Edge.", ) + + with self.argument_context("iot central role") as context: + context.argument( + "role_id", + options_list=["--role-id", "-r"], + help="Provide a unique identifier for the role" + ) diff --git a/azext_iot/central/providers/preview/__init__.py b/azext_iot/central/providers/preview/__init__.py index f47cdd416..db856a08e 100644 --- a/azext_iot/central/providers/preview/__init__.py +++ b/azext_iot/central/providers/preview/__init__.py @@ -17,10 +17,19 @@ from azext_iot.central.providers.preview.api_token_provider_preview import ( CentralApiTokenProviderPreview, ) +from azext_iot.central.providers.preview.device_group_provider_preview import ( + CentralDeviceGroupProviderPreview +) +from azext_iot.central.providers.preview.role_provider_preview import ( + CentralRoleProviderPreview +) __all__ = [ "CentralDeviceProviderPreview", "CentralDeviceTemplateProviderPreview", "CentralUserProviderPreview", "CentralApiTokenProviderPreview", + "CentralDeviceGroupProviderPreview", + "CentralRoleProviderPreview" + ] diff --git a/azext_iot/central/providers/preview/device_group_provider_preview.py b/azext_iot/central/providers/preview/device_group_provider_preview.py new file mode 100644 index 000000000..5ad9634e6 --- /dev/null +++ b/azext_iot/central/providers/preview/device_group_provider_preview.py @@ -0,0 +1,48 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + + +from typing import List +from azext_iot.central.models.deviceGroupPreview import DeviceGroupPreview +from knack.log import get_logger +from azext_iot.constants import CENTRAL_ENDPOINT +from azext_iot.central import services as central_services +from azext_iot.central.models.enum import ApiVersion + +logger = get_logger(__name__) + + +class CentralDeviceGroupProviderPreview: + def __init__(self, cmd, app_id: str, token=None): + """ + Provider for device groups APIs + + 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 ...') + Useful in scenarios where user doesn't own the app + therefore AAD token won't work, but a SAS token generated by owner will + """ + self._cmd = cmd + self._app_id = app_id + self._token = token + self._device_groups = {} + + def list_device_groups(self, central_dns_suffix=CENTRAL_ENDPOINT) -> List[DeviceGroupPreview]: + device_groups = central_services.device_group.list_device_groups( + cmd=self._cmd, + app_id=self._app_id, + token=self._token, + central_dns_suffix=central_dns_suffix, + api_version=ApiVersion.preview.value, + ) + + # add to cache + self._device_groups.update({device_group.id: device_group for device_group in device_groups}) + + return self._device_groups diff --git a/azext_iot/central/providers/preview/role_provider_preview.py b/azext_iot/central/providers/preview/role_provider_preview.py new file mode 100644 index 000000000..e5229baff --- /dev/null +++ b/azext_iot/central/providers/preview/role_provider_preview.py @@ -0,0 +1,71 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + + +from typing import List +from azext_iot.central.models.rolePreview import RolePreview +from knack.util import CLIError +from knack.log import get_logger +from azext_iot.constants import CENTRAL_ENDPOINT +from azext_iot.central import services as central_services +from azext_iot.central.models.enum import ApiVersion +from azext_iot.central import models as central_models + +logger = get_logger(__name__) + + +class CentralRoleProviderPreview: + def __init__(self, cmd, app_id: str, token=None): + """ + Provider for roles APIs + + 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 ...') + Useful in scenarios where user doesn't own the app + therefore AAD token won't work, but a SAS token generated by owner will + """ + self._cmd = cmd + self._app_id = app_id + self._token = token + self._roles = {} + + def list_roles(self, central_dns_suffix=CENTRAL_ENDPOINT) -> List[RolePreview]: + roles = central_services.role.list_roles( + cmd=self._cmd, + app_id=self._app_id, + token=self._token, + central_dns_suffix=central_dns_suffix, + api_version=ApiVersion.preview.value, + ) + + # add to cache + self._roles.update({role.id: role for role in roles}) + + return self._roles + + def get_role( + self, role_id, central_dns_suffix=CENTRAL_ENDPOINT, + ) -> central_models.RolePreview: + # get or add to cache + role = self._roles.get(role_id) + if not role: + role = central_services.role.get_role( + cmd=self._cmd, + app_id=self._app_id, + role_id=role_id, + token=self._token, + central_dns_suffix=central_dns_suffix, + api_version=ApiVersion.preview.value, + ) + self._roles[role_id] = role + + if not role: + raise CLIError("No role found with id: '{}'.".format(role_id)) + + return role diff --git a/azext_iot/central/services/__init__.py b/azext_iot/central/services/__init__.py index 6463834e2..e8f65dc27 100644 --- a/azext_iot/central/services/__init__.py +++ b/azext_iot/central/services/__init__.py @@ -4,7 +4,7 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from azext_iot.central.services import device, device_template, user, api_token +from azext_iot.central.services import device, device_template, user, api_token, device_group, role -__all__ = ["device", "device_template", "user", "api_token"] +__all__ = ["device", "device_template", "user", "api_token", "device_group", "role"] diff --git a/azext_iot/central/services/device_group.py b/azext_iot/central/services/device_group.py new file mode 100644 index 000000000..bedafaf74 --- /dev/null +++ b/azext_iot/central/services/device_group.py @@ -0,0 +1,73 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +# This is largely derived from https://docs.microsoft.com/en-us/rest/api/iotcentral/deviceGroups + +from typing import List +import requests + +from knack.util import CLIError +from knack.log import get_logger + +from azext_iot.constants import CENTRAL_ENDPOINT +from azext_iot.central.services import _utility +from azext_iot.central import models as central_models +from azext_iot.central.models.enum import ApiVersion + +logger = get_logger(__name__) + +BASE_PATH = "api/deviceGroups" + + +def list_device_groups( + cmd, + app_id: str, + token: str, + max_pages=1, + central_dns_suffix=CENTRAL_ENDPOINT, + api_version=ApiVersion.preview.value, +) -> List[central_models.DeviceGroupPreview]: + """ + Get a list of all device groups in IoTC app + + 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: + list of device groups + """ + + device_groups = [] + + 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 + + pages_processed = 0 + while (pages_processed <= max_pages) and url: + 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)) + + device_groups = device_groups + [ + central_models.DeviceGroupPreview(device_group) for device_group in result["value"] + ] + + try: + url = result.get("nextLink", params=query_parameters) + except: + pass + pages_processed = pages_processed + 1 + + return device_groups diff --git a/azext_iot/central/services/role.py b/azext_iot/central/services/role.py new file mode 100644 index 000000000..e33f07f8f --- /dev/null +++ b/azext_iot/central/services/role.py @@ -0,0 +1,121 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +# This is largely derived from https://docs.microsoft.com/en-us/rest/api/iotcentral/roles + +from typing import List +import requests + +from knack.util import CLIError +from knack.log import get_logger + +from azext_iot.constants import CENTRAL_ENDPOINT +from azext_iot.central.services import _utility +from azext_iot.central import models as central_models +from azext_iot.central.models.enum import ApiVersion +from azure.cli.core.util import should_disable_connection_verify + + +logger = get_logger(__name__) + +BASE_PATH = "api/roles" + + +def get_role( + cmd, + app_id: str, + role_id: str, + token: str, + central_dns_suffix=CENTRAL_ENDPOINT, + api_version=ApiVersion.preview.value, +) -> central_models.RolePreview: + """ + Get role info given a role id + + Args: + cmd: command passed into az + role_id: unique case-sensitive role id, + app_id: name of app (used for forming request URL) + token: (OPTIONAL) authorization token to fetch role details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + central_dns_suffix: {centralDnsSuffixInPath} as found in docs + + Returns: + role: dict + """ + + url = "https://{}.{}/{}/{}".format(app_id, central_dns_suffix, BASE_PATH, role_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, + verify=not should_disable_connection_verify(), + ) + result = _utility.try_extract_result(response) + + return central_models.RolePreview(result) + + +def list_roles( + cmd, + app_id: str, + token: str, + max_pages=1, + central_dns_suffix=CENTRAL_ENDPOINT, + api_version=ApiVersion.preview.value, +) -> List[central_models.RolePreview]: + """ + Get a list of all roles in IoTC app + + Args: + cmd: command passed into az + app_id: name of app (used for forming request URL) + token: (OPTIONAL) authorization token to fetch role details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + central_dns_suffix: {centralDnsSuffixInPath} as found in docs + + Returns: + list of roles + """ + + roles = [] + + 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 + + pages_processed = 0 + while (pages_processed <= max_pages) and url: + response = requests.get( + url, + headers=headers, + params=query_parameters, + verify=not should_disable_connection_verify() + ) + result = _utility.try_extract_result(response) + + if "value" not in result: + raise CLIError("Value is not present in body: {}".format(result)) + + roles = roles + [ + central_models.RolePreview(role) for role in result["value"] + ] + + try: + url = result.get("nextLink", params=query_parameters) + except: + pass + pages_processed = pages_processed + 1 + + return roles diff --git a/azext_iot/constants.py b/azext_iot/constants.py index b3e2dcf63..f00235ede 100644 --- a/azext_iot/constants.py +++ b/azext_iot/constants.py @@ -7,7 +7,7 @@ import os -VERSION = "0.10.14" +VERSION = "0.10.15" EXTENSION_NAME = "azure-iot" EXTENSION_ROOT = os.path.dirname(os.path.abspath(__file__)) EXTENSION_CONFIG_ROOT_KEY = "iotext" diff --git a/azext_iot/tests/central/json/device_group.json b/azext_iot/tests/central/json/device_group.json new file mode 100644 index 000000000..bcfbff8d7 --- /dev/null +++ b/azext_iot/tests/central/json/device_group.json @@ -0,0 +1,18 @@ +[ + { + "id": "475cad48-b7ff-4a09-b51e-1a9021385453", + "displayName": "DeviceGroupEntry1" + }, + { + "id": "c2d5ae1d-2cb7-4f58-bf44-5e816aba0a0e", + "displayName": "DeviceGroupEntry2" + }, + { + "id": "241ad72b-32aa-4216-aabe-91b240582c8d", + "displayName": "DeviceGroupEntry3" + }, + { + "id": "2871abbc-32aa-4856-aadc-91b240582c8d", + "displayName": "DeviceGroupEntry4" + } +] \ No newline at end of file diff --git a/azext_iot/tests/central/json/role.json b/azext_iot/tests/central/json/role.json new file mode 100644 index 000000000..4664ab73a --- /dev/null +++ b/azext_iot/tests/central/json/role.json @@ -0,0 +1,14 @@ +[ + { + "id": "ca310b8d-2f4a-44e0-a36e-957c202cd8d4", + "displayName": "Administrator" + }, + { + "id": "344138e9-8de4-4497-8c54-5237e96d6aaf", + "displayName": "Builder" + }, + { + "id": "ae2c9854-393b-4f97-8c42-479d70ce626e", + "displayName": "Operator" + } +] \ No newline at end of file diff --git a/azext_iot/tests/central/test_iot_central_int.py b/azext_iot/tests/central/test_iot_central_int.py index 0196cd99d..a332ee726 100644 --- a/azext_iot/tests/central/test_iot_central_int.py +++ b/azext_iot/tests/central/test_iot_central_int.py @@ -343,6 +343,16 @@ def test_central_device_template_methods_CRD(self): self._delete_device_template(template_id) + def test_central_device_groups_list(self): + result = self._list_device_groups() + # assert object is empty or populated but not null + assert result is not None and (result == {} or bool(result) is True) + + def test_central_roles_list(self): + result = self._list_roles() + # assert object is empty or populated but not null + assert result is not None and (result == {} or bool(result) is True) + def test_central_device_registration_info_registered(self): (template_id, _) = self._create_device_template() (device_id, device_name) = self._create_device( @@ -726,6 +736,18 @@ def _delete_device_template(self, template_id): except: time.sleep(10) + def _list_device_groups(self): + return self.cmd( + "iot central device-group list --app-id {}".format( + APP_ID) + ).get_output_in_json() + + def _list_roles(self): + return self.cmd( + "iot central role list --app-id {}".format( + APP_ID) + ).get_output_in_json() + def _get_credentials(self, device_id): return self.cmd( "iot central device show-credentials --app-id {} -d {}".format( diff --git a/azext_iot/tests/central/test_iot_central_unit.py b/azext_iot/tests/central/test_iot_central_unit.py index a4a4253a5..717a343d1 100644 --- a/azext_iot/tests/central/test_iot_central_unit.py +++ b/azext_iot/tests/central/test_iot_central_unit.py @@ -19,6 +19,10 @@ CentralDeviceProviderV1, CentralDeviceTemplateProviderV1, ) +from azext_iot.central.providers.preview import ( + CentralDeviceGroupProviderPreview, + CentralRoleProviderPreview +) from azext_iot.central.models.devicetwin import DeviceTwin from azext_iot.central import models as central_models from azext_iot.monitor.property import PropertyMonitor @@ -201,6 +205,59 @@ def test_should_return_device_template( assert template == self._device_template +class TestCentralDeviceGroupProvider: + _device_groups = [ + central_models.DeviceGroupPreview(group) for group in load_json(FileNames.central_device_group_file)] + + @mock.patch("azext_iot.central.services.device_group") + def test_should_return_device_groups(self, mock_device_group_svc): + + # setup + provider = CentralDeviceGroupProviderPreview(cmd=None, app_id=app_id) + mock_device_group_svc.list_device_groups.return_value = self._device_groups + + # act + device_groups = provider.list_device_groups() + # verify + # call counts should be at most 1 since the provider has a cache + assert mock_device_group_svc.list_device_groups.call_count == 1 + assert set(device_groups) == set(map(lambda x: x.id, self._device_groups)) + + +class TestCentralRoleProvider: + _roles = [ + central_models.RolePreview(role) for role in load_json(FileNames.central_role_file)] + + @mock.patch("azext_iot.central.services.role") + def test_should_return_roles(self, mock_role_svc): + + # setup + provider = CentralRoleProviderPreview(cmd=None, app_id=app_id) + mock_role_svc.list_roles.return_value = self._roles + + # act + roles = provider.list_roles() + # verify + # call counts should be at most 1 since the provider has a cache + assert mock_role_svc.list_roles.call_count == 1 + assert set(roles) == set(map(lambda x: x.id, self._roles)) + + @mock.patch("azext_iot.central.services.role") + def test_should_return_role( + self, mock_role_svc + ): + # setup + provider = CentralRoleProviderPreview(cmd=None, app_id=app_id) + mock_role_svc.get_role.return_value = self._roles[0] + + # act + role = provider.get_role(self._roles[0].id) + # verify + # call counts should be at most 1 since the provider has a cache + assert mock_role_svc.get_role.call_count == 1 + assert role.id == self._roles[0].id + + class TestCentralPropertyMonitor: _device_twin = load_json(FileNames.central_device_twin_file) _duplicate_property_template = load_json( diff --git a/azext_iot/tests/test_constants.py b/azext_iot/tests/test_constants.py index d39a00250..d824f1e86 100644 --- a/azext_iot/tests/test_constants.py +++ b/azext_iot/tests/test_constants.py @@ -11,6 +11,8 @@ class FileNames: "central/json/deeply_nested_template.json" ) central_device_file = "central/json/device.json" + central_device_group_file = "central/json/device_group.json" + central_role_file = "central/json/role.json" central_device_twin_file = "central/json/device_twin.json" central_property_validation_template_file = ( "central/json/property_validation_template.json"