Skip to content

Commit

Permalink
Feature / alert details helper (#350)
Browse files Browse the repository at this point in the history
* initial implementation of helper method

* rm word

* iter > get

* update

* tests

* docstring & force sort_key

* CHANGELOG

* add :func:s

* honor sort_key and sort_dir on query

* get > search in docstring

* update tests, handle `AlertId` sort_key

* docstring updates

* docstrings and use .get() for key lambda
  • Loading branch information
timabrmsn committed Aug 3, 2021
1 parent a38a112 commit a1d5054
Show file tree
Hide file tree
Showing 3 changed files with 297 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ how a consumer would use the library (e.g. adding unit tests, updating documenta

- `RiskIndicator` and `RiskSeverity` filter classes to new `py42.sdk.queries.fileevents.filters.risk_filter` module.

- New method `sdk.alerts.get_all_alert_details()` as a helper to make getting alerts with details easier (combines `sdk.alerts.search_all_pages()` and `sdk.alerts.get_details()`).

## 1.16.1 - 2021-07-20

### Fixed
Expand Down
30 changes: 30 additions & 0 deletions src/py42/clients/alerts.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,33 @@ def get_aggregate_data(self, alert_id):
:class:`py42.response.Py42Response`
"""
return self._alert_service.get_aggregate_data(alert_id)

def get_all_alert_details(self, query):
"""
Helper method that combines :func:`.search_all_pages()` and :func:`.get_details()`
methods to get alert objects with alert "observations" details populated.
Returns an iterator of alert detail objects.
Note: automatically overrides the `page_size` property on the query object to limit
search to 100 results per page, as that is the max that :func:`.get_details()` can
request at a time.
Args:
query (:class:`py42.sdk.queries.alerts.alert_query.AlertQuery`): An alert query.
Returns:
generator: An object that iterates over alert detail items.
"""
query.page_size = 100
sort_key = query.sort_key[0].lower() + query.sort_key[1:]
if sort_key == "alertId":
sort_key = "id"
reverse = query.sort_direction == "desc"
pages = self._alert_service.search_all_pages(query)
for page in pages:
alert_ids = [alert["id"] for alert in page["alerts"]]
alert_details = self._alert_service.get_details(alert_ids)
for alert in sorted(
alert_details["alerts"], key=lambda x: x.get(sort_key), reverse=reverse,
):
yield alert
266 changes: 265 additions & 1 deletion tests/clients/test_alerts.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,141 @@
import json

import pytest

from .conftest import create_mock_response
from py42.clients.alerts import AlertsClient
from py42.sdk.queries.alerts.alert_query import AlertQuery
from py42.services.alertrules import AlertRulesService
from py42.services.alerts import AlertService

ALERT_A = {
"id": "A",
"createdAt": "2021-01-01T09:40:05.2837100Z",
"actor": "A",
"type": "A",
"name": "A",
"description": "A",
"actorId": "A",
"target": "A",
"severity": "A",
"ruleSource": "A",
"observations": "A",
"notes": "A",
"state": "A",
"stateLastModifiedAt": "2021-01-01T09:40:05.2837100Z",
"stateLastModifiedBy": "A",
"lastModifiedTime": "2021-01-01T09:40:05.2837100Z",
"lastModifiedBy": "A",
"ruleId": "A",
"tenantId": "A",
}
ALERT_B = {
"id": "B",
"createdAt": "2021-02-02T18:24:15.5284760Z",
"actor": "B",
"type": "B",
"name": "B",
"description": "B",
"actorId": "B",
"target": "B",
"severity": "B",
"ruleSource": "B",
"observations": "B",
"notes": "B",
"state": "B",
"stateLastModifiedAt": "2021-02-02T18:24:15.5284760Z",
"stateLastModifiedBy": "B",
"lastModifiedTime": "2021-02-02T18:24:15.5284760Z",
"lastModifiedBy": "B",
"ruleId": "B",
"tenantId": "B",
}
ALERT_C = {
"id": "C",
"createdAt": "2021-03-03T09:40:26.6477830Z",
"actor": "C",
"type": "C",
"name": "C",
"description": "C",
"actorId": "C",
"target": "C",
"severity": "C",
"ruleSource": "C",
"observations": "C",
"notes": "C",
"state": "C",
"stateLastModifiedAt": "2021-03-03T09:40:26.6477830Z",
"stateLastModifiedBy": "C",
"lastModifiedTime": "2021-03-03T09:40:26.6477830Z",
"lastModifiedBy": "C",
"ruleId": "C",
"tenantId": "C",
}
ALERT_D = {
"id": "D",
"createdAt": "2021-04-04T09:40:50.7749710Z",
"actor": "D",
"type": "D",
"name": "D",
"description": "D",
"actorId": "D",
"target": "D",
"severity": "D",
"ruleSource": "D",
"observations": "D",
"notes": "D",
"state": "D",
"stateLastModifiedAt": "2021-04-04T09:40:50.7749710Z",
"stateLastModifiedBy": "D",
"lastModifiedTime": "2021-04-04T09:40:50.7749710Z",
"lastModifiedBy": "D",
"ruleId": "D",
"tenantId": "D",
}
ALERT_E = {
"id": "E",
"createdAt": "2021-05-05T21:29:34.5510380Z",
"actor": "E",
"type": "E",
"name": "E",
"description": "E",
"actorId": "E",
"target": "E",
"severity": "E",
"ruleSource": "E",
"observations": "E",
"notes": "E",
"state": "E",
"stateLastModifiedAt": "2021-05-05T21:29:34.5510380Z",
"stateLastModifiedBy": "E",
"lastModifiedTime": "2021-05-05T21:29:34.5510380Z",
"lastModifiedBy": "E",
"ruleId": "E",
"tenantId": "E",
}
ALERT_F = {
"id": "F",
"createdAt": "2021-06-06T21:24:50.7541390Z",
"actor": "F",
"type": "F",
"name": "F",
"description": "F",
"actorId": "F",
"target": "F",
"severity": "F",
"ruleSource": "F",
"observations": "F",
"notes": "F",
"state": "F",
"stateLastModifiedAt": "2021-06-06T21:24:50.7541390Z",
"stateLastModifiedBy": "F",
"lastModifiedTime": "2021-06-06T21:24:50.7541390Z",
"lastModifiedBy": "F",
"ruleId": "F",
"tenantId": "F",
}
TEST_ALERTS = [ALERT_A, ALERT_B, ALERT_C, ALERT_D, ALERT_E, ALERT_F]


@pytest.fixture
def mock_alerts_service(mocker):
Expand All @@ -21,6 +152,41 @@ def mock_alert_query(mocker):
return mocker.MagicMock(spec=AlertQuery)


@pytest.fixture
def mock_alerts_service_with_pages(mocker, mock_alerts_service):
def _func(ascending=True):
alerts = TEST_ALERTS if ascending else TEST_ALERTS[::-1]
alert_page_1 = create_mock_response(mocker, json.dumps({"alerts": alerts[:3]}))
alert_page_2 = create_mock_response(mocker, json.dumps({"alerts": alerts[3:]}))

def page_gen():
yield alert_page_1
yield alert_page_2

mock_alerts_service.search_all_pages.return_value = page_gen()
return mock_alerts_service

return _func


@pytest.fixture
def mock_details(mocker):
detail_page_1 = create_mock_response(
mocker, json.dumps({"alerts": [ALERT_B, ALERT_C, ALERT_A]})
)
detail_page_2 = create_mock_response(
mocker, json.dumps({"alerts": [ALERT_F, ALERT_E, ALERT_D]})
)

def mock_get_details(alert_ids):
if set(alert_ids) == {"A", "B", "C"}:
return detail_page_1
if set(alert_ids) == {"D", "E", "F"}:
return detail_page_2

return mock_get_details


class TestAlertsClient(object):
_alert_ids = [u"test-id1", u"test-id2"]

Expand Down Expand Up @@ -82,7 +248,7 @@ def test_alerts_client_calls_search_all_pages_with_expected_value_and_param(
self, mock_alerts_service, mock_alert_rules_service,
):
alert_client = AlertsClient(mock_alerts_service, mock_alert_rules_service)
query = '{"test": "data"}}'
query = AlertQuery()
alert_client.search_all_pages(query)
mock_alerts_service.search_all_pages.assert_called_once_with(query)

Expand All @@ -92,3 +258,101 @@ def test_alerts_client_calls_get_aggregate_data_with_expected_value_and_param(
alert_client = AlertsClient(mock_alerts_service, mock_alert_rules_service)
alert_client.get_aggregate_data("alert-id")
mock_alerts_service.get_aggregate_data.assert_called_once_with("alert-id")

def test_alerts_client_get_all_alert_details_calls_get_details_for_each_page(
self, mock_alerts_service_with_pages, mock_alert_rules_service
):
mock_alerts_service = mock_alerts_service_with_pages(ascending=True)
alert_client = AlertsClient(mock_alerts_service, mock_alert_rules_service)
query = AlertQuery()
list(alert_client.get_all_alert_details(query))
assert mock_alerts_service.get_details.call_args_list[0][0][0] == [
"A",
"B",
"C",
]
assert mock_alerts_service.get_details.call_args_list[1][0][0] == [
"D",
"E",
"F",
]

@pytest.mark.parametrize(
"sort_key",
[
"AlertId",
"TenantId",
"Type",
"Name",
"Description",
"Actor",
"ActorId",
"Target",
"Severity",
"RuleSource",
"CreatedAt",
"Observations",
"Notes",
"State",
"StateLastModifiedAt",
"StateLastModifiedBy",
"LastModifiedTime",
"LastModifiedBy",
"RuleId",
],
)
def test_alerts_client_get_all_alert_details_sorts_results_according_to_query_sort_key(
self,
mock_alerts_service_with_pages,
mock_alert_rules_service,
mock_details,
sort_key,
):
mock_alerts_service = mock_alerts_service_with_pages(ascending=True)
mock_alerts_service.get_details = mock_details
alert_client = AlertsClient(mock_alerts_service, mock_alert_rules_service)
query = AlertQuery()
query.sort_direction = "asc"
query.sort_key = sort_key
results = list(alert_client.get_all_alert_details(query))
assert results == [ALERT_A, ALERT_B, ALERT_C, ALERT_D, ALERT_E, ALERT_F]

@pytest.mark.parametrize(
"sort_key",
[
"AlertId",
"TenantId",
"Type",
"Name",
"Description",
"Actor",
"ActorId",
"Target",
"Severity",
"RuleSource",
"CreatedAt",
"Observations",
"Notes",
"State",
"StateLastModifiedAt",
"StateLastModifiedBy",
"LastModifiedTime",
"LastModifiedBy",
"RuleId",
],
)
def test_alerts_client_get_all_alert_details_sorts_results_descending_when_specified(
self,
mock_alerts_service_with_pages,
mock_alert_rules_service,
mock_details,
sort_key,
):
mock_alerts_service = mock_alerts_service_with_pages(ascending=False)
mock_alerts_service.get_details = mock_details
alert_client = AlertsClient(mock_alerts_service, mock_alert_rules_service)
query = AlertQuery()
query.sort_direction = "desc"
query.sort_key = sort_key
results = list(alert_client.get_all_alert_details(query))
assert results == [ALERT_F, ALERT_E, ALERT_D, ALERT_C, ALERT_B, ALERT_A]

0 comments on commit a1d5054

Please sign in to comment.