From 4a46d7424b4c5af41a585f225ef6d731b54fc1bb Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Sat, 26 Mar 2022 14:34:28 +0100 Subject: [PATCH 1/9] Add the short url feature --- src/grafana_api/organisation.py | 22 ++++++++++ src/grafana_api/short_url.py | 53 +++++++++++++++++++++++++ tests/integrationtest/test_short_url.py | 18 +++++++++ tests/unittests/test_alerting.py | 4 +- tests/unittests/test_short_url.py | 42 ++++++++++++++++++++ 5 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 src/grafana_api/organisation.py create mode 100644 src/grafana_api/short_url.py create mode 100644 tests/integrationtest/test_short_url.py create mode 100644 tests/unittests/test_short_url.py diff --git a/src/grafana_api/organisation.py b/src/grafana_api/organisation.py new file mode 100644 index 0000000..9cd1a26 --- /dev/null +++ b/src/grafana_api/organisation.py @@ -0,0 +1,22 @@ +import json +import logging + +from .model import APIModel, APIEndpoints, RequestsMethods +from .api import Api + + +class Organisation: + """The class includes all necessary methods to access the Grafana organisation API endpoint + + Args: + grafana_api_model (APIModel): Inject a Grafana API model object that includes all necessary values and information + + Attributes: + grafana_api_model (APIModel): This is where we store the grafana_api_model + """ + + def __init__(self, grafana_api_model: APIModel): + self.grafana_api_model = grafana_api_model + + +# https://grafana.com/docs/grafana/latest/http_api/org/ diff --git a/src/grafana_api/short_url.py b/src/grafana_api/short_url.py new file mode 100644 index 0000000..ca4e454 --- /dev/null +++ b/src/grafana_api/short_url.py @@ -0,0 +1,53 @@ +import json +import logging + +from .model import APIModel, APIEndpoints, RequestsMethods +from .api import Api + + +class ShortUrl: + """The class includes all necessary methods to access the Grafana short url API endpoint + + Args: + grafana_api_model (APIModel): Inject a Grafana API model object that includes all necessary values and information + + Attributes: + grafana_api_model (APIModel): This is where we store the grafana_api_model + """ + + def __init__(self, grafana_api_model: APIModel): + self.grafana_api_model = grafana_api_model + + def create_short_url(self, path: str): + """The method includes a functionality to create a short link for a specific dashboard + + Args: + path (str): Specify the corresponding dashboard path + + Raises: + ValueError: Missed specifying a necessary value + Exception: Unspecified error by executing the API call + + Returns: + api_call (dict): Returns the uid and the url of the newly generated link + """ + + if len(path) != 0: + api_call: dict = ( + Api(self.grafana_api_model) + .call_the_api( + APIEndpoints.ALERT_NOTIFICATIONS.value, + RequestsMethods.POST, + json.dumps(dict({"path": path})), + ) + .json() + ) + + if api_call == dict() or api_call.get("url") is None: + logging.error(f"Check the error: {api_call}.") + raise Exception + else: + return api_call + else: + logging.error("There is no path defined.") + raise ValueError diff --git a/tests/integrationtest/test_short_url.py b/tests/integrationtest/test_short_url.py new file mode 100644 index 0000000..ae30765 --- /dev/null +++ b/tests/integrationtest/test_short_url.py @@ -0,0 +1,18 @@ +import os +from unittest import TestCase + +from src.grafana_api.model import APIModel +from src.grafana_api.short_url import ShortUrl + + +class ShortUrlTest(TestCase): + model: APIModel = APIModel( + host=os.environ["GRAFANA_HOST"], + token=os.environ["GRAFANA_TOKEN"], + ) + short_url: ShortUrl = ShortUrl(model) + + def test_create_short_url(self): + self.assertIsNotNone( + self.short_url.create_short_url("d/test1/test-1?orgId=4").get("url") + ) diff --git a/tests/unittests/test_alerting.py b/tests/unittests/test_alerting.py index d4805de..a22fa8f 100644 --- a/tests/unittests/test_alerting.py +++ b/tests/unittests/test_alerting.py @@ -787,7 +787,9 @@ def test_test_rule(self, call_the_api_mock): call_the_api_mock.return_value = mock - self.assertEqual(dict({"test": "test"}), alerting.test_rule([datasource_rule_query])) + self.assertEqual( + dict({"test": "test"}), alerting.test_rule([datasource_rule_query]) + ) def test_test_rule_no_data_query(self): model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) diff --git a/tests/unittests/test_short_url.py b/tests/unittests/test_short_url.py new file mode 100644 index 0000000..75e6787 --- /dev/null +++ b/tests/unittests/test_short_url.py @@ -0,0 +1,42 @@ +from unittest import TestCase +from unittest.mock import MagicMock, Mock, patch + +from src.grafana_api.model import APIModel +from src.grafana_api.short_url import ShortUrl + + +class ShortUrlTestCase(TestCase): + @patch("src.grafana_api.api.Api.call_the_api") + def test_create_short_url(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) + short_url: ShortUrl = ShortUrl(grafana_api_model=model) + + mock: Mock = Mock() + mock.json = Mock(return_value=dict({"url": "test"})) + + call_the_api_mock.return_value = mock + + self.assertEqual("test", short_url.create_short_url(path="Test").get("url")) + + @patch("src.grafana_api.api.Api.call_the_api") + def test_create_short_url_invalid_path(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) + short_url: ShortUrl = ShortUrl(grafana_api_model=model) + + mock: Mock = Mock() + mock.json = Mock(return_value=dict()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(ValueError): + short_url.create_short_url(path="") + + @patch("src.grafana_api.api.Api.call_the_api") + def test_create_short_url_invalid_output(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) + short_url: ShortUrl = ShortUrl(grafana_api_model=model) + + call_the_api_mock.side_effect = Exception + + with self.assertRaises(Exception): + short_url.create_short_url(path=MagicMock()) From 8918fc725657b69265136837a2c56fab9fb9f519 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Sat, 26 Mar 2022 14:48:01 +0100 Subject: [PATCH 2/9] Fix the integrationtest --- src/grafana_api/model.py | 2 ++ src/grafana_api/short_url.py | 2 +- tests/integrationtest/test_short_url.py | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/grafana_api/model.py b/src/grafana_api/model.py index 39d9f1d..441ce97 100644 --- a/src/grafana_api/model.py +++ b/src/grafana_api/model.py @@ -20,6 +20,8 @@ class APIEndpoints(Enum): ALERTS_NGALERT = "/api/v1/ngalert" DATASOURCES = "/api/datasources" DATASOURCE_QUERY = "/api/tsdb/query" + SHORT_URLS = "/api/short-urls" + ORGANISATION = "/api/org" class RequestsMethods(Enum): diff --git a/src/grafana_api/short_url.py b/src/grafana_api/short_url.py index ca4e454..0b62355 100644 --- a/src/grafana_api/short_url.py +++ b/src/grafana_api/short_url.py @@ -36,7 +36,7 @@ def create_short_url(self, path: str): api_call: dict = ( Api(self.grafana_api_model) .call_the_api( - APIEndpoints.ALERT_NOTIFICATIONS.value, + APIEndpoints.SHORT_URLS.value, RequestsMethods.POST, json.dumps(dict({"path": path})), ) diff --git a/tests/integrationtest/test_short_url.py b/tests/integrationtest/test_short_url.py index ae30765..0996ae7 100644 --- a/tests/integrationtest/test_short_url.py +++ b/tests/integrationtest/test_short_url.py @@ -14,5 +14,5 @@ class ShortUrlTest(TestCase): def test_create_short_url(self): self.assertIsNotNone( - self.short_url.create_short_url("d/test1/test-1?orgId=4").get("url") + self.short_url.create_short_url("d/test1/test-1?orgId=4&from=now-1h&to=now") ) From 1a8291408e9d2ab302fcc3ca7e4d68a46559f097 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Wed, 6 Apr 2022 08:45:38 +0200 Subject: [PATCH 3/9] Add basic organisation support --- src/grafana_api/model.py | 16 +++ src/grafana_api/organisation.py | 221 +++++++++++++++++++++++++++++++- 2 files changed, 235 insertions(+), 2 deletions(-) diff --git a/src/grafana_api/model.py b/src/grafana_api/model.py index 441ce97..9a287d2 100644 --- a/src/grafana_api/model.py +++ b/src/grafana_api/model.py @@ -22,6 +22,7 @@ class APIEndpoints(Enum): DATASOURCE_QUERY = "/api/tsdb/query" SHORT_URLS = "/api/short-urls" ORGANISATION = "/api/org" + ORGANISATIONS = "/api/orgs" class RequestsMethods(Enum): @@ -30,6 +31,7 @@ class RequestsMethods(Enum): GET = "GET" PUT = "PUT" POST = "POST" + PATCH = "PATCH" DELETE = "DELETE" @@ -45,6 +47,20 @@ class APIModel(NamedTuple): token: str +class APIBasicModel(NamedTuple): + """The class includes all necessary variables to establish a basic connection to the Grafana API endpoints + + Args: + host (str): Specify the host of the Grafana system + username (str): Specify the username of the Grafana system + password (str): Specify the password of the Grafana system + """ + + host: str + username: str + password: str + + class DatasourceQuery(NamedTuple): """The class includes all necessary variables to specify a query for the datasource search endpoint diff --git a/src/grafana_api/organisation.py b/src/grafana_api/organisation.py index 9cd1a26..4e6679d 100644 --- a/src/grafana_api/organisation.py +++ b/src/grafana_api/organisation.py @@ -1,7 +1,7 @@ import json import logging -from .model import APIModel, APIEndpoints, RequestsMethods +from .model import APIModel, APIBasicModel, APIEndpoints, RequestsMethods from .api import Api @@ -18,5 +18,222 @@ class Organisation: def __init__(self, grafana_api_model: APIModel): self.grafana_api_model = grafana_api_model + def get_current_organization(self) -> dict: + """The method includes a functionality to get the current organization. -# https://grafana.com/docs/grafana/latest/http_api/org/ + Required Permissions: + Action: orgs:read + Scope: N/A + + Raises: + Exception: Unspecified error by executing the API call + + Returns: + api_call (dict): Returns the current organization + """ + + api_call: dict = ( + Api(self.grafana_api_model).call_the_api(f"{APIEndpoints.ORGANISATION.value}/").json() + ) + + if api_call == dict() or api_call.get("id") is None: + logging.error(f"Check the error: {api_call}.") + raise Exception + else: + return api_call + + def get_all_users_by_the_current_organization(self) -> list: + """The method includes a functionality to get all users from the current organization. + + Required Permissions: + Action: org.users:read + Scope: users:* + + Raises: + Exception: Unspecified error by executing the API call + + Returns: + api_call (list): Returns the users of the current organization + """ + + api_call: list = ( + Api(self.grafana_api_model).call_the_api(f"{APIEndpoints.ORGANISATION.value}/users").json() + ) + + if api_call == list() or api_call[0].get("orgId") is None: + logging.error(f"Check the error: {api_call}.") + raise Exception + else: + return api_call + + def get_all_users_by_the_current_organization_lookup(self) -> list: + """The method includes a functionality to get the lookup information of all users from the current organization. + + Required Permissions: + Action: org.users:read + Scope: users:* + + Raises: + Exception: Unspecified error by executing the API call + + Returns: + api_call (list): Returns the users of the current organization + """ + + api_call: list = ( + Api(self.grafana_api_model).call_the_api(f"{APIEndpoints.ORGANISATION.value}/users/lookup").json() + ) + + if api_call == list() or api_call[0].get("userId") is None: + logging.error(f"Check the error: {api_call}.") + raise Exception + else: + return api_call + + def update_organization_user_role_by_user_id(self, user_id: str, role: dict): + """The method includes a functionality to update the current organization user by the user id. + + Args: + user_id (str): Specify the id of the user + role (dict): Specify the role of the user as dict + + Required Permissions: + Action: org.users.role:update + Scope: users:* + + Raises: + ValueError: Missed specifying a necessary value + Exception: Unspecified error by executing the API call + + Returns: + None + """ + + if user_id != 0 and role != dict(): + api_call: dict = ( + Api(self.grafana_api_model).call_the_api(f"{APIEndpoints.ORGANISATION.value}/users/{user_id}", + RequestsMethods.PATCH, + json.dumps(role)).json() + ) + + if api_call.get("message") is not "Organization user updated": + logging.error(f"Check the error: {api_call}.") + raise Exception + else: + logging.info("You successfully updated the organization user.") + else: + logging.error("There is no user_id or dict defined.") + raise ValueError + + def delete_organization_user_by_user_id(self, user_id: str): + """The method includes a functionality to delete the current organization user by the user id. + + Args: + user_id (str): Specify the id of the user + + Required Permissions: + Action: org.users:remove + Scope: users:* + + Raises: + ValueError: Missed specifying a necessary value + Exception: Unspecified error by executing the API call + + Returns: + None + """ + + if user_id != 0: + api_call: dict = ( + Api(self.grafana_api_model).call_the_api(f"{APIEndpoints.ORGANISATION.value}/users/{user_id}", + RequestsMethods.DELETE).json() + ) + + if api_call.get("message") is not "User removed from organization": + logging.error(f"Check the error: {api_call}.") + raise Exception + else: + logging.info("You successfully removed the organization user.") + else: + logging.error("There is no user_id defined.") + raise ValueError + + def update_current_organization(self, role_name: dict): + """The method includes a functionality to update the current organization. + + Args: + role_name (dict): Specify the role name as dict + + Required Permissions: + Action: orgs:write + Scope: N/A + + Raises + ValueError: Missed specifying a necessary value + Exception: Unspecified error by executing the API call + + Returns: + None + """ + + if role_name != dict(): + api_call: dict = ( + Api(self.grafana_api_model).call_the_api(f"{APIEndpoints.ORGANISATION.value}", + RequestsMethods.PUT).json() + ) + + if api_call.get("message") is not "Organization updated": + logging.error(f"Check the error: {api_call}.") + raise Exception + else: + logging.info("You successfully updated the organization.") + else: + logging.error("There is no role_name defined.") + raise ValueError + + def add_new_user_to_current_organization(self, user_role: dict) -> int: + """The method includes a functionality to add a new user to the current organization. + + Args: + user_role (dict): Specify the user role attributes as dict + + Required Permissions: + Action: org.users:add + Scope: users:* + + Raises: + ValueError: Missed specifying a necessary value + Exception: Unspecified error by executing the API call + + Returns: + user_id (int): Returns the id of the created user + """ + + if user_role != dict(): + api_call: dict = ( + Api(self.grafana_api_model).call_the_api(f"{APIEndpoints.ORGANISATION.value}/users", + RequestsMethods.POST).json() + ) + + if api_call.get("message") is not "User added to organization": + logging.error(f"Check the error: {api_call}.") + raise Exception + else: + return api_call.get("userId") + else: + logging.error("There is no user_role defined.") + raise ValueError + + +class OrganisationAdmin: + """The class includes all necessary methods to access the Grafana organisation admin API endpoint + + Args: + grafana_api_model (APIBasicModel): Inject a Grafana API basic model object that includes all necessary values and information + + Attributes: + grafana_api_model (APIBasicModel): This is where we store the basic grafana_api_model + """ + + def __init__(self, grafana_api_model: APIBasicModel): + self.grafana_api_model = grafana_api_model From 3827afb09efcc3438bc16ccb47c45afecb2a7ae6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 6 Apr 2022 06:48:56 +0000 Subject: [PATCH 4/9] Add coverage badge and documentation --- docs/content/grafana_api/model.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/content/grafana_api/model.md b/docs/content/grafana_api/model.md index 3b81c75..618bd49 100644 --- a/docs/content/grafana_api/model.md +++ b/docs/content/grafana_api/model.md @@ -4,6 +4,7 @@ * [APIEndpoints](#grafana_api.model.APIEndpoints) * [RequestsMethods](#grafana_api.model.RequestsMethods) * [APIModel](#grafana_api.model.APIModel) + * [APIBasicModel](#grafana_api.model.APIBasicModel) * [DatasourceQuery](#grafana_api.model.DatasourceQuery) * [DatasourceRuleQuery](#grafana_api.model.DatasourceRuleQuery) * [Alert](#grafana_api.model.Alert) @@ -51,6 +52,22 @@ The class includes all necessary variables to establish a connection to the Graf - `host` _str_ - Specify the host of the Grafana system - `token` _str_ - Specify the access token of the Grafana system + + +## APIBasicModel Objects + +```python +class APIBasicModel(NamedTuple) +``` + +The class includes all necessary variables to establish a basic connection to the Grafana API endpoints + +**Arguments**: + +- `host` _str_ - Specify the host of the Grafana system +- `username` _str_ - Specify the username of the Grafana system +- `password` _str_ - Specify the password of the Grafana system + ## DatasourceQuery Objects From 5bc1b1d8e8496665953e8a45f767fe7a6928bc22 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Fri, 8 Apr 2022 08:49:19 +0200 Subject: [PATCH 5/9] Add organization feature and unittests --- src/grafana_api/api.py | 27 +- src/grafana_api/model.py | 16 +- src/grafana_api/organisation.py | 453 +++++++++++++++++-- tests/integrationtest/test_organisation.py | 0 tests/unittests/test_alerting.py | 4 +- tests/unittests/test_api.py | 119 ++--- tests/unittests/test_model.py | 9 + tests/unittests/test_organisation.py | 500 +++++++++++++++++++++ tests/unittests/test_search.py | 2 +- tests/unittests/test_short_url.py | 7 +- 10 files changed, 1022 insertions(+), 115 deletions(-) create mode 100644 tests/integrationtest/test_organisation.py create mode 100644 tests/unittests/test_organisation.py diff --git a/src/grafana_api/api.py b/src/grafana_api/api.py index a948207..6f574f4 100644 --- a/src/grafana_api/api.py +++ b/src/grafana_api/api.py @@ -38,11 +38,22 @@ def call_the_api( """ api_url: str = f"{self.grafana_api_model.host}{api_call}" + headers: dict = dict( + {"Authorization": f"Bearer {self.grafana_api_model.token}"} + ) + + if ( + self.grafana_api_model.username is not None + and self.grafana_api_model.password is not None + ): + url: str = ( + f"{self.grafana_api_model.username}:{self.grafana_api_model.password}@" + ) + api_url = api_url.replace("https://", f"https://{url}") + api_url = api_url.replace("http://", f"http://{url}") + else: + headers["Content-Type"] = "application/json" - headers: dict = { - "Authorization": f"Bearer {self.grafana_api_model.token}", - "Content-Type": "application/json", - } try: if method.value == RequestsMethods.GET.value: return Api.__check_the_api_call_response( @@ -64,6 +75,14 @@ def call_the_api( else: logging.error("Please define the json_complete.") raise Exception + elif method.value == RequestsMethods.PATCH.value: + if json_complete is not None: + return Api.__check_the_api_call_response( + requests.patch(api_url, data=json_complete, headers=headers) + ) + else: + logging.error("Please define the json_complete.") + raise Exception elif method.value == RequestsMethods.DELETE.value: return Api.__check_the_api_call_response( requests.delete(api_url, headers=headers) diff --git a/src/grafana_api/model.py b/src/grafana_api/model.py index 9a287d2..2ea04c8 100644 --- a/src/grafana_api/model.py +++ b/src/grafana_api/model.py @@ -41,24 +41,14 @@ class APIModel(NamedTuple): Args: host (str): Specify the host of the Grafana system token (str): Specify the access token of the Grafana system - """ - - host: str - token: str - - -class APIBasicModel(NamedTuple): - """The class includes all necessary variables to establish a basic connection to the Grafana API endpoints - - Args: - host (str): Specify the host of the Grafana system username (str): Specify the username of the Grafana system password (str): Specify the password of the Grafana system """ host: str - username: str - password: str + token: str = None + username: str = None + password: str = None class DatasourceQuery(NamedTuple): diff --git a/src/grafana_api/organisation.py b/src/grafana_api/organisation.py index 4e6679d..25b97b3 100644 --- a/src/grafana_api/organisation.py +++ b/src/grafana_api/organisation.py @@ -1,7 +1,7 @@ import json import logging -from .model import APIModel, APIBasicModel, APIEndpoints, RequestsMethods +from .model import APIModel, APIEndpoints, RequestsMethods from .api import Api @@ -33,7 +33,9 @@ def get_current_organization(self) -> dict: """ api_call: dict = ( - Api(self.grafana_api_model).call_the_api(f"{APIEndpoints.ORGANISATION.value}/").json() + Api(self.grafana_api_model) + .call_the_api(APIEndpoints.ORGANISATION.value) + .json() ) if api_call == dict() or api_call.get("id") is None: @@ -57,7 +59,9 @@ def get_all_users_by_the_current_organization(self) -> list: """ api_call: list = ( - Api(self.grafana_api_model).call_the_api(f"{APIEndpoints.ORGANISATION.value}/users").json() + Api(self.grafana_api_model) + .call_the_api(f"{APIEndpoints.ORGANISATION.value}/users") + .json() ) if api_call == list() or api_call[0].get("orgId") is None: @@ -81,7 +85,9 @@ def get_all_users_by_the_current_organization_lookup(self) -> list: """ api_call: list = ( - Api(self.grafana_api_model).call_the_api(f"{APIEndpoints.ORGANISATION.value}/users/lookup").json() + Api(self.grafana_api_model) + .call_the_api(f"{APIEndpoints.ORGANISATION.value}/users/lookup") + .json() ) if api_call == list() or api_call[0].get("userId") is None: @@ -90,12 +96,12 @@ def get_all_users_by_the_current_organization_lookup(self) -> list: else: return api_call - def update_organization_user_role_by_user_id(self, user_id: str, role: dict): + def update_organization_user_role_by_user_id(self, user_id: int, role: str): """The method includes a functionality to update the current organization user by the user id. Args: - user_id (str): Specify the id of the user - role (dict): Specify the role of the user as dict + user_id (int): Specify the id of the user + role (str): Specify the role of the user Required Permissions: Action: org.users.role:update @@ -109,14 +115,18 @@ def update_organization_user_role_by_user_id(self, user_id: str, role: dict): None """ - if user_id != 0 and role != dict(): + if user_id != 0 and len(role) != 0: api_call: dict = ( - Api(self.grafana_api_model).call_the_api(f"{APIEndpoints.ORGANISATION.value}/users/{user_id}", - RequestsMethods.PATCH, - json.dumps(role)).json() + Api(self.grafana_api_model) + .call_the_api( + f"{APIEndpoints.ORGANISATION.value}/users/{user_id}", + RequestsMethods.PATCH, + json.dumps(dict({"role": role})), + ) + .json() ) - if api_call.get("message") is not "Organization user updated": + if api_call.get("message") != "Organization user updated": logging.error(f"Check the error: {api_call}.") raise Exception else: @@ -125,11 +135,11 @@ def update_organization_user_role_by_user_id(self, user_id: str, role: dict): logging.error("There is no user_id or dict defined.") raise ValueError - def delete_organization_user_by_user_id(self, user_id: str): + def delete_organization_user_by_user_id(self, user_id: int): """The method includes a functionality to delete the current organization user by the user id. Args: - user_id (str): Specify the id of the user + user_id (int): Specify the id of the user Required Permissions: Action: org.users:remove @@ -145,11 +155,15 @@ def delete_organization_user_by_user_id(self, user_id: str): if user_id != 0: api_call: dict = ( - Api(self.grafana_api_model).call_the_api(f"{APIEndpoints.ORGANISATION.value}/users/{user_id}", - RequestsMethods.DELETE).json() + Api(self.grafana_api_model) + .call_the_api( + f"{APIEndpoints.ORGANISATION.value}/users/{user_id}", + RequestsMethods.DELETE, + ) + .json() ) - if api_call.get("message") is not "User removed from organization": + if api_call.get("message") != "User removed from organization": logging.error(f"Check the error: {api_call}.") raise Exception else: @@ -158,11 +172,11 @@ def delete_organization_user_by_user_id(self, user_id: str): logging.error("There is no user_id defined.") raise ValueError - def update_current_organization(self, role_name: dict): + def update_current_organization(self, name: str): """The method includes a functionality to update the current organization. Args: - role_name (dict): Specify the role name as dict + name (str): Specify the new name of the current organization Required Permissions: Action: orgs:write @@ -176,13 +190,14 @@ def update_current_organization(self, role_name: dict): None """ - if role_name != dict(): + if len(name) != 0: api_call: dict = ( - Api(self.grafana_api_model).call_the_api(f"{APIEndpoints.ORGANISATION.value}", - RequestsMethods.PUT).json() + Api(self.grafana_api_model) + .call_the_api(APIEndpoints.ORGANISATION.value, RequestsMethods.PUT, json.dumps(dict({"name": name}))) + .json() ) - if api_call.get("message") is not "Organization updated": + if api_call.get("message") != "Organization updated": logging.error(f"Check the error: {api_call}.") raise Exception else: @@ -191,11 +206,12 @@ def update_current_organization(self, role_name: dict): logging.error("There is no role_name defined.") raise ValueError - def add_new_user_to_current_organization(self, user_role: dict) -> int: + def add_new_user_to_current_organization(self, login_or_email: str, role: str) -> int: """The method includes a functionality to add a new user to the current organization. Args: - user_role (dict): Specify the user role attributes as dict + login_or_email (str): Specify the added user + role (str): Specify the added role for the user Required Permissions: Action: org.users:add @@ -209,13 +225,17 @@ def add_new_user_to_current_organization(self, user_role: dict) -> int: user_id (int): Returns the id of the created user """ - if user_role != dict(): + if len(login_or_email) != 0 and len(role) != 0: api_call: dict = ( - Api(self.grafana_api_model).call_the_api(f"{APIEndpoints.ORGANISATION.value}/users", - RequestsMethods.POST).json() + Api(self.grafana_api_model) + .call_the_api( + f"{APIEndpoints.ORGANISATION.value}/users", RequestsMethods.POST, + json.dumps(dict({"loginOrEmail": login_or_email, "role": role})) + ) + .json() ) - if api_call.get("message") is not "User added to organization": + if api_call.get("message") != "User added to organization": logging.error(f"Check the error: {api_call}.") raise Exception else: @@ -226,14 +246,377 @@ def add_new_user_to_current_organization(self, user_role: dict) -> int: class OrganisationAdmin: - """The class includes all necessary methods to access the Grafana organisation admin API endpoint + """The class includes all necessary methods to access the Grafana organisation Admin API endpoint. Be aware that all functionalities inside the class only working with basic authentication (username and password). + + Args: + grafana_api_model (APIModel): Inject a Grafana API model object that includes all necessary values and information + + Attributes: + grafana_api_model (APIModel): This is where we store the grafana_api_model + """ + + def __init__(self, grafana_api_model: APIModel): + self.grafana_api_model = grafana_api_model + + def get_organization_by_id(self, org_id: int) -> dict: + """The method includes a functionality to get an organization by the id. Args: - grafana_api_model (APIBasicModel): Inject a Grafana API basic model object that includes all necessary values and information + org_id (int): Specify the organization id + + Required Permissions: + Action: orgs:read + Scope: N/A - Attributes: - grafana_api_model (APIBasicModel): This is where we store the basic grafana_api_model + Raises: + ValueError: Missed specifying a necessary value + Exception: Unspecified error by executing the API call + + Returns: + api_call (dict): Returns the organization as dict """ - def __init__(self, grafana_api_model: APIBasicModel): - self.grafana_api_model = grafana_api_model + if org_id != 0: + api_call: dict = ( + Api(self.grafana_api_model) + .call_the_api(f"{APIEndpoints.ORGANISATIONS.value}/{org_id}") + .json() + ) + + if api_call == dict() or api_call.get("id") is None: + logging.error(f"Check the error: {api_call}.") + raise Exception + else: + return api_call + else: + logging.error("There is no org_id defined.") + raise ValueError + + def get_organization_by_name(self, name: str) -> dict: + """The method includes a functionality to get an organization by the name. + + Args: + name (str): Specify the organization name + + Required Permissions: + Action: orgs:read + Scope: N/A + Note: Needs to be assigned globally. + + Raises: + ValueError: Missed specifying a necessary value + Exception: Unspecified error by executing the API call + + Returns: + api_call (dict): Returns the organization as dict + """ + + if len(name) != 0: + api_call: dict = ( + Api(self.grafana_api_model) + .call_the_api(f"{APIEndpoints.ORGANISATIONS.value}/name/{name}") + .json() + ) + + if api_call == dict() or api_call.get("id") is None: + logging.error(f"Check the error: {api_call}.") + raise Exception + else: + return api_call + else: + logging.error("There is no name defined.") + raise ValueError + + def get_organizations(self) -> list: + """The method includes a functionality to get all organizations. + + Required Permissions: + Action: orgs:read + Scope: N/A + Note: Needs to be assigned globally. + + Raises: + Exception: Unspecified error by executing the API call + + Returns: + api_call (list): Returns all organizations as list + """ + + api_call: list = ( + Api(self.grafana_api_model) + .call_the_api(APIEndpoints.ORGANISATIONS.value) + .json() + ) + + if api_call == list() or api_call[0].get("id") is None: + logging.error(f"Check the error: {api_call}.") + raise Exception + else: + return api_call + + def create_organization(self, name: str) -> int: + """The method includes a functionality to create an organization. + + Args: + name (str): Specify the organization name + + Required Permissions: + Action: orgs:create + Scope: N/A + Note: Needs to be assigned globally. + + Raises: + ValueError: Missed specifying a necessary value + Exception: Unspecified error by executing the API call + + Returns: + org_id (int): Returns the id of the created organization + """ + + if len(name) != 0: + api_call: dict = ( + Api(self.grafana_api_model) + .call_the_api( + APIEndpoints.ORGANISATIONS.value, + RequestsMethods.POST, + json.dumps(dict({"name": name})), + ) + .json() + ) + + if api_call.get("message") != "Organization created": + logging.error(f"Check the error: {api_call}.") + raise Exception + else: + return int(api_call.get("orgId")) + else: + logging.error("There is no name defined.") + raise ValueError + + def update_organization(self, org_id: int, name: str): + """The method includes a functionality to update an organization. + + Args: + org_id (int): Specify the organization id + name (str): Specify the organization name + + Required Permissions: + Action: orgs:write + Scope: N/A + + Raises: + ValueError: Missed specifying a necessary value + Exception: Unspecified error by executing the API call + + Returns: + None + """ + + if org_id != 0 and len(name) != 0: + api_call: dict = ( + Api(self.grafana_api_model) + .call_the_api( + f"{APIEndpoints.ORGANISATIONS.value}/{org_id}", + RequestsMethods.PUT, + json.dumps(dict({"name": name})), + ) + .json() + ) + + if api_call.get("message") != "Organization updated": + logging.error(f"Check the error: {api_call}.") + raise Exception + else: + logging.info("You successfully updated the organization.") + else: + logging.error("There is no org_id or name defined.") + raise ValueError + + def delete_organization(self, org_id: int): + """The method includes a functionality to delete an organization. + + Args: + org_id (int): Specify the organization id + + Required Permissions: + Action: orgs:delete + Scope: N/A + + Raises: + ValueError: Missed specifying a necessary value + Exception: Unspecified error by executing the API call + + Returns: + None + """ + + if org_id != 0: + api_call: dict = ( + Api(self.grafana_api_model) + .call_the_api( + f"{APIEndpoints.ORGANISATIONS.value}/{org_id}", + RequestsMethods.DELETE, + ) + .json() + ) + + if api_call.get("message") != "Organization deleted": + logging.error(f"Check the error: {api_call}.") + raise Exception + else: + logging.info("You successfully deleted the organization.") + else: + logging.error("There is no org_id defined.") + raise ValueError + + def get_organization_users(self, org_id: int) -> list: + """The method includes a functionality to get all organization users specified by the organization id. + + Args: + org_id (int): Specify the organization id + + Required Permissions: + Action: org.users:read + Scope: users:* + + Raises: + ValueError: Missed specifying a necessary value + Exception: Unspecified error by executing the API call + + Returns: + api_call (list): Returns all organization users as list + """ + + if org_id != 0: + api_call: list = ( + Api(self.grafana_api_model) + .call_the_api(f"{APIEndpoints.ORGANISATIONS.value}/{org_id}/users") + .json() + ) + + if api_call == list() or api_call[0].get("orgId") is None: + logging.error(f"Check the error: {api_call}.") + raise Exception + else: + return api_call + else: + logging.error("There is no org_id defined.") + raise ValueError + + def add_organization_user(self, org_id: int, login_or_email: str, role: str) -> int: + """The method includes a functionality to add a user to an organization. + + Args: + org_id (int): Specify the organization id + login_or_email (str): Specify the added user + role (str): Specify the added role for the user + + Required Permissions: + Action: org.users:add + Scope: users:* + + Raises: + ValueError: Missed specifying a necessary value + Exception: Unspecified error by executing the API call + + Returns: + user_id (int): Returns the added user id + """ + + if org_id != 0 and len(login_or_email) != 0 and len(role) != 0: + api_call: dict = ( + Api(self.grafana_api_model) + .call_the_api( + f"{APIEndpoints.ORGANISATIONS.value}/{org_id}/users", + RequestsMethods.POST, + json.dumps(dict({"loginOrEmail": login_or_email, "role": role})), + ) + .json() + ) + + if api_call.get("message") != "User added to organization": + logging.error(f"Check the error: {api_call}.") + raise Exception + else: + return api_call.get("userId") + else: + logging.error("There is no org_id, login_or_email or role defined.") + raise ValueError + + def update_organization_user(self, org_id: int, user_id: int, role: str): + """The method includes a functionality to update organization user specified by the organization id, the user_id and the role. + + Args: + org_id (int): Specify the organization id + user_id (int): Specify the user id + role (str): Specify the added role for the user + + Required Permissions: + Action: org.users.role:update + Scope: users:* + + Raises: + ValueError: Missed specifying a necessary value + Exception: Unspecified error by executing the API call + + Returns: + None + """ + + if org_id != 0 and user_id != 0 and len(role) != 0: + api_call: dict = ( + Api(self.grafana_api_model) + .call_the_api( + f"{APIEndpoints.ORGANISATIONS.value}/{org_id}/users/{user_id}", + RequestsMethods.PATCH, + json.dumps(dict({"role": role})), + ) + .json() + ) + + if api_call.get("message") != "Organization user updated": + logging.error(f"Check the error: {api_call}.") + raise Exception + else: + logging.info("You successfully updated user inside the organization.") + else: + logging.error("There is no org_id, user_id or role defined.") + raise ValueError + + def delete_organization_user(self, org_id: int, user_id: int): + """The method includes a functionality to remove an organization users specified by the organization id and the user id. + + Args: + org_id (int): Specify the organization id + user_id (int): Specify the user id + + Required Permissions: + Action: org.users:remove + Scope: users:* + + Raises: + ValueError: Missed specifying a necessary value + Exception: Unspecified error by executing the API call + + Returns: + None + """ + + if org_id != 0 and user_id != 0: + api_call: dict = ( + Api(self.grafana_api_model) + .call_the_api( + f"{APIEndpoints.ORGANISATIONS.value}/{org_id}/users/{user_id}", + RequestsMethods.DELETE, + ) + .json() + ) + + if api_call.get("message") != "User removed from organization": + logging.error(f"Check the error: {api_call}.") + raise Exception + else: + logging.info("You successfully removed user from the organization.") + else: + logging.error("There is no org_id or user_id defined.") + raise ValueError diff --git a/tests/integrationtest/test_organisation.py b/tests/integrationtest/test_organisation.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unittests/test_alerting.py b/tests/unittests/test_alerting.py index a22fa8f..c0364c1 100644 --- a/tests/unittests/test_alerting.py +++ b/tests/unittests/test_alerting.py @@ -470,7 +470,7 @@ def test_test_alertmanager_receivers_test_not_possible(self, call_the_api_mock): with self.assertRaises(Exception): alerting.test_alertmanager_receivers( - {"test": "test"}, alertmanager_receivers + {"test": "test"}, list([alertmanager_receivers]) ) @patch("src.grafana_api.api.Api.call_the_api") @@ -495,7 +495,7 @@ def test_test_alertmanager_receivers_test_not_found(self, call_the_api_mock): with self.assertRaises(Exception): alerting.test_alertmanager_receivers( - {"test": "test"}, alertmanager_receivers + {"test": "test"}, list([alertmanager_receivers]) ) @patch("src.grafana_api.api.Api.call_the_api") diff --git a/tests/unittests/test_api.py b/tests/unittests/test_api.py index e9497ab..9ceef80 100644 --- a/tests/unittests/test_api.py +++ b/tests/unittests/test_api.py @@ -10,23 +10,20 @@ class ApiTestCase(TestCase): - def test_call_the_api_non_method(self): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) - api: Api = Api(grafana_api_model=model) + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) + api: Api = Api(grafana_api_model=model) + def test_call_the_api_non_method(self): with self.assertRaises(Exception): - api.call_the_api(api_call=MagicMock(), method=None) + self.api.call_the_api(api_call=MagicMock(), method=None) def test_call_the_api_non_valid_method(self): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) - api: Api = Api(grafana_api_model=model) - with self.assertRaises(Exception): - api.call_the_api(api_call=MagicMock(), method=MagicMock()) + self.api.call_the_api(api_call=MagicMock(), method=MagicMock()) @patch("requests.get") - def test_call_the_api_get_valid(self, get_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) + def test_call_the_api_basic_auth(self, get_mock): + model: APIModel = APIModel(host="https://test.test.de", username="test", password="test") api: Api = Api(grafana_api_model=model) mock: Mock = Mock() @@ -36,21 +33,29 @@ def test_call_the_api_get_valid(self, get_mock): self.assertEqual( "success", - api.call_the_api(api_call=MagicMock()).json()["status"], + api.call_the_api( + api_call=MagicMock() + ).json()["status"], ) - def test_call_the_api_get_not_valid(self): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) - api: Api = Api(grafana_api_model=model) + @patch("requests.get") + def test_call_the_api_get_valid(self, get_mock): + mock: Mock = Mock() + mock.json = Mock(return_value={"status": "success"}) + + get_mock.return_value = mock + self.assertEqual( + "success", + self.api.call_the_api(api_call=MagicMock()).json()["status"], + ) + + def test_call_the_api_get_not_valid(self): with self.assertRaises(MissingSchema): - api.call_the_api(api_call=MagicMock(), method=RequestsMethods.GET) + self.api.call_the_api(api_call=MagicMock(), method=RequestsMethods.GET) @patch("requests.put") def test_call_the_api_put_valid(self, put_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) - api: Api = Api(grafana_api_model=model) - mock: Mock = Mock() mock.json = Mock(return_value={"status": "success"}) @@ -58,7 +63,7 @@ def test_call_the_api_put_valid(self, put_mock): self.assertEqual( "success", - api.call_the_api( + self.api.call_the_api( api_call=MagicMock(), method=RequestsMethods.PUT, json_complete=MagicMock(), @@ -66,17 +71,11 @@ def test_call_the_api_put_valid(self, put_mock): ) def test_call_the_api_put_not_valid(self): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) - api: Api = Api(grafana_api_model=model) - with self.assertRaises(Exception): - api.call_the_api(api_call=MagicMock(), method=RequestsMethods.PUT) + self.api.call_the_api(api_call=MagicMock(), method=RequestsMethods.PUT) @patch("requests.post") def test_call_the_api_post_valid(self, post_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) - api: Api = Api(grafana_api_model=model) - mock: Mock = Mock() mock.json = Mock(return_value={"status": "success"}) @@ -84,7 +83,7 @@ def test_call_the_api_post_valid(self, post_mock): self.assertEqual( "success", - api.call_the_api( + self.api.call_the_api( api_call=MagicMock(), method=RequestsMethods.POST, json_complete=MagicMock(), @@ -92,28 +91,47 @@ def test_call_the_api_post_valid(self, post_mock): ) def test_call_the_api_post_not_valid(self): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) - api: Api = Api(grafana_api_model=model) - with self.assertRaises(MissingSchema): - api.call_the_api( + self.api.call_the_api( api_call=MagicMock(), method=RequestsMethods.POST, json_complete=MagicMock(), ) def test_call_the_api_post_no_data(self): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) - api: Api = Api(grafana_api_model=model) + with self.assertRaises(Exception): + self.api.call_the_api(api_call=MagicMock(), method=RequestsMethods.POST) + + @patch("requests.patch") + def test_call_the_api_patch_valid(self, post_mock): + mock: Mock = Mock() + mock.json = Mock(return_value={"status": "success"}) + + post_mock.return_value = mock + + self.assertEqual( + "success", + self.api.call_the_api( + api_call=MagicMock(), + method=RequestsMethods.PATCH, + json_complete=MagicMock(), + ).json()["status"], + ) + + def test_call_the_api_patch_not_valid(self): + with self.assertRaises(MissingSchema): + self.api.call_the_api( + api_call=MagicMock(), + method=RequestsMethods.PATCH, + json_complete=MagicMock(), + ) + def test_call_the_api_patch_no_data(self): with self.assertRaises(Exception): - api.call_the_api(api_call=MagicMock(), method=RequestsMethods.POST) + self.api.call_the_api(api_call=MagicMock(), method=RequestsMethods.PATCH) @patch("requests.delete") def test_call_the_api_delete_valid(self, delete_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) - api: Api = Api(grafana_api_model=model) - mock: Mock = Mock() mock.json = Mock(return_value={"message": "Deletion successful"}) @@ -121,59 +139,44 @@ def test_call_the_api_delete_valid(self, delete_mock): self.assertEqual( "Deletion successful", - api.call_the_api( + self.api.call_the_api( api_call=MagicMock(), method=RequestsMethods.DELETE ).json()["message"], ) def test_call_the_api_delete_not_valid(self): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) - api: Api = Api(grafana_api_model=model) - with self.assertRaises(Exception): - api.call_the_api(api_call=MagicMock(), method=RequestsMethods.DELETE) + self.api.call_the_api(api_call=MagicMock(), method=RequestsMethods.DELETE) def test_check_the_api_call_response(self): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) - api: Api = Api(grafana_api_model=model) - mock: Mock = Mock() mock.json = Mock(return_value=dict({"test": "test"})) self.assertEqual( dict({"test": "test"}), - api._Api__check_the_api_call_response(response=mock).json(), + self.api._Api__check_the_api_call_response(response=mock).json(), ) def test_check_the_api_call_response_no_error_message(self): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) - api: Api = Api(grafana_api_model=model) - mock: Mock = Mock() mock.json = Mock(return_value=dict({"message": "test"})) self.assertEqual( dict({"message": "test"}), - api._Api__check_the_api_call_response(response=mock).json(), + self.api._Api__check_the_api_call_response(response=mock).json(), ) def test_check_the_api_call_response_no_json_response_value(self): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) - api: Api = Api(grafana_api_model=model) - mock: Mock = Mock() mock.text = Mock(return_value="test") self.assertEqual( - "test", api._Api__check_the_api_call_response(response=mock).text() + "test", self.api._Api__check_the_api_call_response(response=mock).text() ) def test_check_the_api_call_response_exception(self): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) - api: Api = Api(grafana_api_model=model) - mock: Mock = Mock() mock.json = Mock(return_value=dict({"message": "invalid API key"})) with self.assertRaises(requests.exceptions.ConnectionError): - api._Api__check_the_api_call_response(response=mock) + self.api._Api__check_the_api_call_response(response=mock) diff --git a/tests/unittests/test_model.py b/tests/unittests/test_model.py index ebe38df..e2cd93b 100644 --- a/tests/unittests/test_model.py +++ b/tests/unittests/test_model.py @@ -17,7 +17,9 @@ def test_api_endpoints_init(self): class RequestsMethodsTestCase(TestCase): def test_requests_methods_init(self): self.assertEqual("RequestsMethods.GET", str(RequestsMethods.GET)) + self.assertEqual("RequestsMethods.PUT", str(RequestsMethods.PUT)) self.assertEqual("RequestsMethods.POST", str(RequestsMethods.POST)) + self.assertEqual("RequestsMethods.PATCH", str(RequestsMethods.PATCH)) self.assertEqual("RequestsMethods.DELETE", str(RequestsMethods.DELETE)) @@ -28,6 +30,13 @@ def test_api_model_init(self): self.assertEqual("test", model.host) self.assertEqual("test", model.token) + def test_api_model_init_basic_auth(self): + model = APIModel(host="test", username="test", password="test") + + self.assertEqual("test", model.host) + self.assertEqual("test", model.username) + self.assertEqual("test", model.password) + class DatasourceQueryTestCase(TestCase): def test_datasource_query_init(self): diff --git a/tests/unittests/test_organisation.py b/tests/unittests/test_organisation.py new file mode 100644 index 0000000..a459840 --- /dev/null +++ b/tests/unittests/test_organisation.py @@ -0,0 +1,500 @@ +from unittest import TestCase +from unittest.mock import MagicMock, Mock, patch + +from src.grafana_api.model import APIModel +from src.grafana_api.organisation import Organisation, OrganisationAdmin + + +class OrganisationTestCase(TestCase): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) + organisation: Organisation = Organisation(grafana_api_model=model) + + @patch("src.grafana_api.api.Api.call_the_api") + def test_get_current_organization(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict({"id": 1})) + + call_the_api_mock.return_value = mock + + self.assertEqual( + dict({"id": 1}), self.organisation.get_current_organization() + ) + + @patch("src.grafana_api.api.Api.call_the_api") + def test_get_current_organization_error_response(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(Exception): + self.organisation.get_current_organization() + + @patch("src.grafana_api.api.Api.call_the_api") + def test_get_all_users_by_the_current_organization(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=list([dict({"orgId": 1})])) + + call_the_api_mock.return_value = mock + + self.assertEqual( + list([dict({"orgId": 1})]), self.organisation.get_all_users_by_the_current_organization() + ) + + @patch("src.grafana_api.api.Api.call_the_api") + def test_get_all_users_by_the_current_organization_error_response(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=list()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(Exception): + self.organisation.get_all_users_by_the_current_organization() + + @patch("src.grafana_api.api.Api.call_the_api") + def test_get_all_users_by_the_current_organization_lookup(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=list([dict({"userId": 1})])) + + call_the_api_mock.return_value = mock + + self.assertEqual( + list([dict({"userId": 1})]), self.organisation.get_all_users_by_the_current_organization_lookup() + ) + + @patch("src.grafana_api.api.Api.call_the_api") + def test_get_all_users_by_the_current_organization_lookup_error_response(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=list()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(Exception): + self.organisation.get_all_users_by_the_current_organization_lookup() + + @patch("src.grafana_api.api.Api.call_the_api") + def test_update_organization_user_role_by_user_id(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict({"message": "Organization user updated"})) + + call_the_api_mock.return_value = mock + + self.assertEqual( + None, self.organisation.update_organization_user_role_by_user_id(1, "Viewer") + ) + + @patch("src.grafana_api.api.Api.call_the_api") + def test_update_organization_user_role_by_user_id_no_user_id(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(ValueError): + self.organisation.update_organization_user_role_by_user_id(0, "") + + @patch("src.grafana_api.api.Api.call_the_api") + def test_update_organization_user_role_by_user_id_error_response(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(Exception): + self.organisation.update_organization_user_role_by_user_id(1, "Viewer") + + @patch("src.grafana_api.api.Api.call_the_api") + def test_delete_organization_user_by_user_id(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict({"message": "User removed from organization"})) + + call_the_api_mock.return_value = mock + + self.assertEqual( + None, self.organisation.delete_organization_user_by_user_id(1) + ) + + @patch("src.grafana_api.api.Api.call_the_api") + def test_delete_organization_user_by_user_id_no_user_id(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(ValueError): + self.organisation.delete_organization_user_by_user_id(0) + + @patch("src.grafana_api.api.Api.call_the_api") + def test_delete_organization_user_by_user_id_error_response(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(Exception): + self.organisation.delete_organization_user_by_user_id(1) + + @patch("src.grafana_api.api.Api.call_the_api") + def test_update_current_organization(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict({"message": "Organization updated"})) + + call_the_api_mock.return_value = mock + + self.assertEqual( + None, self.organisation.update_current_organization("test") + ) + + @patch("src.grafana_api.api.Api.call_the_api") + def test_update_current_organization_no_role_name(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(ValueError): + self.organisation.update_current_organization("") + + @patch("src.grafana_api.api.Api.call_the_api") + def test_update_current_organization_error_response(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(Exception): + self.organisation.update_current_organization("test") + + @patch("src.grafana_api.api.Api.call_the_api") + def test_add_new_user_to_current_organization(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict({"message": "User added to organization", "userId": 1})) + + call_the_api_mock.return_value = mock + + self.assertEqual( + 1, self.organisation.add_new_user_to_current_organization("test", "test") + ) + + @patch("src.grafana_api.api.Api.call_the_api") + def test_add_new_user_to_current_organization_no_role(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(ValueError): + self.organisation.add_new_user_to_current_organization("", "") + + @patch("src.grafana_api.api.Api.call_the_api") + def test_add_new_user_to_current_organization_error_response(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(Exception): + self.organisation.add_new_user_to_current_organization("test", "test") + + +class OrganisationAdminTestCase(TestCase): + model: APIModel = APIModel(host=MagicMock(), username=MagicMock(), password=MagicMock()) + organisation: OrganisationAdmin = OrganisationAdmin(grafana_api_model=model) + + @patch("src.grafana_api.api.Api.call_the_api") + def test_get_organization_by_id(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict({"id": 10})) + + call_the_api_mock.return_value = mock + + self.assertEqual( + dict({"id": 10}), self.organisation.get_organization_by_id(1) + ) + + @patch("src.grafana_api.api.Api.call_the_api") + def test_get_organization_by_id_no_id(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(ValueError): + self.organisation.get_organization_by_id(0) + + @patch("src.grafana_api.api.Api.call_the_api") + def test_get_organization_by_id_error_response(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(Exception): + self.organisation.get_organization_by_id(1) + + @patch("src.grafana_api.api.Api.call_the_api") + def test_get_organization_by_name(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict({"id": 10})) + + call_the_api_mock.return_value = mock + + self.assertEqual( + dict({"id": 10}), self.organisation.get_organization_by_name("test") + ) + + @patch("src.grafana_api.api.Api.call_the_api") + def test_get_organization_by_name_no_name(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(ValueError): + self.organisation.get_organization_by_name("") + + @patch("src.grafana_api.api.Api.call_the_api") + def test_get_organization_by_name_error_response(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(Exception): + self.organisation.get_organization_by_name("test") + + @patch("src.grafana_api.api.Api.call_the_api") + def test_get_organizations(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=list([{"id": 1}])) + + call_the_api_mock.return_value = mock + + self.assertEqual(list([{"id": 1}]), self.organisation.get_organizations()) + + @patch("src.grafana_api.api.Api.call_the_api") + def test_get_organizations_error_response(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=list()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(Exception): + self.organisation.get_organizations() + + @patch("src.grafana_api.api.Api.call_the_api") + def test_create_organization(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict({"message": "Organization created", "orgId": 10})) + + call_the_api_mock.return_value = mock + + self.assertEqual( + 10, self.organisation.create_organization("test") + ) + + @patch("src.grafana_api.api.Api.call_the_api") + def test_create_organization_no_name(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(ValueError): + self.organisation.create_organization("") + + @patch("src.grafana_api.api.Api.call_the_api") + def test_create_organization_error_response(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(Exception): + self.organisation.create_organization("test") + + @patch("src.grafana_api.api.Api.call_the_api") + def test_update_organization(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict({"message": "Organization updated"})) + + call_the_api_mock.return_value = mock + + self.assertEqual( + None, self.organisation.update_organization(1, "test") + ) + + @patch("src.grafana_api.api.Api.call_the_api") + def test_update_organization_no_org_id(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(ValueError): + self.organisation.update_organization(0, "") + + @patch("src.grafana_api.api.Api.call_the_api") + def test_update_organization_error_response(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(Exception): + self.organisation.update_organization(1, "test") + + @patch("src.grafana_api.api.Api.call_the_api") + def test_delete_organization(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict({"message": "Organization deleted"})) + + call_the_api_mock.return_value = mock + + self.assertEqual( + None, self.organisation.delete_organization(1) + ) + + @patch("src.grafana_api.api.Api.call_the_api") + def test_delete_organization_no_org_id(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(ValueError): + self.organisation.delete_organization(0) + + @patch("src.grafana_api.api.Api.call_the_api") + def test_delete_organization_error_response(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(Exception): + self.organisation.delete_organization(1) + + @patch("src.grafana_api.api.Api.call_the_api") + def test_get_organization_users(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=list([dict({"orgId": 1, "userId": 10})])) + + call_the_api_mock.return_value = mock + + self.assertEqual( + list([dict({"orgId": 1, "userId": 10})]), self.organisation.get_organization_users(1) + ) + + @patch("src.grafana_api.api.Api.call_the_api") + def test_get_organization_users_no_org_id(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=list()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(ValueError): + self.organisation.get_organization_users(0) + + @patch("src.grafana_api.api.Api.call_the_api") + def test_get_organization_users_error_response(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=list()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(Exception): + self.organisation.get_organization_users(1) + + @patch("src.grafana_api.api.Api.call_the_api") + def test_add_organization_user(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict({"message": "User added to organization", "userId": 10})) + + call_the_api_mock.return_value = mock + + self.assertEqual( + 10, self.organisation.add_organization_user(1, "test", "test") + ) + + @patch("src.grafana_api.api.Api.call_the_api") + def test_add_organization_user_no_org_id(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(ValueError): + self.organisation.add_organization_user(0, "", "") + + @patch("src.grafana_api.api.Api.call_the_api") + def test_add_organization_user_error_response(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(Exception): + self.organisation.add_organization_user(1, "test", "test") + + @patch("src.grafana_api.api.Api.call_the_api") + def test_update_organization_user(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict({"message": "Organization user updated"})) + + call_the_api_mock.return_value = mock + + self.assertEqual( + None, self.organisation.update_organization_user(1, 10, "test") + ) + + @patch("src.grafana_api.api.Api.call_the_api") + def test_update_organization_user_no_org_id(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(ValueError): + self.organisation.update_organization_user(0, 0, "") + + @patch("src.grafana_api.api.Api.call_the_api") + def test_update_organization_user_error_response(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(Exception): + self.organisation.update_organization_user(1, 10, "test") + + @patch("src.grafana_api.api.Api.call_the_api") + def test_delete_organization_user(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict({"message": "User removed from organization"})) + + call_the_api_mock.return_value = mock + + self.assertEqual( + None, self.organisation.delete_organization_user(1, 10) + ) + + @patch("src.grafana_api.api.Api.call_the_api") + def test_delete_organization_user_no_org_id(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(ValueError): + self.organisation.delete_organization_user(0, 0) + + @patch("src.grafana_api.api.Api.call_the_api") + def test_delete_organization_user_error_response(self, call_the_api_mock): + mock: Mock = Mock() + mock.json = Mock(return_value=dict()) + + call_the_api_mock.return_value = mock + + with self.assertRaises(Exception): + self.organisation.delete_organization_user(1, 10) \ No newline at end of file diff --git a/tests/unittests/test_search.py b/tests/unittests/test_search.py index bbbcebc..20fb32b 100644 --- a/tests/unittests/test_search.py +++ b/tests/unittests/test_search.py @@ -29,7 +29,7 @@ def test_search_invalid_empty_list(self, call_the_api_mock): call_the_api_mock.return_value = mock with self.assertRaises(Exception): - search.search(search_query=MagicMock()) + search.search(search_query="test") @patch("src.grafana_api.api.Api.call_the_api") def test_search_invalid_output(self, call_the_api_mock): diff --git a/tests/unittests/test_short_url.py b/tests/unittests/test_short_url.py index 75e6787..7429ec6 100644 --- a/tests/unittests/test_short_url.py +++ b/tests/unittests/test_short_url.py @@ -36,7 +36,10 @@ def test_create_short_url_invalid_output(self, call_the_api_mock): model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) short_url: ShortUrl = ShortUrl(grafana_api_model=model) - call_the_api_mock.side_effect = Exception + mock: Mock = Mock() + mock.json = Mock(return_value=dict({"url": None})) + + call_the_api_mock.return_value = mock with self.assertRaises(Exception): - short_url.create_short_url(path=MagicMock()) + short_url.create_short_url(path="test") From 152b0dbfd619374056fd4bbfea19b9a735851c92 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 8 Apr 2022 06:51:00 +0000 Subject: [PATCH 6/9] Add coverage badge and documentation --- docs/content/grafana_api/model.md | 15 --------------- docs/coverage.svg | 4 ++-- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/docs/content/grafana_api/model.md b/docs/content/grafana_api/model.md index 618bd49..2af0fbf 100644 --- a/docs/content/grafana_api/model.md +++ b/docs/content/grafana_api/model.md @@ -4,7 +4,6 @@ * [APIEndpoints](#grafana_api.model.APIEndpoints) * [RequestsMethods](#grafana_api.model.RequestsMethods) * [APIModel](#grafana_api.model.APIModel) - * [APIBasicModel](#grafana_api.model.APIBasicModel) * [DatasourceQuery](#grafana_api.model.DatasourceQuery) * [DatasourceRuleQuery](#grafana_api.model.DatasourceRuleQuery) * [Alert](#grafana_api.model.Alert) @@ -51,20 +50,6 @@ The class includes all necessary variables to establish a connection to the Graf - `host` _str_ - Specify the host of the Grafana system - `token` _str_ - Specify the access token of the Grafana system - - - -## APIBasicModel Objects - -```python -class APIBasicModel(NamedTuple) -``` - -The class includes all necessary variables to establish a basic connection to the Grafana API endpoints - -**Arguments**: - -- `host` _str_ - Specify the host of the Grafana system - `username` _str_ - Specify the username of the Grafana system - `password` _str_ - Specify the password of the Grafana system diff --git a/docs/coverage.svg b/docs/coverage.svg index 6bfc8fa..e5db27c 100644 --- a/docs/coverage.svg +++ b/docs/coverage.svg @@ -15,7 +15,7 @@ coverage coverage - 99% - 99% + 100% + 100% From ea3dccca7c9d6556743277a62dc8943dcc82da64 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Fri, 8 Apr 2022 22:42:53 +0200 Subject: [PATCH 7/9] Add integrationtest and update the documentation --- README.md | 29 +++++++++-- tests/integrationtest/test_organisation.py | 57 ++++++++++++++++++++++ 2 files changed, 81 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index fa5524c..62daa6b 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,6 @@ The repository includes an SDK for the Grafana API. It's possible to communicate - Get all Alertmanager alerts - Create or update Alertmanager alerts - Get Alertmanager group alerts - - Get all Alertmanager silences - Get Alertmanager silence by id - Create or update Alertmanager silence @@ -94,13 +93,35 @@ The repository includes an SDK for the Grafana API. It's possible to communicate - Delete a notification channel by uid - Test a notification channel +### Organization +- Get current organisation +- Update the current organisation name +- Add a new user and the role to the current organisation +- Get all users from current organisation +- Get all users from current organisation (lookup) +- Update the role of an organisation user by the user id +- Delete an organisation user by the user id +- Get an organisation by the id +- Get an organisation by the name +- Get all organisations +- Create an organisation +- Update an organisation +- Delete an organisation +- Get organisation users +- Add a new organisation user +- Update an organisation user +- Delete an organisation user + +### Short URL +- Create a short url + ## Feature timeline The following table describes the plan to implement the rest of the Grafana API functionality. Please, open an issue and vote them up, if you prefer a faster implementation of an API functionality. | API endpoint group | Implementation week | Maintainer | PR | State | |:------------------:|:-------------------:|:----------:|:--:|:-----:| -| [Admin HTTP API](https://grafana.com/docs/grafana/latest/http_api/admin/) | | | | | +| [Admin HTTP API](https://grafana.com/docs/grafana/latest/http_api/admin/) | 16 | [ZPascal](https://github.com/ZPascal) | | | | [Annotations HTTP API](https://grafana.com/docs/grafana/latest/http_api/annotations/) | | | | | | [Authentication HTTP API](https://grafana.com/docs/grafana/latest/http_api/auth/) | | | | | | [External Group Sync HTTP API](https://grafana.com/docs/grafana/latest/http_api/external_group_sync/) | | | | | @@ -109,13 +130,11 @@ The following table describes the plan to implement the rest of the Grafana API | [HTTP Snapshot API](https://grafana.com/docs/grafana/latest/http_api/snapshot/) | | | | | | [Library Element HTTP API](https://grafana.com/docs/grafana/latest/http_api/library_element/) | | | | | | [Licensing HTTP API](https://grafana.com/docs/grafana/latest/http_api/licensing/) | | | | | -| [Organization HTTP API](https://grafana.com/docs/grafana/latest/http_api/org/) | 13 | | | In process | | [Other HTTP API](https://grafana.com/docs/grafana/latest/http_api/other/) | | | | | | [Playlist HTTP API](https://grafana.com/docs/grafana/latest/http_api/playlist/) | | | | | | [Reporting API](https://grafana.com/docs/grafana/latest/http_api/reporting/) | | | | | -| [Short URL HTTP API](https://grafana.com/docs/grafana/latest/http_api/short_url/) | 13 | | | In process | | [Team HTTP API](https://grafana.com/docs/grafana/latest/http_api/team/) | | | | | -| [User HTTP API](https://grafana.com/docs/grafana/latest/http_api/user/) | | | | | +| [User HTTP API](https://grafana.com/docs/grafana/latest/http_api/user/) | 16 | [ZPascal](https://github.com/ZPascal) | | | ## Installation diff --git a/tests/integrationtest/test_organisation.py b/tests/integrationtest/test_organisation.py index e69de29..4b036f4 100644 --- a/tests/integrationtest/test_organisation.py +++ b/tests/integrationtest/test_organisation.py @@ -0,0 +1,57 @@ +import os +from unittest import TestCase + +from src.grafana_api.model import ( + APIModel, +) +from src.grafana_api.organisation import Organisation + + +class OrganisationTest(TestCase): + model: APIModel = APIModel( + host=os.environ["GRAFANA_HOST"], + token=os.environ["GRAFANA_TOKEN"], + ) + organisation: Organisation = Organisation(model) + + def test_get_current_organization(self): + organisation: dict = self.organisation.get_current_organization() + + self.assertEqual(4, organisation.get("id")) + + def test_get_all_users_by_the_current_organization(self): + organisation_users: list = self.organisation.get_all_users_by_the_current_organization() + + self.assertEqual(4, organisation_users[0].get("userId")) + + def test_get_all_users_by_the_current_organization_lookup(self): + organisation_users: list = self.organisation.get_all_users_by_the_current_organization_lookup() + + self.assertEqual(4, organisation_users[0].get("userId")) + + def test_a_update_current_organization(self): + self.organisation.update_current_organization("Test") + self.assertEqual("Test", self.organisation.get_current_organization().get("name")) + + def test_b_update_current_organization(self): + self.organisation.update_current_organization("Github") + self.assertEqual("Github", self.organisation.get_current_organization().get("name")) + + def test_a_add_new_user_to_current_organization(self): + self.organisation.add_new_user_to_current_organization("info@theiotstudio.com", "Viewer") + + organisation_users: list = self.organisation.get_all_users_by_the_current_organization_lookup() + self.assertEqual("Test", organisation_users[0].get("login")) + + def test_b_update_organization_user_role_by_user_id(self): + self.organisation.update_organization_user_role_by_user_id(7, "Editor") + + organisation_users: list = self.organisation.get_all_users_by_the_current_organization() + self.assertEqual(7, organisation_users[0].get("userId")) + self.assertEqual("Editor", organisation_users[0].get("role")) + + def test_c_delete_organization_user_by_user_id(self): + self.organisation.delete_organization_user_by_user_id(7) + + organisation_users: list = self.organisation.get_all_users_by_the_current_organization() + self.assertEqual(2, len(organisation_users)) From 16f965501d091e6523842f1212a9ed48f7aa615f Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Fri, 8 Apr 2022 23:17:55 +0200 Subject: [PATCH 8/9] Specify a version --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b08fa6b..f8e7df0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ requests -pydoc-markdown +pydoc-markdown==4.6.2 mkdocs mkdocs-material \ No newline at end of file From 1f56af075bd1d1d6f8e01ea8249ef275ae411688 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Sat, 9 Apr 2022 10:44:20 +0200 Subject: [PATCH 9/9] Reformat the file --- src/grafana_api/organisation.py | 15 +++- tests/integrationtest/test_organisation.py | 32 +++++-- tests/unittests/test_api.py | 8 +- tests/unittests/test_organisation.py | 98 ++++++++++++---------- 4 files changed, 93 insertions(+), 60 deletions(-) diff --git a/src/grafana_api/organisation.py b/src/grafana_api/organisation.py index 25b97b3..f71d12a 100644 --- a/src/grafana_api/organisation.py +++ b/src/grafana_api/organisation.py @@ -193,7 +193,11 @@ def update_current_organization(self, name: str): if len(name) != 0: api_call: dict = ( Api(self.grafana_api_model) - .call_the_api(APIEndpoints.ORGANISATION.value, RequestsMethods.PUT, json.dumps(dict({"name": name}))) + .call_the_api( + APIEndpoints.ORGANISATION.value, + RequestsMethods.PUT, + json.dumps(dict({"name": name})), + ) .json() ) @@ -206,7 +210,9 @@ def update_current_organization(self, name: str): logging.error("There is no role_name defined.") raise ValueError - def add_new_user_to_current_organization(self, login_or_email: str, role: str) -> int: + def add_new_user_to_current_organization( + self, login_or_email: str, role: str + ) -> int: """The method includes a functionality to add a new user to the current organization. Args: @@ -229,8 +235,9 @@ def add_new_user_to_current_organization(self, login_or_email: str, role: str) - api_call: dict = ( Api(self.grafana_api_model) .call_the_api( - f"{APIEndpoints.ORGANISATION.value}/users", RequestsMethods.POST, - json.dumps(dict({"loginOrEmail": login_or_email, "role": role})) + f"{APIEndpoints.ORGANISATION.value}/users", + RequestsMethods.POST, + json.dumps(dict({"loginOrEmail": login_or_email, "role": role})), ) .json() ) diff --git a/tests/integrationtest/test_organisation.py b/tests/integrationtest/test_organisation.py index 4b036f4..56534ec 100644 --- a/tests/integrationtest/test_organisation.py +++ b/tests/integrationtest/test_organisation.py @@ -20,38 +20,54 @@ def test_get_current_organization(self): self.assertEqual(4, organisation.get("id")) def test_get_all_users_by_the_current_organization(self): - organisation_users: list = self.organisation.get_all_users_by_the_current_organization() + organisation_users: list = ( + self.organisation.get_all_users_by_the_current_organization() + ) self.assertEqual(4, organisation_users[0].get("userId")) def test_get_all_users_by_the_current_organization_lookup(self): - organisation_users: list = self.organisation.get_all_users_by_the_current_organization_lookup() + organisation_users: list = ( + self.organisation.get_all_users_by_the_current_organization_lookup() + ) self.assertEqual(4, organisation_users[0].get("userId")) def test_a_update_current_organization(self): self.organisation.update_current_organization("Test") - self.assertEqual("Test", self.organisation.get_current_organization().get("name")) + self.assertEqual( + "Test", self.organisation.get_current_organization().get("name") + ) def test_b_update_current_organization(self): self.organisation.update_current_organization("Github") - self.assertEqual("Github", self.organisation.get_current_organization().get("name")) + self.assertEqual( + "Github", self.organisation.get_current_organization().get("name") + ) def test_a_add_new_user_to_current_organization(self): - self.organisation.add_new_user_to_current_organization("info@theiotstudio.com", "Viewer") + self.organisation.add_new_user_to_current_organization( + "info@theiotstudio.com", "Viewer" + ) - organisation_users: list = self.organisation.get_all_users_by_the_current_organization_lookup() + organisation_users: list = ( + self.organisation.get_all_users_by_the_current_organization_lookup() + ) self.assertEqual("Test", organisation_users[0].get("login")) def test_b_update_organization_user_role_by_user_id(self): self.organisation.update_organization_user_role_by_user_id(7, "Editor") - organisation_users: list = self.organisation.get_all_users_by_the_current_organization() + organisation_users: list = ( + self.organisation.get_all_users_by_the_current_organization() + ) self.assertEqual(7, organisation_users[0].get("userId")) self.assertEqual("Editor", organisation_users[0].get("role")) def test_c_delete_organization_user_by_user_id(self): self.organisation.delete_organization_user_by_user_id(7) - organisation_users: list = self.organisation.get_all_users_by_the_current_organization() + organisation_users: list = ( + self.organisation.get_all_users_by_the_current_organization() + ) self.assertEqual(2, len(organisation_users)) diff --git a/tests/unittests/test_api.py b/tests/unittests/test_api.py index 9ceef80..ccd314a 100644 --- a/tests/unittests/test_api.py +++ b/tests/unittests/test_api.py @@ -23,7 +23,9 @@ def test_call_the_api_non_valid_method(self): @patch("requests.get") def test_call_the_api_basic_auth(self, get_mock): - model: APIModel = APIModel(host="https://test.test.de", username="test", password="test") + model: APIModel = APIModel( + host="https://test.test.de", username="test", password="test" + ) api: Api = Api(grafana_api_model=model) mock: Mock = Mock() @@ -33,9 +35,7 @@ def test_call_the_api_basic_auth(self, get_mock): self.assertEqual( "success", - api.call_the_api( - api_call=MagicMock() - ).json()["status"], + api.call_the_api(api_call=MagicMock()).json()["status"], ) @patch("requests.get") diff --git a/tests/unittests/test_organisation.py b/tests/unittests/test_organisation.py index a459840..71c262f 100644 --- a/tests/unittests/test_organisation.py +++ b/tests/unittests/test_organisation.py @@ -16,9 +16,7 @@ def test_get_current_organization(self, call_the_api_mock): call_the_api_mock.return_value = mock - self.assertEqual( - dict({"id": 1}), self.organisation.get_current_organization() - ) + self.assertEqual(dict({"id": 1}), self.organisation.get_current_organization()) @patch("src.grafana_api.api.Api.call_the_api") def test_get_current_organization_error_response(self, call_the_api_mock): @@ -38,11 +36,14 @@ def test_get_all_users_by_the_current_organization(self, call_the_api_mock): call_the_api_mock.return_value = mock self.assertEqual( - list([dict({"orgId": 1})]), self.organisation.get_all_users_by_the_current_organization() + list([dict({"orgId": 1})]), + self.organisation.get_all_users_by_the_current_organization(), ) @patch("src.grafana_api.api.Api.call_the_api") - def test_get_all_users_by_the_current_organization_error_response(self, call_the_api_mock): + def test_get_all_users_by_the_current_organization_error_response( + self, call_the_api_mock + ): mock: Mock = Mock() mock.json = Mock(return_value=list()) @@ -59,11 +60,14 @@ def test_get_all_users_by_the_current_organization_lookup(self, call_the_api_moc call_the_api_mock.return_value = mock self.assertEqual( - list([dict({"userId": 1})]), self.organisation.get_all_users_by_the_current_organization_lookup() + list([dict({"userId": 1})]), + self.organisation.get_all_users_by_the_current_organization_lookup(), ) @patch("src.grafana_api.api.Api.call_the_api") - def test_get_all_users_by_the_current_organization_lookup_error_response(self, call_the_api_mock): + def test_get_all_users_by_the_current_organization_lookup_error_response( + self, call_the_api_mock + ): mock: Mock = Mock() mock.json = Mock(return_value=list()) @@ -80,11 +84,14 @@ def test_update_organization_user_role_by_user_id(self, call_the_api_mock): call_the_api_mock.return_value = mock self.assertEqual( - None, self.organisation.update_organization_user_role_by_user_id(1, "Viewer") + None, + self.organisation.update_organization_user_role_by_user_id(1, "Viewer"), ) @patch("src.grafana_api.api.Api.call_the_api") - def test_update_organization_user_role_by_user_id_no_user_id(self, call_the_api_mock): + def test_update_organization_user_role_by_user_id_no_user_id( + self, call_the_api_mock + ): mock: Mock = Mock() mock.json = Mock(return_value=dict()) @@ -94,7 +101,9 @@ def test_update_organization_user_role_by_user_id_no_user_id(self, call_the_api_ self.organisation.update_organization_user_role_by_user_id(0, "") @patch("src.grafana_api.api.Api.call_the_api") - def test_update_organization_user_role_by_user_id_error_response(self, call_the_api_mock): + def test_update_organization_user_role_by_user_id_error_response( + self, call_the_api_mock + ): mock: Mock = Mock() mock.json = Mock(return_value=dict()) @@ -106,13 +115,13 @@ def test_update_organization_user_role_by_user_id_error_response(self, call_the_ @patch("src.grafana_api.api.Api.call_the_api") def test_delete_organization_user_by_user_id(self, call_the_api_mock): mock: Mock = Mock() - mock.json = Mock(return_value=dict({"message": "User removed from organization"})) + mock.json = Mock( + return_value=dict({"message": "User removed from organization"}) + ) call_the_api_mock.return_value = mock - self.assertEqual( - None, self.organisation.delete_organization_user_by_user_id(1) - ) + self.assertEqual(None, self.organisation.delete_organization_user_by_user_id(1)) @patch("src.grafana_api.api.Api.call_the_api") def test_delete_organization_user_by_user_id_no_user_id(self, call_the_api_mock): @@ -125,7 +134,9 @@ def test_delete_organization_user_by_user_id_no_user_id(self, call_the_api_mock) self.organisation.delete_organization_user_by_user_id(0) @patch("src.grafana_api.api.Api.call_the_api") - def test_delete_organization_user_by_user_id_error_response(self, call_the_api_mock): + def test_delete_organization_user_by_user_id_error_response( + self, call_the_api_mock + ): mock: Mock = Mock() mock.json = Mock(return_value=dict()) @@ -141,9 +152,7 @@ def test_update_current_organization(self, call_the_api_mock): call_the_api_mock.return_value = mock - self.assertEqual( - None, self.organisation.update_current_organization("test") - ) + self.assertEqual(None, self.organisation.update_current_organization("test")) @patch("src.grafana_api.api.Api.call_the_api") def test_update_current_organization_no_role_name(self, call_the_api_mock): @@ -168,7 +177,9 @@ def test_update_current_organization_error_response(self, call_the_api_mock): @patch("src.grafana_api.api.Api.call_the_api") def test_add_new_user_to_current_organization(self, call_the_api_mock): mock: Mock = Mock() - mock.json = Mock(return_value=dict({"message": "User added to organization", "userId": 1})) + mock.json = Mock( + return_value=dict({"message": "User added to organization", "userId": 1}) + ) call_the_api_mock.return_value = mock @@ -187,7 +198,9 @@ def test_add_new_user_to_current_organization_no_role(self, call_the_api_mock): self.organisation.add_new_user_to_current_organization("", "") @patch("src.grafana_api.api.Api.call_the_api") - def test_add_new_user_to_current_organization_error_response(self, call_the_api_mock): + def test_add_new_user_to_current_organization_error_response( + self, call_the_api_mock + ): mock: Mock = Mock() mock.json = Mock(return_value=dict()) @@ -198,7 +211,9 @@ def test_add_new_user_to_current_organization_error_response(self, call_the_api_ class OrganisationAdminTestCase(TestCase): - model: APIModel = APIModel(host=MagicMock(), username=MagicMock(), password=MagicMock()) + model: APIModel = APIModel( + host=MagicMock(), username=MagicMock(), password=MagicMock() + ) organisation: OrganisationAdmin = OrganisationAdmin(grafana_api_model=model) @patch("src.grafana_api.api.Api.call_the_api") @@ -208,9 +223,7 @@ def test_get_organization_by_id(self, call_the_api_mock): call_the_api_mock.return_value = mock - self.assertEqual( - dict({"id": 10}), self.organisation.get_organization_by_id(1) - ) + self.assertEqual(dict({"id": 10}), self.organisation.get_organization_by_id(1)) @patch("src.grafana_api.api.Api.call_the_api") def test_get_organization_by_id_no_id(self, call_the_api_mock): @@ -285,13 +298,13 @@ def test_get_organizations_error_response(self, call_the_api_mock): @patch("src.grafana_api.api.Api.call_the_api") def test_create_organization(self, call_the_api_mock): mock: Mock = Mock() - mock.json = Mock(return_value=dict({"message": "Organization created", "orgId": 10})) + mock.json = Mock( + return_value=dict({"message": "Organization created", "orgId": 10}) + ) call_the_api_mock.return_value = mock - self.assertEqual( - 10, self.organisation.create_organization("test") - ) + self.assertEqual(10, self.organisation.create_organization("test")) @patch("src.grafana_api.api.Api.call_the_api") def test_create_organization_no_name(self, call_the_api_mock): @@ -320,9 +333,7 @@ def test_update_organization(self, call_the_api_mock): call_the_api_mock.return_value = mock - self.assertEqual( - None, self.organisation.update_organization(1, "test") - ) + self.assertEqual(None, self.organisation.update_organization(1, "test")) @patch("src.grafana_api.api.Api.call_the_api") def test_update_organization_no_org_id(self, call_the_api_mock): @@ -351,9 +362,7 @@ def test_delete_organization(self, call_the_api_mock): call_the_api_mock.return_value = mock - self.assertEqual( - None, self.organisation.delete_organization(1) - ) + self.assertEqual(None, self.organisation.delete_organization(1)) @patch("src.grafana_api.api.Api.call_the_api") def test_delete_organization_no_org_id(self, call_the_api_mock): @@ -383,7 +392,8 @@ def test_get_organization_users(self, call_the_api_mock): call_the_api_mock.return_value = mock self.assertEqual( - list([dict({"orgId": 1, "userId": 10})]), self.organisation.get_organization_users(1) + list([dict({"orgId": 1, "userId": 10})]), + self.organisation.get_organization_users(1), ) @patch("src.grafana_api.api.Api.call_the_api") @@ -409,13 +419,13 @@ def test_get_organization_users_error_response(self, call_the_api_mock): @patch("src.grafana_api.api.Api.call_the_api") def test_add_organization_user(self, call_the_api_mock): mock: Mock = Mock() - mock.json = Mock(return_value=dict({"message": "User added to organization", "userId": 10})) + mock.json = Mock( + return_value=dict({"message": "User added to organization", "userId": 10}) + ) call_the_api_mock.return_value = mock - self.assertEqual( - 10, self.organisation.add_organization_user(1, "test", "test") - ) + self.assertEqual(10, self.organisation.add_organization_user(1, "test", "test")) @patch("src.grafana_api.api.Api.call_the_api") def test_add_organization_user_no_org_id(self, call_the_api_mock): @@ -471,13 +481,13 @@ def test_update_organization_user_error_response(self, call_the_api_mock): @patch("src.grafana_api.api.Api.call_the_api") def test_delete_organization_user(self, call_the_api_mock): mock: Mock = Mock() - mock.json = Mock(return_value=dict({"message": "User removed from organization"})) + mock.json = Mock( + return_value=dict({"message": "User removed from organization"}) + ) call_the_api_mock.return_value = mock - self.assertEqual( - None, self.organisation.delete_organization_user(1, 10) - ) + self.assertEqual(None, self.organisation.delete_organization_user(1, 10)) @patch("src.grafana_api.api.Api.call_the_api") def test_delete_organization_user_no_org_id(self, call_the_api_mock): @@ -497,4 +507,4 @@ def test_delete_organization_user_error_response(self, call_the_api_mock): call_the_api_mock.return_value = mock with self.assertRaises(Exception): - self.organisation.delete_organization_user(1, 10) \ No newline at end of file + self.organisation.delete_organization_user(1, 10)