diff --git a/bin/add_whitelist_rule b/bin/add_whitelist_rule new file mode 120000 index 0000000..e42252a --- /dev/null +++ b/bin/add_whitelist_rule @@ -0,0 +1 @@ +command \ No newline at end of file diff --git a/bin/create_backup b/bin/create_backup new file mode 120000 index 0000000..e42252a --- /dev/null +++ b/bin/create_backup @@ -0,0 +1 @@ +command \ No newline at end of file diff --git a/bin/create_insights_annotation b/bin/create_insights_annotation new file mode 120000 index 0000000..e42252a --- /dev/null +++ b/bin/create_insights_annotation @@ -0,0 +1 @@ +command \ No newline at end of file diff --git a/bin/delete_whitelist_rule b/bin/delete_whitelist_rule new file mode 120000 index 0000000..e42252a --- /dev/null +++ b/bin/delete_whitelist_rule @@ -0,0 +1 @@ +command \ No newline at end of file diff --git a/bin/get_insights_annotations b/bin/get_insights_annotations new file mode 120000 index 0000000..e42252a --- /dev/null +++ b/bin/get_insights_annotations @@ -0,0 +1 @@ +command \ No newline at end of file diff --git a/hypernode_api_python/client.py b/hypernode_api_python/client.py index 942f38e..22cd536 100644 --- a/hypernode_api_python/client.py +++ b/hypernode_api_python/client.py @@ -23,6 +23,8 @@ HYPERNODE_API_BRANCHER_APP_ENDPOINT = "/v2/brancher/app/{}/" HYPERNODE_API_FPM_STATUS_APP_ENDPOINT = "/v2/nats/{}/hypernode.show-fpm-status" HYPERNODE_API_BRANCHER_ENDPOINT = "/v2/brancher/{}/" +HYPERNODE_API_INSIGHTS_ANNOTATION_LIST_ENDPOINT = "/v2/insights-annotation/" +HYPERNODE_API_INSIGHTS_ANNOTATION_CREATE_ENDPOINT = "/v2/insights-annotation/create/" HYPERNODE_API_PRODUCT_APP_DETAIL_ENDPOINT = "/v2/product/app/{}/current/" HYPERNODE_API_PRODUCT_LIST_ENDPOINT = "/v2/product/" HYPERNODE_API_PRODUCT_PRICE_DETAIL_ENDPOINT = "/v2/product/{}/with_price/" @@ -315,6 +317,19 @@ def get_available_backups_for_app(self, app_name): """ return self.requests("GET", HYPERNODE_API_BACKUPS_ENDPOINT.format(app_name)) + def create_backup(self, app_name): + """ + Create a new snapshot backup of the specified app. This requires + sla-standard to be enabled on the Hypernode. + Example: + > client.create_backup('yourhypernodeappname').json() + > 'A job to create a backup has been posted' + + :param str app_name: The app to create the backup for + :return obj response: The request response object + """ + return self.requests("POST", HYPERNODE_API_BACKUPS_ENDPOINT.format(app_name)) + def get_app_eav_description(self): """ List all the available EAV settings that are available. These are @@ -581,6 +596,59 @@ def get_whitelist_rules(self, app_name, filter_data=None): "GET", HYPERNODE_API_WHITELIST_ENDPOINT.format(app_name), filter_data ) + def add_whitelist_rule(self, app_name, data): + """ + Add a whitelist rule for the specified app. See get_whitelist_options + for the available options. + Example: + > client.add_whitelist_rule( + > 'yourhypernodeappname', + > data={'ip': '1.2.3.4', 'type': 'database', 'description': 'my description'} + > ).json() + > { + > 'id': 1234, + > 'created': '2024-05-25T13:39:48Z', + > 'domainname': 'yourhypernodeappname.hypernode.io', + > 'ip': '1.2.3.4', + > 'type': 'database', + > 'description': 'my description' + > } + + :param str app_name: The name of the Hypernode to add the whitelist + rule for + :param dict data: Data describing the whitelist rule to add. An + example could be: {'ip': '1.2.3.4', 'type': 'database'}. The type can + be one of 'waf', 'database' or 'ftp', and optionally a 'description' + can be specified. + :return obj response: The request response object + """ + return self.requests( + "POST", HYPERNODE_API_WHITELIST_ENDPOINT.format(app_name), data=data + ) + + def delete_whitelist_rule(self, app_name, data): + """ + Remove a whitelist rule for the specified app. See get_whitelist_rules + for the currently configured whitelist rules. On success the response + will have status_code 204 and no content. + Example: + > client.delete_whitelist_rule( + > 'yourhypernodeappname', + > data={'ip': '1.2.3.4', 'type': 'database'} + > ).status_code + > 204 + + :param str app_name: The name of the Hypernode to remove the whitelist + rule for + :param dict data: Data describing the whitelist rule to remove. An + example could be: {'ip': '1.2.3.4', 'type': 'database'}. The type can + be one of 'waf', 'database' or 'ftp'. + :return obj response: The request response object + """ + return self.requests( + "DELETE", HYPERNODE_API_WHITELIST_ENDPOINT.format(app_name), data=data + ) + def get_current_product_for_app(self, app_name): """ Retrieve information about the product the specified App is currently on. @@ -859,6 +927,73 @@ def get_fpm_status(self, app_name): "POST", HYPERNODE_API_FPM_STATUS_APP_ENDPOINT.format(app_name) ) + def get_insights_annotations(self, params=None): + """ + List all custom Insights annotations for the Hypernodes you have + access to. Only annotations created through the API are listed, + system annotations generated from platform events are not included. + Example: + > client.get_insights_annotations().json() + > { + > 'count': 1, + > 'next': None, + > 'previous': None, + > 'results': [ + > { + > 'id': 123, + > 'name': 'Deployed release 1.2.3', + > 'x_axis': 1756384957, + > 'app': 'yourhypernodeappname', + > 'metrics': 'memory_usage', + > 'created': '2025-08-28T12:42:37Z', + > 'modified': '2025-08-28T12:42:37Z' + > } + > ] + > } + + :param dict params: Optional query parameters to paginate the + results with. An example could be: {'limit': 10, 'offset': 20} + :return obj response: The request response object + """ + return self.requests( + "GET", HYPERNODE_API_INSIGHTS_ANNOTATION_LIST_ENDPOINT, params=params + ) + + def create_insights_annotation(self, data): + """ + Create a custom Insights annotation for a Hypernode. The annotation + is shown in the Insights graphs at the point in time given by + 'x_axis' (a unix timestamp in seconds). Optionally restrict the + annotation to specific graphs with 'metrics', a comma-separated list + of metric names. When omitted, the annotation applies to all metrics. + Example: + > client.create_insights_annotation( + > data={ + > 'name': 'Deployed release 1.2.3', + > 'x_axis': 1756384957, + > 'app': 'yourhypernodeappname', + > 'metrics': 'memory_usage' + > } + > ).json() + > { + > 'id': 123, + > 'name': 'Deployed release 1.2.3', + > 'x_axis': 1756384957, + > 'app': 'yourhypernodeappname', + > 'metrics': 'memory_usage', + > 'created': '2025-08-28T12:42:37Z', + > 'modified': '2025-08-28T12:42:37Z' + > } + + :param dict data: Data describing the Insights annotation to create. + An example could be: {'name': 'Deployed release 1.2.3', + 'x_axis': 1756384957, 'app': 'yourhypernodeappname'} + :return obj response: The request response object + """ + return self.requests( + "POST", HYPERNODE_API_INSIGHTS_ANNOTATION_CREATE_ENDPOINT, data=data + ) + def create_brancher(self, app_name, data): """ Create a new branch (server replica) of your Hypernode. diff --git a/hypernode_api_python/commands.py b/hypernode_api_python/commands.py index 7f96f6b..44e1761 100644 --- a/hypernode_api_python/commands.py +++ b/hypernode_api_python/commands.py @@ -267,6 +267,24 @@ def get_available_backups_for_app(args=None): print_response(client.get_available_backups_for_app(app_name)) +def create_backup(args=None): + parser = ArgumentParser( + description=""" +Create a new snapshot backup of the Hypernode. This requires +sla-standard to be enabled on the Hypernode. + +Example: +$ ./bin/create_backup +"A job to create a backup has been posted" +""", + formatter_class=RawTextHelpFormatter, + ) + parser.parse_args(args=args) + client = get_client() + app_name = get_app_name() + print_response(client.create_backup(app_name)) + + def get_eav_description(args=None): parser = ArgumentParser( description=""" @@ -483,6 +501,84 @@ def get_whitelist_rules(args=None): print_response(client.get_whitelist_rules(app_name)) +def add_whitelist_rule(args=None): + parser = ArgumentParser( + description=""" +Add a WAF whitelist rule for the Hypernode. + +Example: +$ ./bin/add_whitelist_rule 1.2.3.4 --type database --description "my description" +{ + "id": 1234, + "created": "2024-05-25T13:39:48Z", + "domainname": "yourhypernodeappname.hypernode.io", + "ip": "1.2.3.4", + "type": "database", + "description": "my description" +} +""", + formatter_class=RawTextHelpFormatter, + ) + parser.add_argument( + "ip", + help="The IP address to whitelist", + ) + parser.add_argument( + "--type", + help="The type of whitelist rule to add", + choices=["waf", "database", "ftp"], + default="database", + ) + parser.add_argument( + "--description", + help="An optional description for the whitelist rule", + default="", + ) + args = parser.parse_args(args=args) + client = get_client() + app_name = get_app_name() + data = {"ip": args.ip, "type": args.type, "description": args.description} + print_response(client.add_whitelist_rule(app_name, data=data)) + + +def delete_whitelist_rule(args=None): + parser = ArgumentParser( + description=""" +Remove a WAF whitelist rule for the Hypernode. + +Example: +$ ./bin/delete_whitelist_rule 1.2.3.4 --type database +Whitelist rule '1.2.3.4' (database) has been removed. +""", + formatter_class=RawTextHelpFormatter, + ) + parser.add_argument( + "ip", + help="The whitelisted IP address to remove. See ./bin/get_whitelist_rules", + ) + parser.add_argument( + "--type", + help="The type of the whitelist rule to remove", + choices=["waf", "database", "ftp"], + default="database", + ) + args = parser.parse_args(args=args) + client = get_client() + app_name = get_app_name() + data = {"ip": args.ip, "type": args.type} + try: + client.delete_whitelist_rule(app_name, data=data) + print("Whitelist rule '{}' ({}) has been removed.".format(args.ip, args.type)) + exit(EX_OK) + except Exception as e: + print( + "Whitelist rule '{}' ({}) failed to be removed: {}".format( + args.ip, args.type, e + ) + ) + exit(EX_UNAVAILABLE) + + def get_current_product_for_app(args=None): parser = ArgumentParser( description=""" @@ -703,6 +799,105 @@ def destroy_brancher(args=None): exit(EX_UNAVAILABLE) +def get_insights_annotations(args=None): + parser = ArgumentParser( + description=""" +List all custom Insights annotations for the Hypernodes you have access to. +Only annotations created through the API are listed, system annotations +generated from platform events are not included. + +Example: +$ ./bin/get_insights_annotations +{ + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "id": 123, + "name": "Deployed release 1.2.3", + "x_axis": 1756384957, + "app": "yourhypernodeappname", + "metrics": "memory_usage", + "created": "2025-08-28T12:42:37Z", + "modified": "2025-08-28T12:42:37Z" + } + ] +} +""", + formatter_class=RawTextHelpFormatter, + ) + parser.add_argument( + "--limit", + help="Number of results to return per page", + type=int, + ) + parser.add_argument( + "--offset", + help="The initial index from which to return the results", + type=int, + ) + args = parser.parse_args(args=args) + client = get_client() + params = {} + if args.limit is not None: + params["limit"] = args.limit + if args.offset is not None: + params["offset"] = args.offset + print_response(client.get_insights_annotations(params=params)) + + +def create_insights_annotation(args=None): + parser = ArgumentParser( + description=""" +Create a custom Insights annotation for the Hypernode. The annotation is +shown in the Insights graphs at the specified point in time. Optionally +restrict the annotation to specific graphs with --metrics. When omitted, +the annotation applies to all metrics. + +Example: +$ ./bin/create_insights_annotation "Deployed release 1.2.3" 1756384957 --metrics memory_usage +{ + "id": 123, + "name": "Deployed release 1.2.3", + "x_axis": 1756384957, + "app": "yourhypernodeappname", + "metrics": "memory_usage", + "created": "2025-08-28T12:42:37Z", + "modified": "2025-08-28T12:42:37Z" +} +""", + formatter_class=RawTextHelpFormatter, + ) + parser.add_argument( + "name", + help="The name of the annotation to create", + ) + parser.add_argument( + "x_axis", + help="The point in time of the annotation as a unix timestamp in seconds", + type=int, + ) + parser.add_argument( + "--metrics", + help="An optional comma-separated list of metric names to restrict " + "the annotation to", + ) + parser.add_argument( + "--metadata", + help="Optional metadata to store with the annotation", + ) + args = parser.parse_args(args=args) + client = get_client() + app_name = get_app_name() + data = {"name": args.name, "x_axis": args.x_axis, "app": app_name} + if args.metrics is not None: + data["metrics"] = args.metrics + if args.metadata is not None: + data["metadata"] = args.metadata + print_response(client.create_insights_annotation(data=data)) + + def get_fpm_status(args=None): parser = ArgumentParser( description=""" diff --git a/tests/client/test_add_whitelist_rule.py b/tests/client/test_add_whitelist_rule.py new file mode 100644 index 0000000..b161917 --- /dev/null +++ b/tests/client/test_add_whitelist_rule.py @@ -0,0 +1,36 @@ +from unittest import TestCase +from unittest.mock import Mock + +from hypernode_api_python.client import ( + HYPERNODE_API_WHITELIST_ENDPOINT, + HypernodeAPIPython, +) + + +class TestAddWhitelistRule(TestCase): + def setUp(self): + self.client = HypernodeAPIPython(token="my_token") + self.mock_request = Mock() + self.client.requests = self.mock_request + self.app_name = "my_app" + + def test_whitelist_endpoint_is_correct(self): + self.assertEqual("/v2/whitelist/{}/", HYPERNODE_API_WHITELIST_ENDPOINT) + + def test_calls_add_whitelist_rule_endpoint_properly(self): + data = {"ip": "1.2.3.4", "type": "database", "description": "my description"} + self.client.add_whitelist_rule(self.app_name, data) + + self.mock_request.assert_called_once_with( + "POST", f"/v2/whitelist/{self.app_name}/", data=data + ) + + def test_returns_add_whitelist_rule_response(self): + response = Mock() + response.status_code = 200 + self.mock_request.return_value = response + + self.assertEqual( + self.client.add_whitelist_rule(self.app_name, {"ip": "1.2.3.4"}), + self.mock_request.return_value, + ) diff --git a/tests/client/test_create_backup.py b/tests/client/test_create_backup.py new file mode 100644 index 0000000..89c4587 --- /dev/null +++ b/tests/client/test_create_backup.py @@ -0,0 +1,35 @@ +from unittest import TestCase +from unittest.mock import Mock + +from hypernode_api_python.client import ( + HYPERNODE_API_BACKUPS_ENDPOINT, + HypernodeAPIPython, +) + + +class TestCreateBackup(TestCase): + def setUp(self): + self.client = HypernodeAPIPython(token="my_token") + self.mock_request = Mock() + self.client.requests = self.mock_request + self.app_name = "my_app" + + def test_backups_endpoint_is_correct(self): + self.assertEqual("/v2/app/{}/backup/", HYPERNODE_API_BACKUPS_ENDPOINT) + + def test_calls_create_backup_endpoint_properly(self): + self.client.create_backup(self.app_name) + + self.mock_request.assert_called_once_with( + "POST", f"/v2/app/{self.app_name}/backup/" + ) + + def test_returns_create_backup_response(self): + response = Mock() + response.status_code = 200 + self.mock_request.return_value = response + + self.assertEqual( + self.client.create_backup(self.app_name), + self.mock_request.return_value, + ) diff --git a/tests/client/test_create_insights_annotation.py b/tests/client/test_create_insights_annotation.py new file mode 100644 index 0000000..702fba2 --- /dev/null +++ b/tests/client/test_create_insights_annotation.py @@ -0,0 +1,43 @@ +from unittest import TestCase +from unittest.mock import Mock + +from hypernode_api_python.client import ( + HYPERNODE_API_INSIGHTS_ANNOTATION_CREATE_ENDPOINT, + HypernodeAPIPython, +) + + +class TestCreateInsightsAnnotation(TestCase): + def setUp(self): + self.client = HypernodeAPIPython(token="my_token") + self.mock_request = Mock() + self.client.requests = self.mock_request + + def test_insights_annotation_create_endpoint_is_correct(self): + self.assertEqual( + "/v2/insights-annotation/create/", + HYPERNODE_API_INSIGHTS_ANNOTATION_CREATE_ENDPOINT, + ) + + def test_calls_create_insights_annotation_endpoint_properly(self): + data = { + "name": "Deployed release 1.2.3", + "x_axis": 1756384957, + "app": "my_app", + "metrics": "memory_usage", + } + self.client.create_insights_annotation(data) + + self.mock_request.assert_called_once_with( + "POST", "/v2/insights-annotation/create/", data=data + ) + + def test_returns_create_insights_annotation_response(self): + response = Mock() + response.status_code = 201 + self.mock_request.return_value = response + + self.assertEqual( + self.client.create_insights_annotation({}), + self.mock_request.return_value, + ) diff --git a/tests/client/test_delete_whitelist_rule.py b/tests/client/test_delete_whitelist_rule.py new file mode 100644 index 0000000..3035498 --- /dev/null +++ b/tests/client/test_delete_whitelist_rule.py @@ -0,0 +1,36 @@ +from unittest import TestCase +from unittest.mock import Mock + +from hypernode_api_python.client import ( + HYPERNODE_API_WHITELIST_ENDPOINT, + HypernodeAPIPython, +) + + +class TestDeleteWhitelistRule(TestCase): + def setUp(self): + self.client = HypernodeAPIPython(token="my_token") + self.mock_request = Mock() + self.client.requests = self.mock_request + self.app_name = "my_app" + + def test_whitelist_endpoint_is_correct(self): + self.assertEqual("/v2/whitelist/{}/", HYPERNODE_API_WHITELIST_ENDPOINT) + + def test_calls_delete_whitelist_rule_endpoint_properly(self): + data = {"ip": "1.2.3.4", "type": "database"} + self.client.delete_whitelist_rule(self.app_name, data) + + self.mock_request.assert_called_once_with( + "DELETE", f"/v2/whitelist/{self.app_name}/", data=data + ) + + def test_returns_delete_whitelist_rule_response(self): + response = Mock() + response.status_code = 204 + self.mock_request.return_value = response + + self.assertEqual( + self.client.delete_whitelist_rule(self.app_name, {"ip": "1.2.3.4"}), + self.mock_request.return_value, + ) diff --git a/tests/client/test_get_insights_annotations.py b/tests/client/test_get_insights_annotations.py new file mode 100644 index 0000000..2b52d16 --- /dev/null +++ b/tests/client/test_get_insights_annotations.py @@ -0,0 +1,45 @@ +from unittest import TestCase +from unittest.mock import Mock + +from hypernode_api_python.client import ( + HYPERNODE_API_INSIGHTS_ANNOTATION_LIST_ENDPOINT, + HypernodeAPIPython, +) + + +class TestGetInsightsAnnotations(TestCase): + def setUp(self): + self.client = HypernodeAPIPython(token="my_token") + self.mock_request = Mock() + self.client.requests = self.mock_request + + def test_insights_annotation_list_endpoint_is_correct(self): + self.assertEqual( + "/v2/insights-annotation/", + HYPERNODE_API_INSIGHTS_ANNOTATION_LIST_ENDPOINT, + ) + + def test_calls_get_insights_annotations_endpoint_properly(self): + self.client.get_insights_annotations() + + self.mock_request.assert_called_once_with( + "GET", "/v2/insights-annotation/", params=None + ) + + def test_calls_get_insights_annotations_endpoint_with_pagination_params(self): + params = {"limit": 10, "offset": 20} + self.client.get_insights_annotations(params=params) + + self.mock_request.assert_called_once_with( + "GET", "/v2/insights-annotation/", params=params + ) + + def test_returns_get_insights_annotations_response(self): + response = Mock() + response.status_code = 200 + self.mock_request.return_value = response + + self.assertEqual( + self.client.get_insights_annotations(), + self.mock_request.return_value, + ) diff --git a/tests/commands/test_add_whitelist_rule.py b/tests/commands/test_add_whitelist_rule.py new file mode 100644 index 0000000..29235c8 --- /dev/null +++ b/tests/commands/test_add_whitelist_rule.py @@ -0,0 +1,54 @@ +from hypernode_api_python.commands import add_whitelist_rule +from tests.testcase import TestCase + + +class TestAddWhitelistRule(TestCase): + def setUp(self): + self.print_response = self.set_up_patch( + "hypernode_api_python.commands.print_response" + ) + self.get_client = self.set_up_patch("hypernode_api_python.commands.get_client") + self.client = self.get_client.return_value + self.get_app_name = self.set_up_patch( + "hypernode_api_python.commands.get_app_name" + ) + self.get_app_name.return_value = "myappname" + + def test_add_whitelist_rule_gets_client(self): + add_whitelist_rule(["1.2.3.4"]) + + self.get_client.assert_called_once_with() + + def test_add_whitelist_rule_gets_app_name(self): + add_whitelist_rule(["1.2.3.4"]) + + self.get_app_name.assert_called_once_with() + + def test_add_whitelist_rule_adds_whitelist_rule_with_default_type(self): + add_whitelist_rule(["1.2.3.4"]) + + expected_data = {"ip": "1.2.3.4", "type": "database", "description": ""} + self.client.add_whitelist_rule.assert_called_once_with( + "myappname", data=expected_data + ) + + def test_add_whitelist_rule_adds_whitelist_rule_with_specified_arguments(self): + add_whitelist_rule( + ["1.2.3.4", "--type", "waf", "--description", "my description"] + ) + + expected_data = { + "ip": "1.2.3.4", + "type": "waf", + "description": "my description", + } + self.client.add_whitelist_rule.assert_called_once_with( + "myappname", data=expected_data + ) + + def test_add_whitelist_rule_prints_response(self): + add_whitelist_rule(["1.2.3.4"]) + + self.print_response.assert_called_once_with( + self.client.add_whitelist_rule.return_value + ) diff --git a/tests/commands/test_create_backup.py b/tests/commands/test_create_backup.py new file mode 100644 index 0000000..7ae49ab --- /dev/null +++ b/tests/commands/test_create_backup.py @@ -0,0 +1,37 @@ +from hypernode_api_python.commands import create_backup +from tests.testcase import TestCase + + +class TestCreateBackup(TestCase): + def setUp(self): + self.print_response = self.set_up_patch( + "hypernode_api_python.commands.print_response" + ) + self.get_client = self.set_up_patch("hypernode_api_python.commands.get_client") + self.client = self.get_client.return_value + self.get_app_name = self.set_up_patch( + "hypernode_api_python.commands.get_app_name" + ) + self.get_app_name.return_value = "myappname" + + def test_create_backup_gets_client(self): + create_backup([]) + + self.get_client.assert_called_once_with() + + def test_create_backup_gets_app_name(self): + create_backup([]) + + self.get_app_name.assert_called_once_with() + + def test_create_backup_creates_backup(self): + create_backup([]) + + self.client.create_backup.assert_called_once_with("myappname") + + def test_create_backup_prints_response(self): + create_backup([]) + + self.print_response.assert_called_once_with( + self.client.create_backup.return_value + ) diff --git a/tests/commands/test_create_insights_annotation.py b/tests/commands/test_create_insights_annotation.py new file mode 100644 index 0000000..5856d45 --- /dev/null +++ b/tests/commands/test_create_insights_annotation.py @@ -0,0 +1,69 @@ +from hypernode_api_python.commands import create_insights_annotation +from tests.testcase import TestCase + + +class TestCreateInsightsAnnotation(TestCase): + def setUp(self): + self.print_response = self.set_up_patch( + "hypernode_api_python.commands.print_response" + ) + self.get_client = self.set_up_patch("hypernode_api_python.commands.get_client") + self.client = self.get_client.return_value + self.get_app_name = self.set_up_patch( + "hypernode_api_python.commands.get_app_name" + ) + self.get_app_name.return_value = "myappname" + + def test_create_insights_annotation_gets_client(self): + create_insights_annotation(["Deployed release 1.2.3", "1756384957"]) + + self.get_client.assert_called_once_with() + + def test_create_insights_annotation_gets_app_name(self): + create_insights_annotation(["Deployed release 1.2.3", "1756384957"]) + + self.get_app_name.assert_called_once_with() + + def test_create_insights_annotation_creates_annotation(self): + create_insights_annotation(["Deployed release 1.2.3", "1756384957"]) + + expected_data = { + "name": "Deployed release 1.2.3", + "x_axis": 1756384957, + "app": "myappname", + } + self.client.create_insights_annotation.assert_called_once_with( + data=expected_data + ) + + def test_create_insights_annotation_creates_annotation_with_optional_arguments( + self, + ): + create_insights_annotation( + [ + "Deployed release 1.2.3", + "1756384957", + "--metrics", + "memory_usage", + "--metadata", + "some metadata", + ] + ) + + expected_data = { + "name": "Deployed release 1.2.3", + "x_axis": 1756384957, + "app": "myappname", + "metrics": "memory_usage", + "metadata": "some metadata", + } + self.client.create_insights_annotation.assert_called_once_with( + data=expected_data + ) + + def test_create_insights_annotation_prints_response(self): + create_insights_annotation(["Deployed release 1.2.3", "1756384957"]) + + self.print_response.assert_called_once_with( + self.client.create_insights_annotation.return_value + ) diff --git a/tests/commands/test_delete_whitelist_rule.py b/tests/commands/test_delete_whitelist_rule.py new file mode 100644 index 0000000..c3d79a5 --- /dev/null +++ b/tests/commands/test_delete_whitelist_rule.py @@ -0,0 +1,59 @@ +from hypernode_api_python.commands import delete_whitelist_rule +from tests.testcase import TestCase + + +class TestDeleteWhitelistRule(TestCase): + def setUp(self): + self.print_response = self.set_up_patch( + "hypernode_api_python.commands.print_response" + ) + self.get_client = self.set_up_patch("hypernode_api_python.commands.get_client") + self.client = self.get_client.return_value + self.get_app_name = self.set_up_patch( + "hypernode_api_python.commands.get_app_name" + ) + self.get_app_name.return_value = "myappname" + + def test_delete_whitelist_rule_gets_client(self): + with self.assertRaises(SystemExit): + delete_whitelist_rule(["1.2.3.4"]) + + self.get_client.assert_called_once_with() + + def test_delete_whitelist_rule_gets_app_name(self): + with self.assertRaises(SystemExit): + delete_whitelist_rule(["1.2.3.4"]) + + self.get_app_name.assert_called_once_with() + + def test_delete_whitelist_rule_deletes_whitelist_rule_with_default_type(self): + with self.assertRaises(SystemExit): + delete_whitelist_rule(["1.2.3.4"]) + + expected_data = {"ip": "1.2.3.4", "type": "database"} + self.client.delete_whitelist_rule.assert_called_once_with( + "myappname", data=expected_data + ) + + def test_delete_whitelist_rule_deletes_whitelist_rule_with_specified_type(self): + with self.assertRaises(SystemExit): + delete_whitelist_rule(["1.2.3.4", "--type", "waf"]) + + expected_data = {"ip": "1.2.3.4", "type": "waf"} + self.client.delete_whitelist_rule.assert_called_once_with( + "myappname", data=expected_data + ) + + def test_delete_whitelist_rule_exits_zero_on_success(self): + with self.assertRaises(SystemExit) as cm: + delete_whitelist_rule(["1.2.3.4"]) + + self.assertEqual(cm.exception.code, 0) + + def test_delete_whitelist_rule_exits_nonzero_when_delete_fails(self): + self.client.delete_whitelist_rule.side_effect = RuntimeError("some error") + + with self.assertRaises(SystemExit) as cm: + delete_whitelist_rule(["1.2.3.4"]) + + self.assertNotEqual(cm.exception.code, 0) diff --git a/tests/commands/test_get_insights_annotations.py b/tests/commands/test_get_insights_annotations.py new file mode 100644 index 0000000..1d9262e --- /dev/null +++ b/tests/commands/test_get_insights_annotations.py @@ -0,0 +1,35 @@ +from hypernode_api_python.commands import get_insights_annotations +from tests.testcase import TestCase + + +class TestGetInsightsAnnotations(TestCase): + def setUp(self): + self.print_response = self.set_up_patch( + "hypernode_api_python.commands.print_response" + ) + self.get_client = self.set_up_patch("hypernode_api_python.commands.get_client") + self.client = self.get_client.return_value + + def test_get_insights_annotations_gets_client(self): + get_insights_annotations([]) + + self.get_client.assert_called_once_with() + + def test_get_insights_annotations_gets_insights_annotations(self): + get_insights_annotations([]) + + self.client.get_insights_annotations.assert_called_once_with(params={}) + + def test_get_insights_annotations_passes_pagination_params(self): + get_insights_annotations(["--limit", "10", "--offset", "20"]) + + self.client.get_insights_annotations.assert_called_once_with( + params={"limit": 10, "offset": 20} + ) + + def test_get_insights_annotations_prints_response(self): + get_insights_annotations([]) + + self.print_response.assert_called_once_with( + self.client.get_insights_annotations.return_value + )