GetIncidentsApiModule

Common code that is appended into scripts which require searching incidents.

python · ApiModules

Details

IDGetIncidentsApiModule
Languagepython
From Version5.0.0
Docker Imagedemisto/python3:3.10.14.99865
Tagsinfra server

README

Common code that is appended into scripts which require searching incidents.

Script Data


Name Description
Script Type python3
Tags infra, server
Cortex XSOAR Version 5.0.0

Inputs


There are no inputs for this script.

Outputs


There are no outputs for this script.

import pytest
from GetIncidentsApiModule import *


def mock_incident(
    inc_id: int,
    inc_type: str,
    created: str,
    modified: str,
    **kwargs,
) -> dict:
    # helper method for creating mock incidents
    return {
        "id": inc_id,
        "name": f"This is incident {inc_id}",
        "type": inc_type,
        "severity": 0,
        "status": 1,
        "created": created,
        "modified": modified,
        "CustomFields": {"testField": "testValue"},
        "closed": "0001-01-01T00:00:00Z",
        "labels": [{"type": "subject", "value": "This subject1"}, {"type": "unique", "value": "This subject1"}],
        "attachment": [{"name": "Test word1 word2"}],
    } | kwargs


INCIDENTS_LIST = [
    mock_incident(1, "Phishing", "2019-01-02T00:00:00Z", "2020-01-02T00:00:00Z"),
    mock_incident(2, "Phishing", "2019-02-02T00:00:00Z", "2020-02-02T00:00:00Z"),
    mock_incident(3, "Malware", "2020-02-02T00:00:00Z", "2020-02-02T00:00:00Z"),
    mock_incident(4, "Malware", "2021-02-02T00:00:00Z", "2021-02-02T00:00:00Z"),
    mock_incident(5, "Malware", "2021-02-02T00:00:00Z", "2021-02-02T00:00:00Z"),
    mock_incident(6, "Unclassified", "2021-02-02T00:00:00Z", "2021-02-02T00:00:00Z"),
    mock_incident(7, "Unclassified", "2021-02-02T00:00:00Z", "2021-02-02T00:00:00Z"),
]


def does_incident_match_query(
    inc: dict,
    time_field: str,
    from_date_str: str,
    to_date_str: str,
    incident_types: list[str],
) -> bool:
    # a helper method for mock_execute_command() that determines
    # whether an incident should be part of the response
    if not incident_types or inc["type"] in incident_types:
        inc_time_field = dateparser.parse(inc[time_field])
        from_date = dateparser.parse(from_date_str or inc[time_field])
        to_date = dateparser.parse(to_date_str or inc[time_field])
        return from_date <= inc_time_field < to_date
    return False


def mock_execute_command(command: str, args: dict) -> list[dict]:
    # Mock implementations for `getIncidents` and `getContext` builtin commands.
    match command:
        case "getIncidents":
            page = args["page"]
            size = args["size"]
            query = args["query"] or ""
            incident_types = []
            time_field = "modified" if "modified" in query else "created"
            from_date = args["fromdate"]
            to_date = args["todate"]
            # populate_fields = args["populateFields"] or []

            if match := re.search(r"\(modified:>=\"([^\"]*)\"\)", query):
                from_date = match.group(1)
            if match := re.search(r"\(modified:<\"([^\"]*)\"\)", query):
                to_date = match.group(1)
            if match := re.search(r"\(type:\(([^)]*)\)\)", query):
                incident_types = argToList(match.group(1), separator=" ", transform=lambda t: t.strip('"'))
            res = [
                i  # {k: v for k, v in i.items() if not populate_fields or k in populate_fields}
                for i in INCIDENTS_LIST
                if does_incident_match_query(i, time_field, from_date, to_date, incident_types)
            ][page * size : (page + 1) * size]
            return [{"Contents": {"data": res}, "Type": "json"}]
        case "getContext":
            return [{"Contents": "context", "Type": "json"}]
        case _:
            raise Exception(f"Unmocked command: {command}")


def test_prepare_fields_list():
    """
    Given: A list of incident fields
    When: Running prepare_fields_list()
    Then: Ensure a unique list of fields without the `incident.` prefix for each item is returned
    """
    assert prepare_fields_list(["incident.hello", "", "hello"]) == ["hello"]


def test_build_query():
    """
    Given: Different query arguments
    When: Running build_query_parameter()
    Then: Ensure the result is a query string in the expected format
    """
    query = build_query_parameter(
        custom_query="Extra part",
        incident_types=["*phish*", "Malware"],
        time_field="modified",
        from_date="2019-01-10T00:00:00",
        to_date="2019-01-12T00:00:00",
        non_empty_fields=["status", "closeReason"],
    )
    assert query == (
        '(Extra part) and (type:(*phish* "Malware")) and (modified:>="2019-01-10T00:00:00") '
        'and (modified:<"2019-01-12T00:00:00") and (status:* and closeReason:*)'
    )


def test_build_query_bad():
    """
    Given: No query arguments
    When: Running build_query_parameter()
    Then: Ensuring a DemistoException is raised
    """
    with pytest.raises(DemistoException):
        build_query_parameter(
            custom_query=None,
            incident_types=[],
            time_field=None,
            from_date=None,
            to_date=None,
            non_empty_fields=[],
        )


def test_get_incidents_by_query_sanity_test(mocker):
    """
    Given:
    - A mock incidents database (INCIDENTS_LIST)
    - Search incidents query arguments
    When: Running get_incidents_by_query()
    Then: Ensure the expected 4 incidents are returned
    """
    mocker.patch.object(demisto, "executeCommand", side_effect=mock_execute_command)
    args = {
        "incidentTypes": "Phishing,Malware",
        "timeField": "created",
        "fromDate": "2019-02-01T00:00:00",
        "toDate": "3 days ago",
        "limit": "10",
        "includeContext": "false",
        "pageSize": "10",
    }
    results = get_incidents_by_query(args)
    assert len(results) == 4
    assert all(inc["type"] in args["incidentTypes"] for inc in results)
    assert all(dateparser.parse(args["fromDate"]).astimezone() <= dateparser.parse(inc["created"]) for inc in results)
    assert all(dateparser.parse(inc["created"]) < dateparser.parse(args["toDate"]).astimezone() for inc in results)


def test_get_incidents_by_query_with_pagination(mocker):
    """
    Given:
    - A mock incidents database (INCIDENTS_LIST)
    - Search incidents query arguments that should return 4 incidents (same as the sanity test)
    When:
    - pageSize is 3
    Then:
    - Ensure the expected 4 incidents are returned
    - Ensure executeCommand was called twice
    """
    execute_command = mocker.patch.object(demisto, "executeCommand", side_effect=mock_execute_command)
    args = {
        "incidentTypes": "Phishing,Malware",
        "timeField": "created",
        "fromDate": "2019-02-01T00:00:00",
        "toDate": "3 days ago",
        "limit": "10",
        "includeContext": "false",
        "pageSize": "3",
    }
    results = get_incidents_by_query(args)
    assert len(results) == 4
    assert execute_command.call_count == 2


def test_get_incidents_by_query_with_populate_fields(mocker):
    """
    Given:
    - A mock incidents database (INCIDENTS_LIST)
    - Search incidents query arguments that should return 4 incidents (same as the sanity test)
    When:
    - populateFields is id,name,testField
    Then:
    - Ensure the expected 4 incidents are returned
    - Ensure the returned incidents' keys are "id", "name", and "testField" only.
    """
    mocker.patch.object(demisto, "executeCommand", side_effect=mock_execute_command)
    args = {
        "incidentTypes": "Phishing,Malware",
        "timeField": "created",
        "fromDate": "2019-02-01T00:00:00",
        "toDate": "3 days ago",
        "limit": "10",
        "includeContext": "false",
        "pageSize": "10",
        "populateFields": "id,name,testField",
    }
    results = get_incidents_by_query(args)
    assert len(results) == 4
    assert all(set(inc.keys()) == {"id", "name", "testField"} for inc in results)


def test_get_incidents_by_query_with_populate_fields_with_pipe_separator(mocker):
    """
    Given:
    - A mock incidents database (INCIDENTS_LIST)
    - Search incidents query arguments that should return 4 incidents (same as the sanity test)
    When:
    - populateFields is id|name|testField
    Then:
    - Ensure the expected 4 incidents are returned
    - Ensure the returned incidents' keys are "id", "name", and "testField" only.
    """
    mocker.patch.object(demisto, "executeCommand", side_effect=mock_execute_command)
    args = {
        "incidentTypes": "Phishing,Malware",
        "timeField": "created",
        "fromDate": "2019-02-01T00:00:00",
        "toDate": "3 days ago",
        "limit": "10",
        "includeContext": "false",
        "pageSize": "10",
        "populateFields": "id|name|testField",
    }
    results = get_incidents_by_query(args)
    assert len(results) == 4
    assert all(set(inc.keys()) == {"id", "name", "testField"} for inc in results)


def test_get_incidents_by_query_with_context(mocker):
    """
    - A mock incidents database (INCIDENTS_LIST)
    - Search incidents query arguments that should return 4 incidents (same as the sanity test)
    When:
    - includeContext is true
    Then:
    - Ensure the expected 4 incidents are returned
    - Ensure each incident has a non-empty context key
    """
    mocker.patch.object(demisto, "executeCommand", side_effect=mock_execute_command)
    args = {
        "incidentTypes": "Phishing,Malware",
        "timeField": "created",
        "fromDate": "2019-02-01T00:00:00",
        "toDate": "3 days ago",
        "limit": "10",
        "includeContext": "true",
        "pageSize": "10",
    }
    results = get_incidents_by_query(args)
    assert len(results) == 4
    assert all(inc["context"] for inc in results)


def test_get_incidents_by_query_timefield_is_modified(mocker):
    """
    - A mock incidents database (INCIDENTS_LIST)
    - Search incidents query arguments
    When:
    - timeField is modified
    Then:
    - Ensure the expected 1 incident is returned
    """
    execute_command = mocker.patch.object(demisto, "executeCommand", side_effect=mock_execute_command)
    args = {
        "timeField": "modified",
        "fromDate": "2020-01-02T00:00:00Z",
        "toDate": "2020-01-03T00:00:00Z",
        "limit": "10",
        "includeContext": "false",
        "pageSize": "3",
    }
    results = get_incidents_by_query(args)
    assert len(results) == 1
    assert execute_command.call_count == 1