Respond_Analyst

Use the Mandiant Automated Defense integration to fetch and update incidents from Mandiant Automated Defense. Mandiant Automated Defense fetches open incidents and updates them every minute. Changes made within XSOAR are reflected in Mandiant Automated Defense platform with bi-directional mirroring capabilities enabled.

Analytics & SIEM · Mandiant Automated Defense

Details

IDRespond_Analyst
ProviderGoogle
CategoryAnalytics & SIEM
From Version6.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

What is the Respond Analyst/Mandiant Defense Engine?

Disclaimer: Respond Software was recently acquired by FireEye and has rebranded from the Respond Analyst to the Mandiant Defense Engine. These terms refer to the same product. Most of this integration was written prior to rebranding, and primarily includes references to Respond. This will be updated in the future, at which point this disclaimer will be removed.

Mandiant Defense is the cybersecurity investigation automation solution that connects the dots across disparate cybersecurity data to find real incidents fast. The Mandiant Defense engine is built to accelerate investigations for security operations teams in defense agencies, government bodies, universities, large enterprises, and leading managed service providers to get investigation power at machine speed. Mandiant Defense works with the broadest range of vendors, sensors, threat intelligence and data repositories in the industry to improve detection and response while raising security analyst productivity.

What does this pack do?

This pack provides a set of commands which can be executed against an instance of the Respond Analyst. The commands allow users to retrieve information from Respond and modify incidents from within XSOAR. Additionally, this integration supports bi-directional mirroring (for XSOAR v6 and above) of

  • incident closure status
  • incident assignee
  • incident feedback and notes
  • incident title
  • incident description

When fetch incidents is enabled, the pack will pull all open incidents from Respond into XSOAR. Each incident in XSOAR will follow the naming convention <Respond Tenant Id>:<Respond Incident Id>

It is worth noting that this pack does not pull in all of the data on each incident in Respond, rather a subset deemed to be most critical and helpful based on customer feedback. There is a link to the Respond incident provided on every corresponding XSOAR incident in case a user needs to retrieve additional information.

Use the Mandiant Automated Defense integration to fetch and update incidents from Mandiant Automated Defense. Mandiant Automated Defense fetches open incidents and updates them every minute. Changes made within XSOAR are reflected in Mandiant Automated Defense platform with bi-directional mirroring capabilities enabled.

Configure Mandiant Automated Defense (Formerly Respond Software) in Cortex

Parameter Description Required
Incident Mirroring Direction   False
Base Url https://&lt;Respond Analyst Server&gt; (either hostname or IP address) True
Trust any certificate (not secure)   False
Incident type   False
Fetch incidents   False
API Token steps to generate an API token here -&gt; https://knowledge-base.respond-software.com/knowledge/api-token True
Max Fetch   False
First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days)   False
Incidents Fetch Interval   False

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.

mad-get-incident


pull data for a specific incident from MAD. This command will only return an output of the incident data. it does not create a new incident

Base Command

mad-get-incident

Input

Argument Name Description Required
tenant_id ID of the Tenant in which the incident resides in Respond. Optional
incident_id Respond incident ID of the incident to retrieve. Required

Context Output

Path Type Description
Mandiant.AutomatedDefense.Incident.incidentId String ID of incident
Mandiant.AutomatedDefense.Incident.timeGenerated Date time incident was created
Mandiant.AutomatedDefense.Incident.eventCount Number number of events associated with incident
Mandiant.AutomatedDefense.Incident.firstEventTime Date time first event associated with incident occurred
Mandiant.AutomatedDefense.Incident.lastEventTime Date time most recent event associated with incident occurred
Mandiant.AutomatedDefense.Incident.URL String URL to incident in Mandiant Advantage platform
Mandiant.AutomatedDefense.Incident.closeURL String URL to incident close page in Mandiant Advantage platform
Mandiant.AutomatedDefense.Incident.title String incident title
Mandiant.AutomatedDefense.Incident.description String incident description
Mandiant.AutomatedDefense.Incident.status String incident status
Mandiant.AutomatedDefense.Incident.severity String incident severity
Mandiant.AutomatedDefense.Incident.probability String incident probability
Mandiant.AutomatedDefense.Incident.attackStage String incident attack stage
Mandiant.AutomatedDefense.Incident.attackTactic Unknown incident attack tactic
Mandiant.AutomatedDefense.Incident.assetCriticality String incident asset criticiality
Mandiant.AutomatedDefense.Incident.assetCount Number incident asset count
Mandiant.AutomatedDefense.Incident.assets.hostname String asset hostname
Mandiant.AutomatedDefense.Incident.assets.ipaddress String asset ip address
Mandiant.AutomatedDefense.Incident.assets.isinternal Boolean asset is internal
Mandiant.AutomatedDefense.Incident.externalsystems.hostname String system hostname
Mandiant.AutomatedDefense.Incident.externalsystems.ipaddress String system ip address
Mandiant.AutomatedDefense.Incident.externalsystems.isinternal Boolean system is internal
Mandiant.AutomatedDefense.Incident.accounts.domain Unknown account domain
Mandiant.AutomatedDefense.Incident.accounts.name String account name
Mandiant.AutomatedDefense.Incident.hashes.hash String hash
Mandiant.AutomatedDefense.Incident.malware.name String malware name
Mandiant.AutomatedDefense.Incident.malware.type String malware type
Mandiant.AutomatedDefense.Incident.malware.vendor String malware vendor
Mandiant.AutomatedDefense.Incident.escalationreasons.label String escalation reason
Mandiant.AutomatedDefense.Incident.assignedUsers String assigned users
Mandiant.AutomatedDefense.Incident.tenantIdRespond String tenant id in mandiant
Mandiant.AutomatedDefense.Incident.tenantId String tenant id external
Mandiant.AutomatedDefense.Incident.respondRemoteId String remote id
Mandiant.AutomatedDefense.Incident.dbotMirrorDirection String mirror direction
Mandiant.AutomatedDefense.Incident.dbotMirrorInstance String mirror instance
Mandiant.AutomatedDefense.Incident.owner String owner
Mandiant.AutomatedDefense.Incident.feedback.timeUpdated Date time feedback updated
Mandiant.AutomatedDefense.Incident.feedback.userId String user id
Mandiant.AutomatedDefense.Incident.feedback.outcome String feedback outcome
Mandiant.AutomatedDefense.Incident.feedback.comments String feedback comments

mad-close-incident


close an incident in Respond and provide feedback on that incident. If the incident is already closed, feedback can still be updated. Additional comments and an updated closure code are viable options for updates on an incident that has already been closed (and on incidents that have not been closed yet as well)

Base Command

mad-close-incident

Input

Argument Name Description Required
tenant_id ID of the Tenant in which the incident resides in Respond. Optional
incident_id Respond incident ID of the incident to retrieve. Required
incident_feedback Outcome of the incident. Confirmed, Non-Actionable, or Inconclusive. This outcome is determined by the analyst who closes the incident. Possible values are: ConfirmedIncident, NonActionable, Inconclusive. Optional
feedback_optional_text additional feedback information added by analysts. Any specific notes or observations. Optional

Context Output

There is no context output for this command.

mad-assign-user


assign a user to a Respond incident

Base Command

mad-assign-user

Input

Argument Name Description Required
incident_id respond incident id. Required
tenant_id tenant id. Optional
username email. Required

Context Output

There is no context output for this command.

mad-remove-user


unassign a user from a Respond incident

Base Command

mad-remove-user

Input

Argument Name Description Required
incident_id incident id. Required
tenant_id tenant id. Optional
username email. Required

Context Output

There is no context output for this command.

mad-get-escalations


Get escalation data associated with incident. In Respond, an ‘escalation’ is a specific event derived from a cybersecurity telemetry. Escalations are compiled together to form Incidents in Respond.

Base Command

mad-get-escalations

Input

Argument Name Description Required
incident_id incident_id. Required
tenant_id tenant id. Optional

Context Output

There is no context output for this command.

Incident Mirroring

You can enable incident mirroring between Cortex XSOAR incidents and Mandiant Automated Defense (Formerly Respond Software) corresponding events (available from Cortex XSOAR version 6.0.0).
To set up the mirroring:

  1. Enable Fetching incidents in your instance configuration.
  2. In the Mirroring Direction integration parameter, select in which direction the incidents should be mirrored:

    Option Description
    None Turns off incident mirroring.
    Incoming Any changes in Mandiant Automated Defense (Formerly Respond Software) events (mirroring incoming fields) will be reflected in Cortex XSOAR incidents.
    Outgoing Any changes in Cortex XSOAR incidents will be reflected in Mandiant Automated Defense (Formerly Respond Software) events (outgoing mirrored fields).
    Both  

Newly fetched incidents will be mirrored in the chosen direction. However, this selection does not affect existing incidents.
Important Note: To ensure the mirroring works as expected, mappers are required, both for incoming and outgoing, to map the expected fields in Cortex XSOAR and Mandiant Automated Defense (Formerly Respond Software).

Configuration parameters

  • mirror_direction — Incident Mirroring Direction
  • base_url — Base Url (required)
  • insecure — Trust any certificate (not secure)
  • incidentType — Incident type
  • isFetch — Fetch incidents
  • token — API Token (required)
  • max_fetch — Max Fetch
  • first_fetch — First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days)
  • incidentFetchInterval — Incidents Fetch Interval

Commands (5)

  • mad-assign-user

    assign a user to a Respond incident.

  • mad-close-incident

    close an incident in Respond and provide feedback on that incident. If the incident is already closed, feedback can still be updated. Additional comments and an updated closure code are viable options for updates on an incident that has already been closed (and on incidents that have not been closed yet as well).

  • mad-get-escalations

    Get escalation data associated with incident. In Respond, an 'escalation' is a specific event derived from a cybersecurity telemetry. Escalations are compiled together to form Incidents in Respond.

  • mad-get-incident

    pull data for a specific incident from MAD. This command will only return an output of the incident data. it does not create a new incident.

  • mad-remove-user

    unassign a user from a Respond incident.

import json

import demistomock as demisto
import pytest
import requests

# Disable insecure warnings
requests.packages.urllib3.disable_warnings()

BASE_URL = "https://localhost:6078"
API_TOKEN = "apitoken"


params = {
    "base_url": BASE_URL,
    "token": API_TOKEN,
    "insecure": True,
    "mirror_direction": "Both",
    "first_fetch": "7 Days",
    "max_fetch": "2",
}


@pytest.fixture(autouse=True)
def set_mocker(mocker):
    mocker.patch.object(demisto, "params", return_value=params)
    mocker.patch.object(demisto, "integrationInstance", return_value="respond_test")
    mocker.patch.object(demisto, "findUser", return_value={"username": "user1"})


def load_test_data(json_path):
    with open(json_path) as f:
        return json.load(f)


def mock_rest_client():
    from RespondAnalyst import RestClient

    return RestClient(base_url=BASE_URL, verify=False)


def test_fetch_incidents_does_not_get_most_recent_event_again(mocker, requests_mock):
    from RespondAnalyst import fetch_incidents

    get_ids_response = []
    get_full_incidents_response = []

    client = mock_rest_client()

    last_run = {"Tenant 1": {"time": 1593044883}}

    requests_mock.get(
        f"{BASE_URL}/session/tenantIdMapping?tempId={API_TOKEN}", json={"dev1": "Tenant 1", "dev1_tenant2": "Tenant 2"}
    )
    mocker.patch.object(client, "construct_and_send_get_incident_ids_query", return_value=get_ids_response)
    mocker.patch.object(client, "construct_and_send_full_incidents_query", return_value=get_full_incidents_response)

    next_run, incidents = fetch_incidents(client, last_run)
    assert len(incidents) == 0
    assert next_run["Tenant 1"]["time"] == 1593044883
    assert next_run["Tenant 2"]["time"] is None


def test_get_incident_command(requests_mock):
    from RespondAnalyst import get_incident_command

    full_incidents_response = load_test_data("test_data/full_incidents_response_single_full_incident.json")

    client = mock_rest_client()

    requests_mock.get(
        f"{BASE_URL}/session/tenantIdMapping?tempId={API_TOKEN}", json={"dev1": "Tenant 1", "dev1_tenant2": "Tenant 2"}
    )
    requests_mock.post(f"{BASE_URL}/graphql?tempId={API_TOKEN}&tenantId=dev1", json=full_incidents_response)
    args = {"tenant_id": "Tenant 1", "incident_id": 6}
    command_result = get_incident_command(client, args)

    assert command_result
    assert "### Mandiant Automated Defense Alert, Tenant 1 : 6" in command_result.readable_output


def test_fetch_incidents_no_new(mocker, requests_mock):
    from RespondAnalyst import fetch_incidents

    get_ids_response = []
    get_full_incidents_response = []

    client = mock_rest_client()

    last_run = {"Tenant 1": {"time": 1593044883}}

    requests_mock.get(
        f"{BASE_URL}/session/tenantIdMapping?tempId={API_TOKEN}", json={"dev1": "Tenant 1", "dev1_tenant2": "Tenant 2"}
    )
    mocker.patch.object(client, "construct_and_send_get_incident_ids_query", return_value=get_ids_response)
    mocker.patch.object(client, "construct_and_send_full_incidents_query", return_value=get_full_incidents_response)

    next_run, incidents = fetch_incidents(client, last_run)
    assert len(incidents) == 0
    assert next_run["Tenant 1"]["time"] == 1593044883
    assert next_run["Tenant 2"]["time"] is None


def test_fetch_incidents(mocker, requests_mock):
    from RespondAnalyst import fetch_incidents

    get_ids_response = [{"id": "8", "dateCreated": "1234566789"}, {"id": "14", "dateCreated": "12345676789"}]
    get_full_incidents_response = load_test_data("test_data/full_incidents.json")

    client = mock_rest_client()

    requests_mock.get(f"{BASE_URL}/session/tenantIdMapping?tempId={API_TOKEN}", json={"dev1": "Tenant 1"})
    mocker.patch.object(client, "construct_and_send_get_incident_ids_query", return_value=get_ids_response)
    mocker.patch.object(client, "construct_and_send_full_incidents_query", return_value=get_full_incidents_response)

    expected_output = load_test_data("test_data/fetch_incidents_response.json")

    next_run, response = fetch_incidents(client, None)
    assert expected_output == response
    assert next_run["Tenant 1"]["time"] == "1591374028642"


def test_remove_user(mocker, requests_mock):
    from RespondAnalyst import remove_user_command

    rest_client = mock_rest_client()
    get_all_users_response = load_test_data("test_data/users.json")
    remove_user_response = {"data": {"removeUserFromIncident": {"id": "5", "userIds": []}}}
    mocker.patch.object(demisto, "info")

    requests_mock.post(f"{BASE_URL}/graphql?tempId={API_TOKEN}&tenantId=dev1", json=remove_user_response)
    requests_mock.get(f"{BASE_URL}/api/v0/users?tempId={API_TOKEN}", json=get_all_users_response)
    requests_mock.get(f"{BASE_URL}/session/tenantIdMapping?tempId={API_TOKEN}", json={"dev1": "Tenant 1"})
    requests_mock.get(
        f"{BASE_URL}/session/activeUser",
        json={
            "userId": "qa1-user-id",
            "currentTenant": "dev1",
            "email": "qa-user@respond-software.com",
            "firstname": "jay",
            "lastname": "blue",
        },
    )

    args = {"tenant_id": "Tenant 1", "incident_id": 5, "username": "qa-user2@respond-software.com"}
    res = remove_user_command(rest_client, args)
    assert res == "user with email: qa-user2@respond-software.com removed from incident with id 5 on tenant Tenant 1"


def test_assign_user(mocker, requests_mock):
    from RespondAnalyst import assign_user_command

    assign_user_response = {"data": {"addUserToIncident": {"id": "5", "userIds": ["675ad53a-d8f4-4ae7-9a3a-59de6c70b912"]}}}
    get_all_users_response = load_test_data("test_data/users.json")

    rest_client = mock_rest_client()
    mocker.patch.object(demisto, "info")

    requests_mock.get(f"{BASE_URL}/api/v0/users?tempId={API_TOKEN}", json=get_all_users_response)
    requests_mock.get(f"{BASE_URL}/session/tenantIdMapping?tempId={API_TOKEN}", json={"dev1": "Tenant 1"})
    requests_mock.get(
        f"{BASE_URL}/session/activeUser",
        json={
            "userId": "qa1-user-id",
            "currentTenant": "dev1",
            "email": "qa-user@respond-software.com",
            "firstname": "jay",
            "lastname": "blue",
        },
    )
    requests_mock.post(f"{BASE_URL}/graphql?tempId={API_TOKEN}&tenantId=dev1", json=assign_user_response)

    args = {
        "tenant_id": "Tenant 1",
        "incident_id": 5,
        "username": "qa-user2@respond-software.com",
    }
    res = assign_user_command(rest_client, args)
    assert res == "user with email: qa-user2@respond-software.com added to incident with id 5 on tenant Tenant 1"

    # no tenant id provided
    args = {
        "incident_id": 5,
        "username": "qa-user3@respond-software.com",
    }
    res = assign_user_command(rest_client, args)
    assert res == "user with email: qa-user3@respond-software.com added to incident with id 5 on tenant Tenant 1"


def test_close_incident(mocker, requests_mock):
    from RespondAnalyst import close_incident_command

    rest_client = mock_rest_client()
    mocker.patch.object(demisto, "info")

    get_all_users_response = load_test_data("test_data/users.json")
    close_incident_response = load_test_data("test_data/close_incident_response.json")
    single_full_incident_response = load_test_data("test_data/single_full_incident.json")

    mocker.patch.object(rest_client, "construct_and_send_full_incidents_query", return_value=single_full_incident_response)

    requests_mock.get(f"{BASE_URL}/api/v0/users?tempId={API_TOKEN}", json=get_all_users_response)
    requests_mock.get(f"{BASE_URL}/session/tenantIdMapping?tempId={API_TOKEN}", json={"dev1": "Tenant 1"})
    requests_mock.get(
        f"{BASE_URL}/session/activeUser",
        json={
            "userId": "qa1-user-id",
            "currentTenant": "dev1",
            "email": "qa-user@respond-software.com",
            "firstname": "jay",
            "lastname": "blue",
        },
    )
    requests_mock.post(f"{BASE_URL}/graphql?tempId={API_TOKEN}&tenantId=dev1", json=close_incident_response)

    args = {
        "tenant_id": "Tenant 1",
        "incident_id": 5,
        "incident_feedback": "NonActionable",
        "feedback_selected_options": [
            {"id": "4", "key": "unmonitoredAssets", "value": "true"},
            {"id": "19", "key": "scopedCorrectly", "value": "No"},
        ],
        "incident_comments": "new text",
    }

    res = close_incident_command(rest_client, args)
    assert "incident closed and/or feedback updated for incident with id 5 on tenant Tenant 1" in res

    # no tenant id
    args = {
        "incident_id": 6,
        "incident_feedback": "NonActionable",
        "feedback_selected_options": [
            {"id": "4", "key": "unmonitoredAssets", "value": "true"},
            {"id": "19", "key": "scopedCorrectly", "value": "No"},
        ],
        "incident_comments": "new text",
    }

    # not expecting a different id bc of mocked responses, just expecting a successful response
    res = close_incident_command(rest_client, args)
    assert "incident closed and/or feedback updated for incident with id 6 on tenant Tenant 1" in res


def test_assign_user_raise_exception(mocker, requests_mock):
    from RespondAnalyst import assign_user_command

    rest_client = mock_rest_client()

    mocker.patch.object(demisto, "error")

    get_all_users_response = load_test_data("test_data/users.json")
    mocker.patch.object(rest_client, "construct_and_send_add_user_to_incident_mutation", return_value=Exception)
    requests_mock.get(f"{BASE_URL}/api/v0/users?tempId={API_TOKEN}", json=get_all_users_response)
    requests_mock.get(f"{BASE_URL}/session/tenantIdMapping?tempId={API_TOKEN}", json={"dev1": "Tenant 1"})
    requests_mock.get(
        f"{BASE_URL}/session/activeUser",
        json={
            "userId": "qa1-user-id",
            "currentTenant": "dev1",
            "email": "qa-user@respond-software.com",
            "firstname": "jay",
            "lastname": "blue",
        },
    )
    args = {
        "tenant_id": "Tenant 1",
        "incident_id": 5,
        "username": "qa-user2@respond-software.com",
    }
    with pytest.raises(Exception):
        assign_user_command(rest_client, args)
    demisto.error.assert_any_call("error adding user to incident: type object 'Exception' has no attribute 'get'")


def test_remove_user_raises_exception(mocker, requests_mock):
    from RespondAnalyst import remove_user_command

    rest_client = mock_rest_client()

    mocker.patch.object(demisto, "error")

    get_all_users_response = load_test_data("test_data/users.json")
    mocker.patch.object(rest_client, "construct_and_send_remove_user_from_incident_mutation", return_value=Exception)
    requests_mock.get(f"{BASE_URL}/api/v0/users?tempId={API_TOKEN}", json=get_all_users_response)
    requests_mock.get(f"{BASE_URL}/session/tenantIdMapping?tempId={API_TOKEN}", json={"dev1": "Tenant 1"})
    requests_mock.get(
        f"{BASE_URL}/session/activeUser?tempId={API_TOKEN}",
        json={
            "userId": "qa1-user-id",
            "currentTenant": "dev1",
            "email": "qa-user@respond-software.com",
            "firstname": "jay",
            "lastname": "blue",
        },
    )
    args = {"tenant_id": "Tenant 1", "incident_id": 5, "username": "qa-user4@respond-software.com"}
    with pytest.raises(Exception):
        remove_user_command(rest_client, args)

    demisto.error.assert_called_once_with("no user found with email qa-user4@respond-software.com")


def test_close_incident_with_bad_responses(mocker, requests_mock):
    from RespondAnalyst import close_incident_command

    rest_client = mock_rest_client()
    mocker.patch.object(demisto, "error")

    get_all_users_response = load_test_data("test_data/users.json")

    mocker.patch.object(rest_client, "construct_and_send_close_incident_mutation", return_value=Exception)
    mocker.patch.object(rest_client, "construct_and_send_full_incidents_query", return_value=Exception)
    requests_mock.get(f"{BASE_URL}/api/v0/users?tempId={API_TOKEN}", json=get_all_users_response)
    requests_mock.get(f"{BASE_URL}/session/tenantIdMapping?tempId={API_TOKEN}", json={"dev1": "Tenant 1"})
    requests_mock.get(
        f"{BASE_URL}/session/activeUser",
        json={
            "userId": "qa1-user-id",
            "currentTenant": "dev1",
            "email": "qa-user@respond-software.com",
            "firstname": "jay",
            "lastname": "blue",
        },
    )

    args = {
        "tenant_id": "Tenant 1",
        "incident_id": 5,
        "incident_feedback": "NonActionable",
        "feedback_selected_options": [
            {"id": "4", "key": "unmonitoredAssets", "value": "true"},
            {"id": "19", "key": "scopedCorrectly", "value": "No"},
        ],
        "incident_comments": "new text",
    }
    with pytest.raises(Exception):
        close_incident_command(rest_client, args)

    demisto.error.assert_any_call("error closing incident and/or updating feedback: type 'Exception' is not subscriptable")


def test_get_remote_data_command(requests_mock):
    from RespondAnalyst import get_remote_data_command

    full_incidents_response = load_test_data("test_data/full_incidents_response_single_full_incident.json")

    rest_client = mock_rest_client()
    requests_mock.get(f"{BASE_URL}/session/tenantIdMapping?tempId={API_TOKEN}", json={"dev1": "Tenant 1"})
    requests_mock.post(f"{BASE_URL}/graphql?tempId={API_TOKEN}&tenantId=dev1", json=full_incidents_response)

    args = {"id": "Tenant 1:1"}

    res = get_remote_data_command(rest_client, args)
    expected_result = [
        {
            "id": "Tenant 1:1",
            "incidentId": "6",
            "timeGenerated": "2020-06-05T16:20:21Z",
            "eventCount": 24,
            "firstEventTime": "2019-12-21T13:05:31Z",
            "lastEventTime": "2020-06-05T08:20:17Z",
            "URL": "https://localhost:6078/secure/incidents/6?tenantId=dev1",
            "closeURL": "https://localhost:6078/secure/incidents/feedback/6?tenantId=dev1",
            "title": "Virus Infections, Suspicious Repeated Connections and Int - Int Network IPS Activity",
            "description": "description of the incident",
            "status": "Closed",
            "severity": "Critical",
            "probability": "VeryHigh",
            "attackStage": "LateralMovement",
            "attackTactic": None,
            "assetCriticality": "Critical",
            "assetCount": 1,
            "assets": [{"hostname": "host1", "ipaddress": "10.150.0.11", "isinternal": True}],
            "escalationreasons": [{"label": "Multiple Network IPS Signatures Triggered by Same Internal Asset"}],
            "assignedUsers": ["user1"],
            "feedback": {
                "timeUpdated": "1593469076049",
                "userId": "qa-user@respond-software.com",
                "outcome": "Non-Actionable",
                "comments": "blah blah blah",
            },
            "tenantIdRespond": "dev1",
            "tenantId": "Tenant 1",
            "respondRemoteId": "Tenant 1:6",
            "dbotMirrorDirection": "Both",
            "dbotMirrorInstance": "respond_test",
            "owner": "user1",
            "externalsystems": [{"hostname": "host2", "ipaddress": "10.150.0.22", "isinternal": False}],
            "malware": [
                {"name": "name1", "type": "Ransomware", "vendor": "vendor"},
                {"name": "name2", "type": "RAT", "vendor": "vendor"},
            ],
            "hashes": [{"hash": "44d88612fea8a8f36de82e1278abb02f"}],
            "accounts": [
                {"domain": None, "name": "svc_adminscom3"},
                {"domain": None, "name": "svc_adminscom"},
                {"domain": "test", "name": "svc_adminscom2"},
                {"domain": None, "name": "svc_adminscom2"},
                {"domain": "test", "name": "svc_adminscom3"},
                {"domain": "test", "name": "svc_adminscom"},
                {"domain": None, "name": "Unknown"},
            ],
            "signatures": [],
            "domains": [],
        },
        {
            "Contents": {"closeNotes": "blah blah blah", "closeReason": "Non-Actionable", "dbotIncidentClose": True},
            "ContentsFormat": "json",
            "Type": 1,
        },
    ]
    assert res == expected_result


def test_update_remote_system_command(mocker, requests_mock):
    from RespondAnalyst import update_remote_system_command

    args = {
        "data": "tons of data",
        "entries": "entries val",
        "incidentChanged": True,
        "remoteId": "Tenant 1:1",
        "status": "status val",
        "delta": {"title": "title val", "description": "description val"},
    }
    rest_client = mock_rest_client()

    get_all_users_response = load_test_data("test_data/users.json")
    requests_mock.get(
        f"{BASE_URL}/session/tenantIdMapping?tempId={API_TOKEN}", json={"dev1": "Tenant 1", "dev1_tenant2": "Tenant 2"}
    )
    requests_mock.get(f"{BASE_URL}/api/v0/users?tempId={API_TOKEN}", json=get_all_users_response)
    requests_mock.get(
        f"{BASE_URL}/session/activeUser",
        json={
            "userId": "qa1-user-id",
            "currentTenant": "dev1",
            "email": "qa-user@respond-software.com",
            "firstname": "jay",
            "lastname": "blue",
        },
    )
    mocker.patch.object(rest_client, "construct_and_send_update_title_mutation", return_value={})
    mocker.patch.object(rest_client, "construct_and_send_update_description_mutation", return_value={})
    title_spy = mocker.spy(rest_client, "construct_and_send_update_title_mutation")
    desc_spy = mocker.spy(rest_client, "construct_and_send_update_description_mutation")
    res = update_remote_system_command(rest_client, args)
    assert title_spy.call_count == 1
    assert desc_spy.call_count == 1
    assert res == "Tenant 1:1"


def test_get_mapping_fields_command():
    from RespondAnalyst import get_mapping_fields_command

    res = get_mapping_fields_command()
    expected = {
        "Respond Software Incident": {
            "feedback comments": "the user assigned outcome of a closed incident",
            "title": "incident title",
            "feedback outcome": "the outcome of the incident close",
        }
    }
    assert res.extract_mapping() == expected


def test_get_escalations_no_new(requests_mock, mocker):
    from RespondAnalyst import get_escalations_command

    escalation_query_response = {"data": {"newEscalations": []}}
    requests_mock.post(f"{BASE_URL}/graphql?tempId={API_TOKEN}&tenantId=dev1", json=escalation_query_response)
    args = {"incident_id": "1"}
    rest_client = mock_rest_client()
    requests_mock.get(f"{BASE_URL}/session/tenantIdMapping?tempId={API_TOKEN}", json={"dev1": "Tenant 1"})
    escalations_spy = mocker.spy(rest_client, "construct_and_send_new_escalations_query")
    res = get_escalations_command(rest_client, args)
    assert res == [{"Type": 1, "Contents": "No new escalations", "ContentsFormat": "text"}]
    assert escalations_spy.call_count == 1


def test_get_escalations_throws_exception(requests_mock, mocker):
    from RespondAnalyst import get_escalations_command

    args = {"tenant_id": "Tenant 1", "incident_id": "1"}
    rest_client = mock_rest_client()
    requests_mock.get(
        f"{BASE_URL}/session/tenantIdMapping?tempId={API_TOKEN}", json={"dev1": "Tenant 1", "dev1_tenant2": "Tenant 2"}
    )
    debug_spy = mocker.spy(demisto, "debug")
    mocker.patch.object(rest_client, "construct_and_send_new_escalations_query").side_effect = Exception("Unauthorized")
    with pytest.raises(Exception):
        get_escalations_command(rest_client, args)
    assert debug_spy.call_count == 1
    debug_spy.assert_called_with(
        "Error while getting escalation data in Respond incoming mirror for incident 1 Error message: Unauthorized"
    )