Skip to content

Commit 7e6b389

Browse files
author
João Silva
committed
!33 feat(target commands): [#73] Bulk target actions only for multiple targets Closes #73
1 parent b7267fc commit 7e6b389

8 files changed

Lines changed: 151 additions & 22 deletions

File tree

probely_cli/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
list_targets,
44
retrieve_target,
55
retrieve_targets,
6+
delete_target,
67
delete_targets,
78
add_target,
89
update_target,
@@ -16,7 +17,7 @@
1617
"list_targets",
1718
"retrieve_target",
1819
"retrieve_targets",
19-
# "delete_target",
20+
"delete_target",
2021
"delete_targets",
2122
"update_target",
2223
"update_targets",

probely_cli/cli/commands/targets/delete.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
target_filters_handler,
33
)
44
from probely_cli.exceptions import ProbelyCLIValidation
5-
from probely_cli.sdk.targets import delete_targets, list_targets
5+
from probely_cli.sdk.targets import delete_targets, list_targets, delete_target
66

77

88
def targets_delete_command_handler(args):
@@ -18,10 +18,16 @@ def targets_delete_command_handler(args):
1818
if filters and targets_ids:
1919
raise ProbelyCLIValidation("filters and Target IDs are mutually exclusive.")
2020

21-
if not targets_ids:
21+
if filters:
2222
targets_list = list_targets(targets_filters=filters)
2323
targets_ids = [target.get("id") for target in targets_list]
2424

25+
if len(targets_ids) == 1:
26+
target_id = delete_target(targets_ids[0])
27+
args.console.print(target_id)
28+
return
29+
2530
targets = delete_targets(targets_ids=targets_ids)
31+
2632
for ids in targets.get("ids"):
2733
args.console.print(ids)

probely_cli/cli/commands/targets/parsers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ def build_targets_parser(commands_parser, configs_parser, file_parser, output_pa
9797

9898
targets_delete_parser = targets_command_parser.add_parser(
9999
"delete",
100-
parents=[configs_parser, target_filters_parser, output_parser],
100+
parents=[configs_parser, target_filters_parser],
101101
formatter_class=RichHelpFormatter,
102102
)
103103
targets_delete_parser.add_argument(

probely_cli/sdk/client.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ def post(cls, url, query_params: dict = None, payload: dict = None):
7676
def patch(cls, url, payload: dict = None):
7777
return cls._send_request("patch", url, payload=payload)
7878

79+
def delete(self, url):
80+
return self._send_request("delete", url)
81+
7982
@classmethod
8083
def _send_request(
8184
cls, method: str, url: str, payload: dict = None, query_params: dict = None

probely_cli/sdk/targets.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
PROBELY_API_TARGETS_BULK_UPDATE_URL,
2020
PROBELY_API_TARGETS_URL,
2121
PROBELY_API_TARGETS_RETRIEVE_URL,
22+
PROBELY_API_TARGETS_DELETE_URL,
2223
)
2324

2425
logger = logging.getLogger(__name__)
@@ -44,6 +45,20 @@ def retrieve_target(target_id: str) -> dict:
4445
return resp_content
4546

4647

48+
def delete_target(target_id: str) -> str:
49+
url = PROBELY_API_TARGETS_DELETE_URL.format(id=target_id)
50+
51+
resp_status_code, resp_content = ProbelyAPIClient().delete(url=url)
52+
53+
if resp_status_code == 404:
54+
raise ProbelyObjectNotFound(id=target_id)
55+
56+
if resp_status_code != 204:
57+
raise ProbelyRequestFailed(resp_content)
58+
59+
return target_id
60+
61+
4762
def delete_targets(targets_ids: List[str]):
4863
"""Delete targets
4964

probely_cli/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ def _get_config(
9595
# URLs
9696
PROBELY_API_TARGETS_URL = PROBELY_API_URL_BASE + "targets/"
9797
PROBELY_API_TARGETS_RETRIEVE_URL = PROBELY_API_TARGETS_URL + "{id}/"
98+
PROBELY_API_TARGETS_DELETE_URL = PROBELY_API_TARGETS_URL + "{id}/"
9899
PROBELY_API_TARGETS_BULK_DELETE_URL = PROBELY_API_TARGETS_URL + "bulk/delete/"
99100
PROBELY_API_TARGETS_BULK_UPDATE_URL = PROBELY_API_TARGETS_URL + "bulk/update/"
100101

tests/cli/test_targets_delete.py

Lines changed: 87 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
from unittest.mock import patch, Mock
1+
from unittest.mock import patch, Mock, MagicMock
22

33

4+
@patch("probely_cli.cli.commands.targets.delete.delete_target")
45
@patch("probely_cli.cli.commands.targets.delete.delete_targets")
56
@patch("probely_cli.cli.commands.targets.delete.list_targets")
6-
def test_targets_delete_target(
7-
list_targets_mock: Mock, delete_targets_mock: Mock, probely_cli
7+
def test_targets_delete__multiple_targets(
8+
list_targets_mock: Mock,
9+
delete_targets_mock: Mock,
10+
delete_target_mock: MagicMock,
11+
probely_cli,
812
):
913
target_id1 = "target_id1"
1014
target_id2 = "target_id2"
@@ -18,22 +22,87 @@ def test_targets_delete_target(
1822
target_id2,
1923
return_list=True,
2024
)
21-
list_targets_mock.assert_not_called()
25+
26+
assert delete_target_mock.call_count == 0, "Expected bulk call for multiple targets"
27+
assert list_targets_mock.call_count == 0, "Expected no search for multiple IDs"
28+
2229
delete_targets_mock.assert_called_once_with(targets_ids=[target_id1, target_id2])
23-
assert len(stderr_lines) == 0
30+
assert stderr_lines == []
2431
assert len(stdout_lines) == 2, "Expected to have 2 line with the deleted target id"
32+
assert target_id1 == stdout_lines[0]
2533
assert target_id2 == stdout_lines[1]
2634

2735

36+
@patch("probely_cli.cli.commands.targets.delete.delete_targets")
37+
@patch("probely_cli.cli.commands.targets.delete.delete_target")
38+
@patch("probely_cli.cli.commands.targets.delete.list_targets")
39+
def test_targets_delete__one_target(
40+
list_targets_mock: Mock,
41+
delete_target_mock: MagicMock,
42+
delete_targets_mock: MagicMock,
43+
probely_cli,
44+
):
45+
testable_target_id = "testable_target_id"
46+
delete_target_mock.return_value = testable_target_id
47+
48+
stdout_lines, stderr_lines = probely_cli(
49+
"targets", "delete", testable_target_id, return_list=True
50+
)
51+
52+
assert delete_targets_mock.call_count == 0, "Expected bulk delete to NOT called"
53+
assert list_targets_mock.call_count == 0, "Expected no search for ID"
54+
55+
assert delete_target_mock.call_count == 1, "Expected usage of single delete"
56+
57+
assert stderr_lines == []
58+
assert stdout_lines[-1] == testable_target_id
59+
60+
61+
@patch("probely_cli.cli.commands.targets.delete.delete_target")
2862
@patch("probely_cli.cli.commands.targets.delete.delete_targets")
2963
@patch("probely_cli.cli.commands.targets.delete.list_targets")
30-
def test_targets_delete_target_with_filters(
31-
list_targets_mock: Mock, delete_targets_mock: Mock, probely_cli
64+
def test_targets_delete__filters_with_multiple_results(
65+
list_targets_mock: Mock,
66+
delete_targets_mock: Mock,
67+
delete_target_mock: Mock,
68+
probely_cli,
69+
):
70+
target_id1 = "target_id1"
71+
target_id2 = "target_id2"
72+
list_targets_mock.return_value = [{"id": target_id1}, {"id": target_id2}]
73+
74+
delete_targets_mock.return_value = {"ids": [target_id1, target_id2]}
75+
76+
stdout_lines, stderr_lines = probely_cli(
77+
"targets",
78+
"delete",
79+
"--f-has-unlimited-scans=True",
80+
return_list=True,
81+
)
82+
83+
assert delete_target_mock.call_count == 0, "Expect single delete method not called"
84+
85+
list_targets_mock.assert_called_with(targets_filters={"unlimited": True})
86+
delete_targets_mock.assert_called_once_with(targets_ids=[target_id1, target_id2])
87+
assert stderr_lines == []
88+
assert len(stdout_lines) == 2, "Expected to have 1 line with the deleted target id"
89+
assert target_id1 == stdout_lines[0]
90+
assert target_id2 == stdout_lines[1]
91+
92+
93+
@patch("probely_cli.cli.commands.targets.delete.delete_target")
94+
@patch("probely_cli.cli.commands.targets.delete.delete_targets")
95+
@patch("probely_cli.cli.commands.targets.delete.list_targets")
96+
def test_targets_delete__filters_with_single_result(
97+
list_targets_mock: Mock,
98+
delete_targets_mock: Mock,
99+
delete_target_mock: Mock,
100+
probely_cli,
32101
):
33102
target_id1 = "target_id1"
34103
list_targets_mock.return_value = [{"id": target_id1}]
35104

36-
delete_targets_mock.return_value = {"ids": [target_id1]}
105+
delete_target_mock.return_value = target_id1
37106

38107
stdout_lines, stderr_lines = probely_cli(
39108
"targets",
@@ -42,8 +111,10 @@ def test_targets_delete_target_with_filters(
42111
return_list=True,
43112
)
44113

114+
assert delete_targets_mock.call_count == 0, "Expect single delete method not called"
115+
45116
list_targets_mock.assert_called_with(targets_filters={"unlimited": True})
46-
delete_targets_mock.assert_called_once_with(targets_ids=[target_id1])
117+
delete_target_mock.assert_called_once_with(target_id1)
47118
assert len(stderr_lines) == 0
48119
assert len(stdout_lines) == 1, "Expected to have 1 line with the deleted target id"
49120
assert target_id1 == stdout_lines[0]
@@ -66,8 +137,13 @@ def test_targets_delete__mutually_exclusive_arguments(probely_cli):
66137
)
67138

68139

140+
@patch("probely_cli.cli.commands.targets.delete.delete_target")
69141
@patch("probely_cli.cli.commands.targets.delete.delete_targets")
70-
def test_targets_delete__without_any_argument(delete_targets_mock: Mock, probely_cli):
142+
def test_targets_delete__without_any_argument(
143+
delete_targets_mock: Mock,
144+
delete_target_mock: Mock,
145+
probely_cli,
146+
):
71147
stdout_lines, stderr_lines = probely_cli(
72148
"targets",
73149
"delete",
@@ -81,3 +157,4 @@ def test_targets_delete__without_any_argument(delete_targets_mock: Mock, probely
81157
)
82158

83159
delete_targets_mock.assert_not_called()
160+
delete_target_mock.assert_not_called()

tests/sdk/test_targets.py

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717
retrieve_targets,
1818
update_target,
1919
add_target,
20+
delete_target,
2021
)
2122
from probely_cli.settings import (
2223
PROBELY_API_TARGETS_BULK_DELETE_URL,
2324
PROBELY_API_TARGETS_BULK_UPDATE_URL,
2425
PROBELY_API_TARGETS_RETRIEVE_URL,
26+
PROBELY_API_TARGETS_DELETE_URL,
2527
)
2628
from tests.testable_api_responses import RETRIEVE_TARGET_200_RESPONSE
2729

@@ -84,27 +86,51 @@ def test_retrieve_target__success_api_call(api_client_mock: Mock):
8486
api_client_mock.assert_called_with(expected_call_url)
8587

8688

87-
@patch("probely_cli.sdk.client.ProbelyAPIClient.post")
89+
@patch("probely_cli.sdk.client.ProbelyAPIClient.delete")
8890
def test_delete_target__success_api_call(api_client_mock: Mock):
89-
resp_code = 200
91+
resp_code = 204
9092
testable_id = "2DZkoZH8WMEM"
9193
resp_content = {"id": testable_id}
9294

9395
api_client_mock.return_value = (resp_code, resp_content)
9496

95-
delete_targets([testable_id])
97+
delete_target(testable_id)
9698

97-
expected_call_url = PROBELY_API_TARGETS_BULK_DELETE_URL
98-
api_client_mock.assert_called_with(
99-
url=expected_call_url, payload={"ids": [testable_id]}
100-
)
99+
expected_call_url = PROBELY_API_TARGETS_DELETE_URL.format(id=testable_id)
101100

101+
api_client_mock.assert_called_with(url=expected_call_url)
102102

103-
@patch("probely_cli.sdk.client.ProbelyAPIClient.post")
103+
104+
@patch("probely_cli.sdk.client.ProbelyAPIClient.delete")
104105
def test_delete_target__unsuccessful_api_call(api_client_mock: Mock):
105106
resp_code = 400
106107
testable_id = "2DZkoZH8WMEM"
107108

109+
api_client_mock.return_value = (resp_code, {})
110+
with pytest.raises(BaseException) as exc_info:
111+
delete_target([testable_id])
112+
113+
raised_exception = exc_info.value
114+
assert isinstance(raised_exception, ProbelyRequestFailed)
115+
116+
api_client_mock.reset_mock()
117+
118+
not_found_id = "random_target_id"
119+
api_client_mock.return_value = (404, {})
120+
121+
with pytest.raises(BaseException) as exc_info:
122+
delete_target(not_found_id)
123+
124+
raised_exception = exc_info.value
125+
assert isinstance(raised_exception, ProbelyObjectNotFound)
126+
assert raised_exception.not_found_object_id == not_found_id
127+
128+
129+
@patch("probely_cli.sdk.client.ProbelyAPIClient.post")
130+
def test_delete_targets__unsuccessful_api_call(api_client_mock: Mock):
131+
resp_code = 400
132+
testable_id = "2DZkoZH8WMEM"
133+
108134
api_client_mock.return_value = (resp_code, {})
109135
with pytest.raises(BaseException) as exc_info:
110136
delete_targets([testable_id])

0 commit comments

Comments
 (0)