Ironscales Event Collector
Use this integration to fetch email security incidents from Ironscales as XSIAM events.
Email · Ironscales Event Collector
Details
| ID | Ironscales Event Collector |
|---|---|
| Provider | Ironscales |
| Category | |
| From Version | 8.2.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | XSIAM |
README
Use this integration to fetch email security incidents from Ironscales as XSIAM events.
Configure Ironscales Event Collector in Cortex
| Parameter | Description | Required |
|---|---|---|
| Server URL (e.g., https://appapi.ironscales.com) | True | |
| API Key | True | |
| Company ID | True | |
| Scopes (e.g., “company.all”) | True | |
| Maximum number of events per fetch | False | |
| First fetch | False | |
| Collect all events | Fetch all events instead of only open events. | False |
| Fetch mailbox mitigation details | Whether to fetch the affected mailbox mitigation details as part of the event data. | False |
| Trust any certificate (not secure) | False | |
| Use system proxy settings | False |
Scopes
partner.all
company.all
partner.company.view
company.view
Collect All Events
Checking this box will fetch all events, not just open ones.
Note: Currently, ATO and MTS event types are not supported.
Commands
You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
ironscales-get-events
Gets events from Ironscales. This command is intended for development and debugging purposes and is to be used with caution as it may create duplicate events, exceed API request rate limits, and disrupt the fetch events mechanism.
Base Command
ironscales-get-events
Input
| Argument Name | Description | Required |
|---|---|---|
| limit | The number of events to return. Default is 10. | Optional |
| since_time | The start time by which to filter events. Date format will be the same as in the first_fetch parameter. Default is 3 days. | Optional |
| mailbox_enrichment | Set this argument to True in order to include the affected mailboxes mitigation details in the event data. Possible values are: true, false. Default is false. | Optional |
| should_push_events | Set this argument to True in order to create events, otherwise the command will only display them. Possible values are: true, false. Default is false. | Required |
Context Output
There is no context output for this command.
Configuration parameters
url— Server URL (e.g., https://appapi.ironscales.com) (required)apikey— (required)company_id— Company ID (required)scopes— Scopes (e.g., "company.all") (required)max_fetch— Maximum number of events per fetchfirst_fetch— First fetchcollect_all_events— Collect all eventsmailbox_enrichment— Fetch mailbox mitigation detailsinsecure— Trust any certificate (not secure)proxy— Use system proxy settings
Commands (1)
-
ironscales-get-eventsGets events from Ironscales. This command is intended for development and debugging purposes and is to be used with caution as it may create duplicate events, exceed API request rate limits, and disrupt the fetch events mechanism.
import demistomock as demisto import pytest from pytest_mock import MockerFixture from datetime import datetime, timedelta, UTC from IronscalesEventCollector import ( DATEPARSER_SETTINGS, Client, arg_to_datetime, enrich_events_with_mailbox_details, fetch_events_command, get_events_command, incident_to_events, main, ) @pytest.fixture def client(mocker): def mock_get_incident(inc_id): return { "incident_id": inc_id, "first_reported_date": f"{4 - inc_id} days ago", "reports": [ { "name": "first_name", } ], } mocked_client = mocker.Mock() mocked_client.all_incident = False mocked_client.get_incident_ids.return_value = [0, 1, 3, 4] mocked_client.get_incident.side_effect = mock_get_incident return mocked_client def test_fetch_events_by_fetch_time(client: Client): """ Given: A mock Ironscales client. When: Running fetch-events, where `max_fetch` param is 1 and `first_fetch` param is "2 days ago". Then: Ensure only the first event that occured up to 2 days ago is returned. """ events, last_id, _ = fetch_events_command( client, first_fetch=arg_to_datetime("2 days ago", settings=DATEPARSER_SETTINGS), # type: ignore max_fetch=1, ) assert len(events) == 1 assert events[0]["incident_id"] == 3 assert last_id == 3 def test_fetch_events_by_last_id(client: Client): """ Given: A mock Ironscales client. When: Running fetch-events, where `max_fetch` param is 10 and the last fetched incident id is 1. Then: Ensure incidents 3 and 4 are returned as events. """ res, last_run, _ = fetch_events_command( client, first_fetch=arg_to_datetime("2 days ago", settings=DATEPARSER_SETTINGS), # type: ignore max_fetch=10, last_id=1, ) assert res[0]["incident_id"] == 3 assert res[-1]["incident_id"] == 4 def test_get_events(client: Client): """ Given: A mock Ironscales client. When: Running get-events with a limit of 1, while there are four open incidents. Then: Ensure only one event is returned. """ _, events = get_events_command(client, {"limit": 1}) assert len(events) == 1 assert events[0]["incident_id"] == 0 def test_incident_to_events(): """ Given: A mock Ironscales incident data that aggregates two reports. When: Calling incident_to_events(). Then: Ensure there are two events returned, where each of them consists of the incident data and a single report data. """ dummy_incident = { "incident_id": 1, "first_reported_date": "2023-05-11T11:39:53.104571.0Z", "reports": [ { "name": "dummy name 1", "email": "test@paloaltonetworks.com", "headers": [ {"name": "header1", "value": "value1"}, ], }, { "name": "dummy name 2", "email": "test2@paloaltonetworks.com", "headers": [ {"name": "header2", "value": "value2"}, ], }, ], "links": [ {"url": "http://www.ironscales.com/", "name": "tests"}, ], "attachments": [{"file_name": "dummy file", "file_size": 1024, "md5": "a36544c75d1253d8dd32070908adebd0"}], } events = incident_to_events(dummy_incident) assert len(events) == 2 assert "reports" not in events[0] assert "reports" not in events[1] assert events[0]["incident_id"] == events[1]["incident_id"] assert events[0]["links"] == events[1]["links"] assert events[0]["attachments"] == events[1]["attachments"] assert events[0]["headers"][0]["name"] == "header1" assert events[1]["headers"][0]["name"] == "header2" assert events[0]["headers"][0]["value"] == "value1" assert events[1]["headers"][0]["value"] == "value2" @pytest.mark.parametrize( "params, is_valid, result_msg", [ ({"max_fetch": "1", "first_fetch": "", "url": ""}, True, "ok"), ({"max_fetch": "not a number", "first_fetch": "3 days", "url": ""}, False, '"not a number" is not a valid number'), ({"max_fetch": "1", "first_fetch": "not a date", "url": ""}, False, '"not a date" is not a valid date'), ], ) def test_test_module(mocker: MockerFixture, params: dict, is_valid: bool, result_msg: str): """ Given: different assignments for integration parameters. When: Running test-module command. Then: Make sure the correct message is returned. """ mocker.patch.object(Client, "get_jwt_token", return_value="mock_token") mocker.patch.object(Client, "get_incident_ids", return_value=[]) mocker.patch.object(Client, "get_incident") mocker.patch.object(demisto, "command", return_value="test-module") mocker.patch.object(demisto, "params", return_value=params) demisto_result = mocker.patch.object(demisto, "results") return_error = mocker.patch("IronscalesEventCollector.return_error") main() result = (demisto_result if is_valid else return_error).call_args[0][0] assert result_msg in result ###### get all incidents tests ####### def test_no_incidents(mocker: MockerFixture): """ Given: No incidents to pull When: pulling incidents Then: return empty list """ mocker.patch.object(Client, "get_jwt_token", return_value="mocked_jwt") client = Client( company_id="1", base_url="test_url", verify_certificate=True, proxy=False, api_key="test", scopes=[""], all_incident=True, ) mocker.patch.object(client, "_http_request", return_value={"total_pages": 1, "incidents": []}) start_time = datetime.now(UTC) - timedelta(days=1) result = client.get_all_incident_ids(start_time) assert result == [] def test_single_page_with_incidents(mocker: MockerFixture): """ Given: Single page to pull incidents from When: running get_all_incidents Then: return the incidents on this page """ mocker.patch.object(Client, "get_jwt_token", return_value="mocked_jwt") client = Client( company_id="1", base_url="test_url", verify_certificate=True, proxy=False, api_key="test", scopes=[""], all_incident=True, ) mocker.patch.object( client, "_http_request", return_value={ "total_pages": 1, "incidents": [ {"incidentID": 1, "created": "2025-07-04T10:39:05.260833Z"}, {"incidentID": 2, "created": "2025-07-04T10:39:05.260833Z"}, ], }, ) start_time = datetime.now(UTC) - timedelta(days=1) result = client.get_all_incident_ids(start_time) assert result == [1, 2] def test_multiple_pages(mocker: MockerFixture): """ Given: Multiple page to pull incidents from When: running get_all_incidents Then: return the incidents from all of the pages """ mocker.patch.object(Client, "get_jwt_token", return_value="mocked_jwt") client = Client( company_id="1", base_url="test_url", verify_certificate=True, proxy=False, api_key="test", scopes=[""], all_incident=True, ) responses = [ {"total_pages": 2, "incidents": [{"incidentID": 1, "created": "2019-08-24T14:15:22.0Z"}]}, {"total_pages": 2, "incidents": [{"incidentID": 2, "created": "2019-08-24T14:15:22.0Z"}]}, {"total_pages": 2, "incidents": []}, ] mocker.patch.object(client, "_http_request", side_effect=responses) start_time = datetime.now(UTC) - timedelta(days=1) result = client.get_all_incident_ids(start_time) assert result == [1, 2] def test_respects_max_fetch(mocker: MockerFixture): """ Given: More incidents then max_fetch When: pulling all incidents Then: stop at max_fetch """ mocker.patch.object(Client, "get_jwt_token", return_value="mocked_jwt") client = Client( company_id="1", base_url="test_url", verify_certificate=True, proxy=False, api_key="test", scopes=[""], all_incident=True, ) def ids_generator(): i = 1 while i < 100: yield { "total_pages": 50, "incidents": [ {"incidentID": i, "created": "2019-08-24T14:15:22.0Z"}, {"incidentID": i + 1, "created": "2019-08-24T14:15:22.0Z"}, ], } i += 2 mocker.patch.object(client, "_http_request", side_effect=ids_generator()) mocker.patch.object( client, "get_incident", side_effect=lambda x: {"incident_id": x, "first_reported_date": f"{str(2019+x)}-08-24T14:15:22.0Z"}, ) mocker.patch("IronscalesEventCollector.incident_to_events", side_effect=lambda x: [x]) res, last_run, last_timestamp_ids = fetch_events_command( client, first_fetch=arg_to_datetime("2 days ago", settings=DATEPARSER_SETTINGS), # type: ignore max_fetch=10, ) assert len(res) == 10 assert res[0].get("incident_id") == 1 assert last_run == -1 assert last_timestamp_ids == [10] def test_all_incidents_last_id(mocker: MockerFixture): """ Given: A page where we already seen some of the incidents in it When: running fetch_events_command with all_incidents = True and we already pulled some incidents Then: pull only new incidents """ mocker.patch.object(Client, "get_jwt_token", return_value="mocked_jwt") client = Client( company_id="1", base_url="test_url", verify_certificate=True, proxy=False, api_key="test", scopes=[""], all_incident=True, ) responses = [ { "total_pages": 2, "incidents": [ {"incidentID": 1, "created": "2019-08-24T14:15:22.0Z"}, {"incidentID": 2, "created": "2019-08-24T14:15:22.0Z"}, ], }, { "total_pages": 2, "incidents": [ {"incidentID": 3, "created": "2019-08-24T14:15:22.0Z"}, {"incidentID": 4, "created": "2019-08-24T14:15:22.0Z"}, ], }, {"total_pages": 2, "incidents": []}, ] mocker.patch.object(client, "_http_request", side_effect=responses) mocker.patch.object( client, "get_incident", side_effect=lambda x: {"incident_id": x, "first_reported_date": f"{str(2019+x)}-08-24T14:15:22.0Z"}, ) mocker.patch("IronscalesEventCollector.incident_to_events", side_effect=lambda x: [x]) res, _, _ = fetch_events_command( client, first_fetch=arg_to_datetime("2 days ago", settings=DATEPARSER_SETTINGS), # type: ignore max_fetch=10, last_id=1, last_timestamp_ids=[1], ) assert len(res) == 3 assert res[0].get("incident_id") == 2 def test_all_incidents_last_id_complex(mocker: MockerFixture): """ Tests that the fetch_events_command function correctly handles pagination and incident deduplication when fetching all incidents. Given: - A scenario where the current page contains some incidents that were already fetched (their IDs are included in last_timestamp_ids). When: - Running fetch_events_command with `all_incidents=True` - A `last_id` is provided to indicate the last seen incident - `max_fetch=10` is set to limit the number of new incidents to fetch Then: - Previously fetched incidents (based on ID) are ignored - Only new incidents are returned - The number of fetched incidents does not exceed max_fetch """ mocker.patch.object(Client, "get_jwt_token", return_value="mocked_jwt") client = Client( company_id="1", base_url="test_url", verify_certificate=True, proxy=False, api_key="test", scopes=[""], all_incident=True, ) def ids_generator(): i = 1 while i < 100: yield { "total_pages": 50, "incidents": [ {"incidentID": i, "created": "2019-08-24T14:15:22.0Z"}, {"incidentID": i + 1, "created": "2019-08-24T14:15:22.0Z"}, ], } i += 2 mocker.patch.object(client, "_http_request", side_effect=ids_generator()) mocker.patch.object( client, "get_incident", side_effect=lambda x: {"incident_id": x, "first_reported_date": f"{str(2019+x)}-08-24T14:15:22.0Z"}, ) mocker.patch("IronscalesEventCollector.incident_to_events", side_effect=lambda x: [x]) res, _, _ = fetch_events_command( client, first_fetch=arg_to_datetime("2 days ago", settings=DATEPARSER_SETTINGS), # type: ignore max_fetch=10, last_id=2, last_timestamp_ids=[1, 2], ) assert len(res) == 10 assert res[0].get("incident_id") == 3 def test_last_run_from_context(mocker: MockerFixture): """ Tests that the integration correctly resumes fetching incidents from the last saved state. Given: - The integration previously ran and stored `last_id` and `last_incident_time` in the context. - The command being executed is 'fetch-events'. - The Client is mocked to return sequential incidents from an HTTP request. When: - Running the `main()` function (which eventually triggers `fetch-events` logic). Then: - The integration should use the values from `getLastRun` to avoid re-fetching already seen incidents. - No errors raise """ import IronscalesEventCollector mocker.patch.object(IronscalesEventCollector, "send_events_to_xsiam") mocker.patch.object(demisto, "setLastRun") mocker.patch.object(demisto, "getLastRun", return_value={"last_id": 5, "last_incident_time": "2019-08-24T14:15:22.0Z"}) mocker.patch.object(Client, "get_jwt_token", return_value="mock_token") mocker.patch.object(Client, "get_incident_ids", return_value=[]) mocker.patch.object(Client, "get_incident") mocker.patch.object(demisto, "command", return_value="fetch-events") mocker.patch.object( demisto, "params", return_value={"max_fetch": "10", "first_fetch": "3 days", "url": "", "collect_all_incidents": True}, ) def ids_generator(): i = 1 while i < 100: yield {"total_pages": 100, "incidents": [{"incidentID": i}, {"incidentID": i + 1}]} i += 2 mocker.patch.object(Client, "_http_request", side_effect=ids_generator()) mocker.patch.object(Client, "get_incident", side_effect=lambda x: x) mocker.patch("IronscalesEventCollector.incident_to_events", side_effect=lambda x: [{x: 1}]) main() def test_sort_incidents(mocker: MockerFixture): """ Tests that incidents returned by get_all_incident_ids are sorted by their creation timestamps. Given: - Multiple pages of incident data, with incidents returned in non-chronological order. - Each incident has a `created` timestamp. When: - Calling `get_all_incident_ids'. Then: - The function returns incident IDs sorted in ascending order based on their `created` timestamps. - The number of incidents returned matches the total number of incidents across all pages. """ mocker.patch.object(Client, "get_jwt_token", return_value="mocked_jwt") client = Client( company_id="1", base_url="test_url", verify_certificate=True, proxy=False, api_key="test", scopes=[""], all_incident=True, ) responses = [ { "total_pages": 2, "incidents": [ {"incidentID": 1, "created": "2019-08-24T14:15:22.0Z"}, {"incidentID": 13, "created": "2019-08-24T14:15:25.0Z"}, ], }, { "total_pages": 2, "incidents": [ {"incidentID": 5, "created": "2019-08-24T14:15:20.0Z"}, {"incidentID": 2, "created": "2019-08-24T14:15:00.0Z"}, ], }, {"total_pages": 2, "incidents": []}, ] mocker.patch.object(client, "_http_request", side_effect=responses) mocker.patch.object( client, "get_incident", side_effect=lambda x: {"incident_id": x, "first_reported_date": f"{str(2019+x)}-08-24T14:15:22.0Z"}, ) mocker.patch("IronscalesEventCollector.incident_to_events", side_effect=lambda x: [x]) start_time = datetime.now(UTC) - timedelta(days=1) result = client.get_all_incident_ids(start_time) assert result == [2, 5, 1, 13] assert len(result) == 4 def test_same_timestamp(mocker: MockerFixture): """ Given: A page where we already seen some of the incidents in it When: running fetch_events_command with all_incidents = True and we already pulled some incidents Then: pull only new incidents """ mocker.patch.object(Client, "get_jwt_token", return_value="mocked_jwt") client = Client( company_id="1", base_url="test_url", verify_certificate=True, proxy=False, api_key="test", scopes=[""], all_incident=True, ) responses = [ { "total_pages": 2, "incidents": [ {"incidentID": 1, "created": "2024-08-24T14:15:12.0Z"}, {"incidentID": 2, "created": "2024-08-24T14:15:20.0Z"}, ], }, { "total_pages": 2, "incidents": [ {"incidentID": 3, "created": "2024-08-24T14:15:22.0Z"}, {"incidentID": 4, "created": "2024-08-24T14:15:22.0Z"}, ], }, {"total_pages": 2, "incidents": []}, ] times = ["2024-08-24T14:15:12.0Z", "2024-08-24T14:15:20.0Z", "2024-08-24T14:15:22.0Z", "2024-08-24T14:15:22.0Z"] mocker.patch.object(client, "_http_request", side_effect=responses) mocker.patch.object(client, "get_incident", side_effect=lambda x: {"incident_id": x, "first_reported_date": times[x - 1]}) mocker.patch("IronscalesEventCollector.incident_to_events", side_effect=lambda x: [x]) res, last_run, last_timestamp_ids = fetch_events_command( client, first_fetch=arg_to_datetime("2019-08-24T14:15:02.0Z", settings=DATEPARSER_SETTINGS), # type: ignore max_fetch=10, last_id=0, last_timestamp_ids=[0], ) assert len(res) == 4 assert res[0].get("incident_id") == 1 assert last_timestamp_ids == [3, 4] def test_unknown_incident_id(mocker: MockerFixture): """ Given: A page where we already seen some of the incidents in it When: running fetch_events_command with all_incidents = True and we already pulled some incidents Then: pull only new incidents """ mocker.patch.object(Client, "get_jwt_token", return_value="mocked_jwt") client = Client( company_id="1", base_url="test_url", verify_certificate=True, proxy=False, api_key="test", scopes=[""], all_incident=True, ) responses = [ { "total_pages": 2, "incidents": [ {"incidentID": 1, "created": "2024-08-24T14:15:12.0Z"}, {"incidentID": 2, "created": "2024-08-24T14:15:20.0Z"}, ], }, { "total_pages": 2, "incidents": [ {"incidentID": 3, "created": "2024-08-24T14:15:22.0Z"}, {"incidentID": 4, "created": "2024-08-24T14:15:22.0Z"}, ], }, {"total_pages": 2, "incidents": []}, ] def new_get_incident(incident): if incident != 3: return {"incident_id": incident, "first_reported_date": times[incident - 1]} raise ValueError("Incident 13500 not found for company") times = ["2024-08-24T14:15:12.0Z", "2024-08-24T14:15:20.0Z", "2024-08-24T14:15:22.0Z", "2024-08-24T14:15:22.0Z"] mocker.patch.object(client, "_http_request", side_effect=responses) mocker.patch.object(client, "get_incident", side_effect=new_get_incident) mocker.patch("IronscalesEventCollector.incident_to_events", side_effect=lambda x: [x]) res, last_run, last_timestamp_ids = fetch_events_command( client, first_fetch=arg_to_datetime("2019-08-24T14:15:02.0Z", settings=DATEPARSER_SETTINGS), # type: ignore max_fetch=10, last_id=0, last_timestamp_ids=[0], ) assert len(res) == 3 assert res[-1].get("incident_id") == 4 assert last_timestamp_ids == [4] def test_enrich_events_with_mailbox_details_empty_events(mocker: MockerFixture, client: Client): """ Given: An empty list of events. When: Running enrich_events_with_mailbox_details. Then: The function should return early without making any API calls. """ events = [] enrich_events_with_mailbox_details(client, events) mock_get_mailbox_mitigation_details = mocker.patch.object(client, "get_mailbox_mitigation_details") # Ensure no API calls were made assert mock_get_mailbox_mitigation_details.call_count == 0 def test_enrich_events_with_mailbox_details_single_page(mocker: MockerFixture, client: Client): """ Given: A list of events from multiple incidents and a single page of mailbox mitigation details. When: Running enrich_events_with_mailbox_details. Then: Each event should be enriched with the corresponding mailbox mitigations. """ # Mock events from two different incidents events = [ {"incident_id": 100, "name": "event1"}, {"incident_id": 200, "name": "event2"}, {"incident_id": 300, "name": "event3"}, ] # Mock API response with mitigations for both incidents mock_response = { "page": 1, "total_pages": 1, "mitigations": [ { "incidentID": 100, "mitigationID": 1, "mailboxEmail": "user1@example.com", "subject": "Phishing Email 1", }, { "incidentID": 100, "mitigationID": 2, "mailboxEmail": "user2@example.com", "subject": "Phishing Email 2", }, { "incidentID": 200, "mitigationID": 3, "mailboxEmail": "user3@example.com", "subject": "Phishing Email 3", }, ], "messages": [], } mocker.patch.object(client, "get_mailbox_mitigation_details", return_value=mock_response) enrich_events_with_mailbox_details(client, events) # Verify that events from incident 100 has 2 mitigations assert len(events[0]["mitigations"]) == 2 assert events[0]["mitigations"][0]["mailbox_email"] == "user1@example.com" assert events[0]["mitigations"][1]["mailbox_email"] == "user2@example.com" # Verify that events from incident 200 has 1 mitigation assert len(events[1]["mitigations"]) == 1 assert events[1]["mitigations"][0]["mailbox_email"] == "user3@example.com" # Verify that event from incident 300 has 0 mitigation assert len(events[2]["mitigations"]) == 0 def test_enrich_events_with_mailbox_details_multiple_pages(mocker: MockerFixture, client: Client): """ Given: A list of events and multiple pages of mailbox mitigation details. When: Running enrich_events_with_mailbox_details. Then: The function should paginate through all pages and enrich events with all mitigations. """ events = [ {"incident_id": 100, "name": "event1"}, {"incident_id": 200, "name": "event2"}, ] # Mock API responses for multiple pages page1_response = { "page": 1, "total_pages": 3, "mitigations": [ { "incidentID": 100, "mitigationID": 1, "mailboxEmail": "user1@example.com", "subject": "Email 1", }, ], "messages": [], } page2_response = { "page": 2, "total_pages": 3, "mitigations": [ { "incidentID": 100, "mitigationID": 2, "mailboxEmail": "user2@example.com", "subject": "Email 2", }, { "incidentID": 200, "mitigationID": 3, "mailboxEmail": "user3@example.com", "subject": "Email 3", }, ], "messages": [], } page3_response = { "page": 3, "total_pages": 3, "mitigations": [ { "incidentID": 200, "mitigationID": 4, "mailboxEmail": "user4@example.com", "subject": "Email 4", }, ], "messages": [], } mocker.patch.object(client, "get_mailbox_mitigation_details", side_effect=[page1_response, page2_response, page3_response]) enrich_events_with_mailbox_details(client, events) # Verify incident 100 has 2 mitigations from pages 1 and 2 assert len(events[0]["mitigations"]) == 2 assert events[0]["mitigations"][0]["mailbox_email"] == "user1@example.com" assert events[0]["mitigations"][1]["mailbox_email"] == "user2@example.com" # Verify incident 200 has 2 mitigations from pages 2 and 3 assert len(events[1]["mitigations"]) == 2 assert events[1]["mitigations"][0]["mailbox_email"] == "user3@example.com" assert events[1]["mitigations"][1]["mailbox_email"] == "user4@example.com" def test_enrich_events_with_mailbox_details_no_mitigations(mocker: MockerFixture, client: Client): """ Given: A list of events and an API response with no mitigations. When: Running enrich_events_with_mailbox_details. Then: Events should have empty mitigations lists. """ events = [ {"incident_id": 100, "name": "event1"}, {"incident_id": 200, "name": "event2"}, ] mock_response = { "page": 1, "total_pages": 1, "mitigations": [], "messages": [], } mocker.patch.object(client, "get_mailbox_mitigation_details", return_value=mock_response) enrich_events_with_mailbox_details(client, events) # Verify all events have empty mitigations assert events[0]["mitigations"] == [] assert events[1]["mitigations"] == [] def test_enrich_events_with_mailbox_details_field_transformation(mocker: MockerFixture, client: Client): """ Given: A list of events and mailbox mitigation details with camelCase fields. When: Running enrich_events_with_mailbox_details. Then: The mitigation fields should be transformed to snake_case. """ events = [{"incident_id": 100, "name": "event1"}] mock_response = { "page": 1, "total_pages": 1, "mitigations": [ { "incidentID": 100, "mitigationID": 1, "incidentState": "resolved", "remediatedTime": "2019-08-24T14:15:22Z", "mailboxId": 123, "mailboxEmail": "user@example.com", "subject": "Test Subject", "senderEmail": "sender@example.com", "senderIP": "192.168.1.1", "reportedBy": "reporter@example.com", "resolution": "deleted", "spfResult": "pass", }, ], "messages": [], } mocker.patch.object(client, "get_mailbox_mitigation_details", return_value=mock_response) enrich_events_with_mailbox_details(client, events) # Verify snake_case transformation and incidentID removal mitigation = events[0]["mitigations"][0] assert "incidentID" not in mitigation assert mitigation["mitigation_id"] == 1 assert mitigation["incident_state"] == "resolved" assert mitigation["remediated_time"] == "2019-08-24T14:15:22Z" assert mitigation["mailbox_id"] == 123 assert mitigation["mailbox_email"] == "user@example.com" assert mitigation["sender_email"] == "sender@example.com" assert mitigation["sender_ip"] == "192.168.1.1" assert mitigation["reported_by"] == "reporter@example.com" assert mitigation["spf_result"] == "pass" @pytest.mark.parametrize("all_incident", [True, False]) def test_fetch_events_no_events_returned(mocker: MockerFixture, all_incident: bool): """ Given: No incidents are available from the API (get_incident_ids returns an empty list). When: Running the fetch-events command via main(). Then: - send_events_to_xsiam is called with an empty events list. - setLastRun is called with last_id=-1, the original first_fetch timestamp, and empty last_timestamp_ids. """ import IronscalesEventCollector first_fetch_str = "2025-01-01T00:00:00+00:00" mocker.patch.object(Client, "get_jwt_token", return_value="mock_token") mocker.patch.object(Client, "get_incident_ids", return_value=[]) mocker.patch.object(demisto, "command", return_value="fetch-events") mocker.patch.object(demisto, "getLastRun", return_value={}) mocker.patch.object( demisto, "params", return_value={ "max_fetch": "10", "first_fetch": first_fetch_str, "url": "test_url", "collect_all_events": all_incident, }, ) mock_send = mocker.patch.object(IronscalesEventCollector, "send_events_to_xsiam") mock_set_last_run = mocker.patch.object(demisto, "setLastRun") main() # Verify send_events_to_xsiam was called with an empty list mock_send.assert_called_once_with([], vendor="ironscales", product="ironscales") # Verify setLastRun preserves the first_fetch time and resets IDs last_run_call = mock_set_last_run.call_args[0][0] assert last_run_call["last_id"] == -1 assert last_run_call["last_incident_time"] == first_fetch_str assert last_run_call["last_timestamp_ids"] == []