ExtraHop v2

ExtraHop Reveal(x) for Cortex XSOAR is a network detection and response solution that provides complete visibility of network communications at enterprise scale, real-time threat detections backed by machine learning, and guided investigation workflows that simplify response.

Network Security · ExtraHop Reveal(x)

Details

IDExtraHop v2
ProviderBain Capital Private Equity
CategoryNetwork Security
From Version6.5.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM EDR Cortex Cloud Cloud Runtime Security

README

ExtraHop Reveal(x) for Cortex XSOAR is a network detection and response solution that provides complete visibility of network communications at enterprise scale, real-time threat detections backed by machine learning, and guided investigation workflows that simplify response.

Configure ExtraHop Reveal(x) in Cortex

  1. Navigate to Settings > Integrations > Servers & Services.
  2. Search for ExtraHop Reveal(x).
  3. Click Add instance to create and configure a new integration instance.
Parameter Description Required
Name The name of the instance. True
Fetches incidents Select to enable this instance to fetch detection events. Otherwise, select Do not fetch. Each API call fetches a maximum of 200 detection events. True
Classifier Specifies the type of incident to be created for detection events ingested by this instance. False
Incident type Specifies the type of incident to be created for detection events ingested by this instance if a Classifier is not specified. False
Mapper Specifies how detection events ingested by this instance are mapped to Cortex XSOAR incident fields. False
On Cloud The type of ExtraHop system the integration will connect to. Select if connecting to ExtraHop Reveal(x) 360. Leave unselected if connecting to Reveal(x) Enterprise. False
URL The URL of the ExtraHop system this integration will connect to. True
API Key The API key required for authentication if connecting to ExtraHop Reveal(x) Enterprise. The API key is generated on your ExtraHop system. False
Client ID and Client Secret The credential pair required for authentication if connecting to ExtraHop Reveal(x) 360. The client ID and secret are generated on your ExtraHop system. False
Trust any certificate (not secure) Specifies whether to allow connections without verifying SSL certificate’s validity. False
Use system proxy settings Specifies whether to use XSOAR system proxy settings to connect to the API. False
First fetch time Specifies the beginning timestamp from which to start fetching detection events. False
Incidents Fetch Interval Specifies how often the instance fetches detection events. Because each API call fetches a maximum of 200 detection events, we recommend specifying one minute intervals to fetch all detection events. False
Advanced Filter Applies a filter to the list of detections based on a JSON-specific query.

Example for detections:
{
“categories”: [“sec.attack”],
“risk_score_min”: 51
}

If the categories and category are not specified, then categories will be set to [“sec.attack”]. The category field is deprecated by the API, so please use the categories field instead.
For a complete reference to the Extrahop detections filter fields, please refer to the ExtraHop REST API documentation at
https://docs.extrahop.com/current/rest-api-guide/
False
Do not use by default Select to disable running commands through the Cortex XSOAR CLI on this instance of the integration. False
Log Level Specifies the level of logging to enable for this instance of the integration. False
Run on Specifies whether to run the instance of the integration on a single engine. False
  1. Click Test to validate the URL, credentials, and connection.

Commands

You can run the following commands from the Cortex XSOAR CLI, as part of an automation, or in a playbook.
After you successfully run a command, a DBot message appears in the War Room with the command details.

  • Get detections from ExtraHop Reveal(x): extrahop-detections-list
  • Link an ExtraHop Reveal(x) detection to a Cortex XSOAR incident: extrahop-ticket-track
  • Search for devices in ExtraHop Reveal(x): extrahop-devices-search
  • Get all active network protocols for a device from ExtraHop Reveal(x): extrahop-protocols-get
  • Get all peers for a device from ExtraHop Reveal(x): extrahop-peers-get
  • Get a link to a live activity map in ExtraHop Reveal(x): extrahop-activity-map-get
  • Get all devices on the Advanced Analysis watchlist in ExtraHop Reveal(x): extrahop-watchlist-get
  • Add or remove devices from the Advanced Analysis watchlist in ExtraHop Reveal(x): extrahop-watchlist-edit
  • Add or remove a tag from devices in ExtraHop Reveal(x): extrahop-devices-tag
  • Get all alert rules from ExtraHop Reveal(x): extrahop-alert-rules-get
  • Create a new alert rule in ExtraHop Reveal(x): extrahop-alert-rule-create
  • Modify an alert rule in ExtraHop Reveal(x): extrahop-alert-rule-edit
  • Get metrics for specified objects from ExtraHop Reveal(x): extrahop-metrics-list
  • Search for specific packets in ExtraHop Reveal(x): extrahop-packets-search

extrahop-watchlist-get


Get all devices on the advanced analysis watchlist in ExtraHop Reveal(x).

Base Command

extrahop-watchlist-get

Input

There are no input arguments for this command.

Context Output

Path Type Description
ExtraHop.Device.Macaddr String The MAC Address of the device.
ExtraHop.Device.DeviceClass String The class of this device.
ExtraHop.Device.UserModTime Number The time of the most recent update, expressed in milliseconds since the epoch.
ExtraHop.Device.AutoRole String The role automatically detected by the ExtraHop system.
ExtraHop.Device.ParentId Number The ID of the parent device.
ExtraHop.Device.Vendor String The device vendor.
ExtraHop.Device.Analysis string The level of analysis received by the device.
ExtraHop.Device.DiscoveryId String The UUID for this device.
ExtraHop.Device.DefaultName String The default name for this device.
ExtraHop.Device.DisplayName String The display name of device.
ExtraHop.Device.OnWatchlist Boolean Whether the device is on the advanced analysis watch list.
ExtraHop.Device.ModTime Number The time of the most recent update, expressed in milliseconds since the epoch.
ExtraHop.Device.IsL3 Boolean Indicates whether the device is a layer 3 device.
ExtraHop.Device.Role String The role of the device.
ExtraHop.Device.DiscoverTime Number The time that the device was discovered.
ExtraHop.Device.Id Number The ID of the device.
ExtraHop.Device.Ipaddr4 String The IPv4 address for this device.
ExtraHop.Device.Vlanid Number The unique identifier for the VLAN associated with the device.
ExtraHop.Device.Ipaddr6 string The IPv6 address of the device.
ExtraHop.Device.NodeId number The node ID of the sensor associated with this device.
ExtraHop.Device.Description string A user customizable description of the device.
ExtraHop.Device.DnsName string The DNS name associated with the device.
ExtraHop.Device.DhcpName string The DHCP name associated with the device.
ExtraHop.Device.CdpName string The Cisco Discovery Protocol name associated with the device.
ExtraHop.Device.NetbiosName string The NetBIOS name associated with the device.
ExtraHop.Device.Url string Link to the device details page in ExtraHop Reveal(x).

Command example


#### Context Example

```json
{
    "ExtraHop": {
        "Device": [
            {
                "analysis": "advanced",
                "analysis_level": 2,
                "auto_role": "other",
                "critical": false,
                "default_name": "VM9",
                "device_class": "node",
                "dhcp_name": "test",
                "discover_time": 1635499650000,
                "discovery_id": "0000000000000000",
                "display_name": "test",
                "extrahop_id": "0000000000000000",
                "id": 25769803982,
                "ipaddr4": "0.0.0.0",
                "is_l3": false,
                "macaddr": "00:00:00:00:00:00",
                "mod_time": 1676638611398,
                "model": "vmware_vm",
                "node_id": 6,
                "on_watchlist": true,
                "role": "other",
                "url": "https://dummy_url/extrahop/#/metrics/devices/overview/",
                "user_mod_time": 1676290306316,
                "vendor": "VMware",
                "vlanid": 0
            },
            {
                "analysis": "advanced",
                "analysis_level": 2,
                "auto_role": "other",
                "critical": false,
                "default_name": "VM8",
                "device_class": "node",
                "discover_time": 1675318050000,
                "discovery_id": "0000000000000000",
                "display_name": "VM8",
                "extrahop_id": "0000000000000000",
                "id": 25769808133,
                "ipaddr4": "0.0.0.0",
                "is_l3": false,
                "last_seen_time": 1675319010000,
                "macaddr": "00:00:00:00:00:00",
                "mod_time": 1675425919964,
                "model": "vmware_vm",
                "node_id": 6,
                "on_watchlist": true,
                "role": "other",
                "url": "https://dummy_url/extrahop/#/metrics/devices/00000000000000000000000000000000.0000000000000000/overview/",
                "user_mod_time": 0,
                "vendor": "VMware",
                "vlanid": 0
            }
        ]
    }
}

Human Readable Output

Device Details

Display Name IP Address MAC Address Role Vendor URL
test 0.0.0.0 00:00:00:00:00:00 other VMware View Device in ExtraHop
VM 8 0.0.0.0 00:00:00:00:00:00 other VMware View Device in ExtraHop

extrahop-peers-get


Get all peers for a device from ExtraHop Reveal(x).

Base Command

extrahop-peers-get

Input

Argument Name Description Required
ip_or_id The IP address or ExtraHop API ID of the source device to get peer devices. Required
query_from The beginning timestamp of the time range the query will search, expressed in milliseconds since the epoch. A negative value is evaluated relative to the current time. The default unit for a negative value is milliseconds, but other units can be specified with one of the following unit suffixes: ms, s, m, h, d, w, M, y. See https://docs.extrahop.com/current/rest-api-guide/#supported-time-units- for more details on supported time units and suffixes. Default is -30m. Optional
query_until The ending timestamp of the time range the query will search, expressed in milliseconds since the epoch. 0 indicates the time of the request. A negative value is evaluated relative to the current time. The default unit for a negative value is milliseconds, but other units can be specified with one of the following unit suffixes: ms, s, m, h, d, w, M, y. See https://docs.extrahop.com/current/rest-api-guide/#supported-time-units- for more details on supported time units and suffixes. Optional
peer_role The role of the peer device in relation to the origin device. Possible values are: any, client, server. Default is any. Optional
protocol A filter to only return peers that the source device has communicated with over this protocol. If no value is set, the object includes any protocol. Possible values are: any, AAA, ActiveMQ, AJP, amf, CIFS, DB, DHCP, DICOM, DNS, FIX, FTP, HL7, HTTP, IBMMQ, ICA, IKE/ISAKMP, IMAP, IPFIX, IPsec NAT-T, IRC, iSCSI, Kerberos, L2TP, LDAP, lync-compress, memcache, Modbus, MongoDB, MSMQ, MSN, MSRPC, NetFlow, NFS, NTP, OpenVPN, PCoIP, Perforce, POP3, RDP, Redis, RFB, RTCP, RTP, sFlow, SIP, SMPP, SMTP, SNMP, SSH, SSL, Syslog, TCP, telnet, UDP, WebSocket. Default is any. Optional

Context Output

Path Type Description
ExtraHop.Device.Macaddr String The MAC address of the device.
ExtraHop.Device.DeviceClass String The class of the device.
ExtraHop.Device.UserModTime Number The time of the most recent update, expressed in milliseconds since the epoch.
ExtraHop.Device.AutoRole String The role automatically detected by the ExtraHop system.
ExtraHop.Device.ParentId Number The ID of the parent device.
ExtraHop.Device.Vendor String The device vendor.
ExtraHop.Device.Analysis string The level of analysis received by the device.
ExtraHop.Device.DiscoveryId String The UUID given of the device.
ExtraHop.Device.DefaultName String The default name for this device.
ExtraHop.Device.DisplayName String The display name of device.
ExtraHop.Device.OnWatchlist Boolean Whether the device is on the advanced analysis watch list.
ExtraHop.Device.ModTime Number The time of the most recent update, expressed in milliseconds since the epoch.
ExtraHop.Device.IsL3 Boolean Indicates whether the device is a layer 3 device.
ExtraHop.Device.Role String The role of the device.
ExtraHop.Device.DiscoverTime Number The time that the device was discovered.
ExtraHop.Device.Id Number The ID of the device.
ExtraHop.Device.Ipaddr4 String The IPv4 address for this device.
ExtraHop.Device.Vlanid Number The unique identifier for the VLAN associated with the device.
ExtraHop.Device.Ipaddr6 string The IPv6 address of the device.
ExtraHop.Device.NodeId number The node ID of the sensor associated with the device.
ExtraHop.Device.Description string A user customizable description of the device.
ExtraHop.Device.DnsName string The DNS name associated with the device.
ExtraHop.Device.DhcpName string The DHCP name associated with the device.
ExtraHop.Device.CdpName string The Cisco Discovery Protocol name associated with the device.
ExtraHop.Device.NetbiosName string The NetBIOS name associated with the device.
ExtraHop.Device.Url string Link to the device details page in ExtraHop Reveal(x).

Command example

!extrahop-peers-get ip_or_id=0.0.0.0 peer_role=server protocol=any query_from=-60m query_until=0

Context Example

{
    "ExtraHop": {
        "Device": {
            "analysis": "advanced",
            "analysis_level": 1,
            "auto_role": "gateway",
            "critical": true,
            "default_name": "Cisco Meraki 23D27A",
            "device_class": "gateway",
            "discover_time": 1655102100000,
            "discovery_id": "0000000000000000",
            "display_name": "Cisco Meraki 23D27A",
            "extrahop_id": "0000000000000000",
            "id": 25769805776,
            "ipaddr4": "0.0.0.0",
            "is_l3": false,
            "macaddr": "00:00:00:00:00:00",
            "mod_time": 1676638911830,
            "node_id": 6,
            "on_watchlist": false,
            "role": "gateway",
            "server_protocols": [
                "UDP:NTP"
            ],
            "url": "https://dummy_url/extrahop/#/metrics/devices/overview/",
            "user_mod_time": 0,
            "vendor": "Cisco Meraki",
            "vlanid": 0
        }
    }
}

Human Readable Output

Device Details

Display Name IP Address MAC Address Role Protocols URL Vendor
Cisco Meraki 23D27A 0.0.0.0 00:00:00:00:00:00 gateway Server: UDP:NTP View Device in ExtraHop Cisco Meraki

extrahop-devices-search


Search for devices in ExtraHop Reveal(x).

Base Command

extrahop-devices-search

Input

Argument Name Description Required
name The name of the device. This searches for matches on all ExtraHop Reveal(x) name fields (DHCP, DNS, NetBIOS, Cisco Discovery Protocol, etc). Optional
ip The IP address of the device. Optional
mac The MAC address of the device. Optional
role The role of the device. Possible values are: db_server, dhcp_server, dns_server, file_server, firewall, gateway, http_server, domain_controller, web_proxy, load_balancer, pc, medical_device, mobile_device, printer, scanner, custom, voip_phone, other. Optional
software The OS of the device. Possible values are: android, apple_ios, arista_eos, cisco_ios, cisco_nx-os, chrome_os, linux, mac_os, windows, windows_server, windows_server_2008, windows_server_2008_r2, windows_server_2012, windows_server_2012_r2, windows_server_2016, windows_vista, windows_7, windows_8, windows_8.1, windows_10. Optional
tag A tag present on the device. Optional
vendor The vendor of the device, based on MAC address via OUI lookup. Possible values are: alcatel-lucent, apple, arista, asus, brother, canon, cisco, cisco-linksys, citrix, dell, dellemc, d-link, emc, f5, google, hp, htc, huawei, ibm, juniper, kyocera, microsoft, netapp, netgear, nokia, nortel, oracle, paloalto, samsung, 3com, toshiba, virtualbox, vmware, zte. Optional
discover_time The time that device was first seen by the ExtraHop system, expressed in milliseconds since the epoch. A negative value is evaluated relative to the current time. The default unit for a negative value is milliseconds, but other units can be specified with the following unit suffixes: ms, s, m, h, d, w, M, y. For example, to look one day back enter -1d or -24h. See https://docs.extrahop.com/current/rest-api-guide/#supported-time-units- for more details on supported time units and suffixes. Optional
vlan The VLAN ID of the Virtual LAN that the device is on. Optional
activity The activity of the device. Possible values are: aaa_client, aaa_server, ajp_client, ajp_server, amf_client, amf_server, cifs_client, cifs_server, db_client, db_server, dhcp_client, dhcp_server, dicom_client, dicom_server, dns_client, dns_server, fix_client, fix_server, ftp_client, ftp_server, hl7_client, hl7_server, http_client, http_server, ibmmq_client, ibmmq_server, ica_client, ica_server, icmp, iscsi_client, iscsi_server, kerberos_client, kerberos_server, ldap_client, ldap_server, llmnr_client, llmnr_server, memcache_client, memcache_server, modbus_client, modbus_server, mongo_client, mongo_server, msmq, nbns_client, nbns_server, nfs_client, nfs_server, pcoip_client, pcoip_server, pop3_client, pop3_server, rdp_client, rdp_server, redis_client, redis_server, rfb_client, rfb_server, rpc_client, rpc_server, rtcp, rtp, scanner, sip_client, sip_server, smpp_client, smpp_server, smtp_client, smtp_server, ssh_client, ssh_server, ssl_client, ssl_server, tcp, telnet_client, telnet_server, udp, websocket_client, websocket_server, wsman_client, wsman_server. Optional
operator The compare method applied when matching the fields against their values. For example, to find devices with names that begin with ‘SEA1’ (set name=SEA1, operator=startswith). Possible values are: >, <, <=, >=, =, !=, startswith, exists, not_exists, ~, !~. Default is =. Optional
match_type The match operator to use when chaining the search fields together. For example, to find all HTTP servers running Windows on the network (set match_type=and, role=http_server, software=windows). Possible values are: and, or, not. Default is and. Optional
active_from The beginning timestamp for the request. Return only devices active after this time. Time is expressed in milliseconds since the epoch. 0 indicates the time of the request. A negative value is evaluated relative to the current time. The default unit for a negative value is milliseconds, but other units can be specified with one of the following unit suffixes: ms, s, m, h, d, w, M, y. See https://docs.extrahop.com/current/rest-api-guide/#supported-time-units- for more details on supported time units and suffixes. Optional
active_until The ending timestamp for the request. Return only devices active before this time. Time is expressed in milliseconds since the epoch. 0 indicates the time of the request. A negative value is evaluated relative to the current time. The default unit for a negative value is milliseconds, but other units can be specified with one of the following unit suffixes: ms, s, m, h, d, w, M, y. See https://docs.extrahop.com/current/rest-api-guide/#supported-time-units- for more details on supported time units and suffixes. Optional
limit The maximum number of devices to return. Default is 10. Optional
l3_only Only returns layer 3 devices by filtering out any layer 2 parent devices. Possible values are: true, false. Default is true. Optional

Context Output

Path Type Description
ExtraHop.Device.Macaddr String The MAC address of the device.
ExtraHop.Device.DeviceClass String The class of the device.
ExtraHop.Device.UserModTime Number The time of the most recent update, expressed in milliseconds since the epoch.
ExtraHop.Device.AutoRole String The role automatically detected by the ExtraHop system.
ExtraHop.Device.ParentId Number The ID of the parent device.
ExtraHop.Device.Vendor String The device vendor.
ExtraHop.Device.Analysis string The level of analysis received by the device.
ExtraHop.Device.DiscoveryId String The UUID of the device.
ExtraHop.Device.DefaultName String The default name of the device.
ExtraHop.Device.DisplayName String The display name of device.
ExtraHop.Device.OnWatchlist Boolean Whether the device is on the advanced analysis watch list.
ExtraHop.Device.ModTime Number The time of the most recent update, expressed in milliseconds since the epoch.
ExtraHop.Device.IsL3 Boolean Indicates whether the device is a layer 3 device.
ExtraHop.Device.Role String The role of the device.
ExtraHop.Device.DiscoverTime Number The time that the device was discovered.
ExtraHop.Device.Id Number The ID of the device.
ExtraHop.Device.Ipaddr4 String The IPv4 address of the device.
ExtraHop.Device.Vlanid Number The ID of the VLAN associated with the device.
ExtraHop.Device.Ipaddr6 String The IPv6 address of the device.
ExtraHop.Device.NodeId Number The node ID of the sensor associated with the device.
ExtraHop.Device.Description String A user customizable description of the device.
ExtraHop.Device.DnsName String The DNS name associated with the device.
ExtraHop.Device.DhcpName String The DHCP name associated with the device.
ExtraHop.Device.CdpName String The Cisco Discovery Protocol name associated with the device.
ExtraHop.Device.NetbiosName String The NetBIOS name associated with the device.
ExtraHop.Device.Url String Link to the device details page in ExtraHop Reveal(x).

Command example

!extrahop-devices-search activity=aaa_client discover_time=-10m ip=0.0.0.0 l3_only=true limit=2 mac=00:00:00:00:00:00 match_type=or name=DNS operator=!= role=file_server software=linux tag=tag1 vendor=cisco

Context Example

{
    "ExtraHop": {
        "Device": [
            {
                "analysis": "advanced",
                "analysis_level": 1,
                "auto_role": "other",
                "critical": false,
                "default_name": "VMware 8",
                "device_class": "node",
                "discover_time": 1676633640000,
                "discovery_id": "0000000000000000",
                "display_name": "VMware 8",
                "extrahop_id": "0000000000000000",
                "id": 25769808421,
                "ipaddr4": "0.0.0.0",
                "is_l3": false,
                "last_seen_time": 1676634840000,
                "macaddr": "00:00:00:00:00:00",
                "mod_time": 1676634890174,
                "model": "vmware_vm",
                "node_id": 6,
                "on_watchlist": false,
                "role": "other",
                "url": "https://dummy_url/extrahop/#/metrics/devices/overview/",
                "user_mod_time": 0,
                "vendor": "VMware",
                "vlanid": 0
            },
            {
                "analysis": "advanced",
                "analysis_level": 1,
                "auto_role": "other",
                "critical": false,
                "default_name": "VMware 3",
                "device_class": "node",
                "discover_time": 1676614620000,
                "discovery_id": "0000000000000000",
                "display_name": "VMware 3",
                "extrahop_id": "0000000000000000",
                "id": 25769808417,
                "ipaddr4": "0.0.0.0",
                "is_l3": false,
                "last_seen_time": 1676616960000,
                "macaddr": "00:00:00:00:00:00",
                "mod_time": 1676616977189,
                "model": "vmware_vm",
                "node_id": 6,
                "on_watchlist": false,
                "role": "other",
                "url": "https://dummy_url/extrahop/#/metrics/devices/overview/",
                "user_mod_time": 0,
                "vendor": "VMware",
                "vlanid": 0
            }
        ]
    }
}

Human Readable Output

Device Details

Display Name IP Address MAC Address Role Vendor URL
VMware 8 0.0.0.0 00:00:00:00:00:00 other VMware View Device in ExtraHop
VMware 3 0.0.0.0 00:00:00:00:00:00 other VMware View Device in ExtraHop

extrahop-protocols-get


Get all active network protocols for a device from ExtraHop Reveal(x).

Base Command

extrahop-protocols-get

Input

Argument Name Description Required
ip_or_id The IP address or ExtraHop API ID of the device to get all active network protocols. Required
query_from The beginning timestamp of the time range the query will search, expressed in milliseconds since the epoch. A negative value is evaluated relative to the current time. The default unit for a negative value is milliseconds, but other units can be specified with one of the following unit suffixes: ms, s, m, h, d, w, M, y. See https://docs.extrahop.com/current/rest-api-guide/#supported-time-units- for more details on supported time units and suffixes. Default is -30m. Optional
query_until The ending timestamp of the time range the query will search, expressed in milliseconds since the epoch. 0 indicates the time of the request. A negative value is evaluated relative to the current time. The default unit for a negative value is milliseconds, but other units can be specified with one of the following unit suffixes: ms, s, m, h, d, w, M, y. See https://docs.extrahop.com/current/rest-api-guide/#supported-time-units- for more details on supported time units and suffixes. Optional

Context Output

Path Type Description
ExtraHop.Device.Macaddr String The MAC address of the device.
ExtraHop.Device.DeviceClass String The class of the device.
ExtraHop.Device.UserModTime Number The time of the most recent update, expressed in milliseconds since the epoch.
ExtraHop.Device.AutoRole String The role automatically detected by the ExtraHop system.
ExtraHop.Device.ParentId Number The ID of the parent device.
ExtraHop.Device.Vendor String The device vendor.
ExtraHop.Device.Analysis String The level of analysis received by the device.
ExtraHop.Device.DiscoveryId String The UUID of the device.
ExtraHop.Device.DefaultName String The default name of the device.
ExtraHop.Device.DisplayName String The display name of device.
ExtraHop.Device.OnWatchlist Boolean Whether the device is on the advanced analysis watch list.
ExtraHop.Device.ModTime Number The time of the most recent update, expressed in milliseconds since the epoch.
ExtraHop.Device.IsL3 Boolean Indicates whether the device is a layer 3 device.
ExtraHop.Device.Role String The role of the device.
ExtraHop.Device.DiscoverTime Number The time that the device was discovered.
ExtraHop.Device.Id Number The ID of the device.
ExtraHop.Device.Ipaddr4 String The IPv4 address of the device.
ExtraHop.Device.Vlanid Number The ID of the VLAN associated with the device.
ExtraHop.Device.Ipaddr6 String The IPv6 address of the device.
ExtraHop.Device.NodeId Number The node ID of the sensor associated with the device.
ExtraHop.Device.Description String A user customizable description of the device.
ExtraHop.Device.DnsName String The DNS name associated with the device.
ExtraHop.Device.DhcpName String The DHCP name associated with the device.
ExtraHop.Device.CdpName String The Cisco Discovery Protocol name associated with the device.
ExtraHop.Device.NetbiosName String The NetBIOS name associated with the device.
ExtraHop.Device.Url String Link to the device details page in ExtraHop Reveal(x).
ExtraHop.Device.ClientProtocols String The list of protocols the peer device is communicating on as a client.
ExtraHop.Device.ServerProtocols String The list of protocols the peer device is communicating on as a server.

Command example

!extrahop-protocols-get ip_or_id=0.0.0.0 query_from=-20m query_until=0

Context Example

{
    "ExtraHop": {
        "Device": {
            "analysis": "advanced",
            "analysis_level": 2,
            "auto_role": "other",
            "client_protocols": [
                "UDP:NTP"
            ],
            "critical": false,
            "default_name": "VMware 9",
            "device_class": "node",
            "dhcp_name": "test",
            "discover_time": 1635499650000,
            "discovery_id": "0000000000000000",
            "display_name": "test",
            "extrahop_id": "0000000000000000",
            "id": 10000000000,
            "ipaddr4": "0.0.0.0",
            "is_l3": false,
            "macaddr": "00:00:00:00:00:000",
            "mod_time": 1676638611398,
            "model": "vmware_vm",
            "node_id": 6,
            "on_watchlist": true,
            "role": "other",
            "url": "https://dummy_url/extrahop/#/metrics/devices/overview/",
            "user_mod_time": 1676290306316,
            "vendor": "VMware",
            "vlanid": 0
        }
    }
}

Human Readable Output

Device Activity Found

Display Name IP Address MAC Address Protocols (Client) Role Vendor URL
test 0.0.0.0 00:00:00:00:00:000 UDP:NTP other VMware View Device in ExtraHop

extrahop-activity-map-get


Get a link to a live activity map in ExtraHop Reveal(x).

Base Command

extrahop-activity-map-get

Input

Argument Name Description Required
ip_or_id The IP address or ExtraHop API ID of the source device to get an activity map. Required
time_interval The time interval of the live activity map, expressed as the “Last” 30 minutes. For example, specify a value of 30 minutes to get an activity map showing the time range of the last 30 minutes. This field is ignored if from_time and until_time are provided. Possible values are: 30 minutes, 6 hours, 1 day, 1 week. Default is 30 minutes. Optional
from_time The beginning timestamp of a fixed time range the activity map will display, expressed in seconds since the epoch. Optional
until_time The ending timestamp of a fixed time range the activity map will display, expressed in seconds since the epoch. Optional
peer_role The role of the peer devices in relation to the source device. For example, specifying a peer_role of client will show All Clients communicating with the source device. Additionally specifying a protocol of HTTP will result in further filtering and only showing HTTP Clients communicating with the source device. Possible values are: any, client, server. Default is any. Optional
protocol The protocol over which the source device is communicating. For example, specifying a protocol of HTTP show only HTTP Clients and HTTP Servers communicating with the source device. Additionally specifying a peer_role of client will result in further filtering and only showing HTTP Clients communicating with the source device. Possible values are: any, AAA, ActiveMQ, AJP, amf, CIFS, DB, DHCP, DICOM, DNS, FIX, FTP, HL7, HTTP, IBMMQ, ICA, IKE/ISAKMP, IMAP, IPFIX, IPsec NAT-T, IRC, iSCSI, Kerberos, L2TP, LDAP, lync-compress, memcache, Modbus, MongoDB, MSMQ, MSN, MSRPC, NetFlow, NFS, NTP, OpenVPN, PCoIP, Perforce, POP3, RDP, Redis, RFB, RTCP, RTP, sFlow, SIP, SMPP, SMTP, SNMP, SSH, SSL, Syslog, TCP, telnet, UDP, WebSocket. Default is any. Optional

Context Output

Path Type Description
ExtraHop.ActivityMap.url String The link to a visual activity map in ExtraHop Reveal(x).

Command example

!extrahop-activity-map-get ip_or_id=0.0.0.0 peer_role=server protocol=any time_interval="30 minutes"

Context Example

{
    "ExtraHop": {
        "ActivityMap": {
            "url": "https://dummy_url/extrahop/#/activitymaps?appliance_id=00000000000000000000000000000000&discovery_id=0000000000000000&from=30&interval_type=MIN&object_type=device&protocol=any&role=server&until=0"
        }
    }
}

Human Readable Output

View Live Activity Map in ExtraHop

extrahop-alert-rules-get


Get all alert rules from ExtraHop Reveal(x).

Base Command

extrahop-alert-rules-get

Input

There are no input arguments for this command.

Context Output

Path Type Description
ExtraHop.Alert.Operator String The logical operator applied when comparing the value of the operand field to alert conditions.
ExtraHop.Alert.FieldName String The name of the monitored metric.
ExtraHop.Alert.NotifySnmp Boolean Indicates whether to send an SNMP trap when an alert is generated.
ExtraHop.Alert.Operand String The value to compare against alert conditions.
ExtraHop.Alert.IntervalLength Number The length of the alert interval, expressed in seconds.
ExtraHop.Alert.Author String The name of the user that created the alert.
ExtraHop.Alert.Name String The unique, friendly name for the alert.
ExtraHop.Alert.FieldName2 String The second monitored metric when applying a ratio.
ExtraHop.Alert.RefireInterval Number The time interval in which alert conditions are monitored, expressed in seconds.
ExtraHop.Alert.ModTime Number The time of the most recent update, expressed in milliseconds since the epoch.
ExtraHop.Alert.Units String The interval in which to evaluate the alert condition.
ExtraHop.Alert.ApplyAll Boolean Indicates whether the alert is assigned to all available data sources.
ExtraHop.Alert.Type String The type of alert.
ExtraHop.Alert.FieldOp String The type of comparison between the “field_name” and “field_name2” fields when applying a ratio.
ExtraHop.Alert.Id Number The unique identifier for the alert.
ExtraHop.Alert.Disabled Boolean Indicates whether the alert is disabled.
ExtraHop.Alert.Description String An optional description for the alert.
ExtraHop.Alert.Severity Number The severity level of the alert.
ExtraHop.Alert.StatName String The statistic name for the alert.

Command example


#### Context Example

```json
{
    "ExtraHop": {
        "Alert": [
            {
                "apply_all": false,
                "author": "ExtraHop",
                "description": "Alert triggered when ratio of DB errors is greater than 1%.",
                "disabled": false,
                "field_name": "rsp_error",
                "field_name2": "rsp",
                "field_op": "/",
                "id": 15,
                "interval_length": 30,
                "mod_time": 1617887147538,
                "name": "DB Error Ratio - Orange",
                "notify_snmp": false,
                "operand": "0.01",
                "operator": ">",
                "refire_interval": 300,
                "severity": 3,
                "stat_name": "extrahop.application.db",
                "type": "threshold",
                "units": "none"
            },
            {
                "apply_all": false,
                "author": "ExtraHop",
                "description": "Alert triggered when ratio of DB errors is greater than 5%.",
                "disabled": false,
                "field_name": "rsp_error",
                "field_name2": "rsp",
                "field_op": "/",
                "id": 14,
                "interval_length": 30,
                "mod_time": 1617887147615,
                "name": "DB Error Ratio - Red",
                "notify_snmp": false,
                "operand": "0.05",
                "operator": ">",
                "refire_interval": 300,
                "severity": 1,
                "stat_name": "extrahop.application.db",
                "type": "threshold",
                "units": "none"
            },
            {
                "apply_all": false,
                "author": "ExtraHop",
                "description": "Alert triggered when ratio of DNS errors is greater than 0.1%.",
                "disabled": false,
                "field_name": "rsp_error",
                "field_name2": "rsp",
                "field_op": "/",
                "id": 19,
                "interval_length": 30,
                "mod_time": 1617887147785,
                "name": "DNS Error Ratio - Yellow",
                "notify_snmp": false,
                "operand": "0.001",
                "operator": ">",
                "refire_interval": 300,
                "severity": 5,
                "stat_name": "extrahop.application.dns",
                "type": "threshold",
                "units": "none"
            }
        ]
    }
}

Human Readable Output

Found 3 Alert(s)

Apply All Author Description Disabled Field Name Field Name2 Field Op Id Interval Length Mod Time Name Notify Snmp Operand Operator Refire Interval Severity Stat Name Type Units
false ExtraHop Alert triggered when ratio of DB errors is greater than 1%. false rsp_error rsp / 15 30 1617887147538 DB Error Ratio - Orange false 0.01 > 300 3 extrahop.application.db threshold none
false ExtraHop Alert triggered when ratio of DB errors is greater than 5%. false rsp_error rsp / 14 30 1617887147615 DB Error Ratio - Red false 0.05 > 300 1 extrahop.application.db threshold none
false ExtraHop Alert triggered when ratio of DNS errors is greater than 0.1%. false rsp_error rsp / 19 30 1617887147785 DNS Error Ratio - Yellow false 0.001 > 300 5 extrahop.application.dns threshold none

extrahop-packets-search


Search for specific packets in ExtraHop Reveal(x).

Base Command

extrahop-packets-search

Input

Argument Name Description Required
output The output format. A pcap file, A keylog.txt file that can be loaded in wireshark to decode ssl packets, or a zip file containing both a packets.pcap and keylog.txt. Possible values are: pcap, keylog_txt, zip. Default is pcap. Optional
limit_bytes The maximum number of bytes to return. Default is 10MB. Optional
limit_search_duration The maximum amount of time to run the packet search. The default unit is milliseconds, but other units can be specified with a unit suffix. Default is 5m. Optional
query_from The beginning timestamp of the time range the search will include, expressed in milliseconds since the epoch. A negative value specifies that the search will begin with packets captured at a time in the past relative to the current time. For example, specify -10m to begin the search with packets captured 10 minutes before the time of the request. The default unit for a negative value is milliseconds, but other units can be specified with one of the following unit suffixes: ms, s, m, h, d, w, M, y. See https://docs.extrahop.com/current/rest-api-guide/#supported-time-units- for more details on supported time units and suffixes. Default is -10m. Optional
query_until The ending timestamp of the time range the search will include, expressed in milliseconds since the epoch. A 0 value specifies that the search will end with packets captured at the time of the search. A negative value specifies that the search will end with packets captured at a time in the past relative to the current time. For example, specify -5m to end the search with packets captured 5 minutes before the time of the request. The default unit for a negative value is milliseconds, but other units can be specified with one of the following unit suffixes: ms, s, m, h, d, w, M, y. See https://docs.extrahop.com/current/rest-api-guide/#supported-time-units- for more details on supported time units and suffixes. Optional
bpf The Berkeley Packet Filter (BPF) syntax for the packet search. Optional
ip1 Returns packets sent to or received by the specified IP address. Optional
port1 Returns packets sent from or received on the specified port. Optional
ip2 Returns packets sent to or received by the specified IP address. Optional
port2 Returns packets sent from or received on the specified port. Optional

Context Output

Path Type Description
File.Size Number The size of the file.
File.SHA1 String The SHA1 hash of the file.
File.SHA256 String The SHA256 hash of the file.
File.Name String The name of the file.
File.SSDeep String The SSDeep hash of the file.
File.EntryID String The entry ID of the file.
File.Info String File information.
File.Type String The file type.
File.MD5 String The MD5 hash of the file.
File.Extension String The file extension.

Command example

!extrahop-packets-search ip1=0.0.0.0 ip2=0.0.0.0 limit_bytes=10MB limit_search_duration=10m output=pcap port1=8000 port2=8000 query_from=-15m query_until=0

Human Readable Output

Uploaded file: extrahop 2022-12-15 21.12.29 to 21.27.29 IST.pcapDownload.

Property Value
Type pcap
Size 1,122,020 bytes
Info data
MD5 710737f2d9874690f130da14da38e7cb
SHA1 a89d4696c11ee0a8890d8f4effba8fad891cf05d
SHA256 433f238d350d8eb19979f0f513974d97b9e9f3445f99deb75c0a1f46e54de111
SHA512 fbb914a425d324e4d50bdcf15fc31499720e48d9242005c796d91c345dcb44e1f2fb1435d6bf44c89e0f8256dbae43638f5d8175872bcd29e5bf4fbcba4124cb
SSDeep 12288:WzC9IOFcF8jgBXx00uMOsOFtKu1R4mF48f6G2GeXCuX:Wgo8cNx3QsODKugmnfjcPX

extrahop-devices-tag


Add or remove a tag from devices in ExtraHop Reveal(x).

Base Command

extrahop-devices-tag

Input

Argument Name Description Required
tag The case-sensitive value of the tag. Required
add The list of IP addresses or ExtraHop API IDs of the devices to tag, comma separated. Optional
remove The list of IP addresses or ExtraHop API IDs of the devices to remove the tag from, comma separated. Optional

Context Output

There is no context output for this command.

Command example

!extrahop-devices-tag tag=MyTag add=0.0.0.0 remove=0.0.0.0

Human Readable Output

Successfully tagged untagged the device/s.

extrahop-alert-rule-create


Create a new alert rule in ExtraHop Reveal(x).

Base Command

extrahop-alert-rule-create

Input

Argument Name Description Required
apply_all Indicates whether the alert is assigned to all available data sources. Possible values are: true, false. Required
disabled Indicates whether the alert is disabled. Possible values are: true, false. Required
field_name The name of the monitored metric. Only applicable to threshold alerts. Optional
field_name2 The second monitored metric when applying a ratio. Only applicable to threshold alerts. Optional
field_op The type of comparison between the field_name and field_name2 fields when applying a ratio. Only applicable to threshold alerts. Possible values are: /, null. Optional
interval_length The length of the alert interval, expressed in seconds. Only applicable to threshold alerts. Possible values are: 30, 60, 120, 300, 600, 900, 1200, 1800. Default is 30. Optional
name The unique, friendly name for the alert. Required
notify_snmp Indicates whether to send an SNMP trap when an alert is generated. Possible values are: true, false. Required
object_type The type of metric source monitored by the alert configuration. Only applicable to detection alerts. Possible values are: application, device. Optional
operand The value to compare against alert conditions. The compare method is specified by the value of the operator field. Only applicable to threshold alerts. Optional
operator The logical operator applied when comparing the value of the operand field to alert conditions. Only applicable to threshold alerts. Possible values are: ==, >, <, >=, <=. Optional
param The first alert parameter, which is either a key pattern or a data point. Only applicable to threshold alerts. Optional
param2 The second alert parameter, which is either a key pattern or a data point. Only applicable to threshold alerts. Optional
protocols The list of monitored protocols. Only applicable to detection alerts. Optional
refire_interval The time interval in which alert conditions are monitored, expressed in seconds. Possible values are: 300, 600, 900, 1800, 3600, 7200, 14400. Required
severity The severity level of the alert, which is displayed in the Alert History, email notifications, and SNMP traps. Possible values are: 0, 1, 2, 3, 4, 5, 6, 7. Required
stat_name The statistic name for the alert. Only applicable to threshold alerts. Optional
type The type of alert. Possible values are: detection, threshold. Required
units The interval in which to evaluate the alert condition. Only applicable to threshold alerts. Possible values are: none, period, 1 sec, 1 min, 1 hr. Optional

Context Output

There is no context output for this command.

Command example

!extrahop-alert-rule-create apply_all=true interval_length=30 disabled=false name="test10" notify_snmp=false refire_interval=300 severity=4 type=detection object_type=device protocols="udp"

Human Readable Output

Successfully created alert rule.

extrahop-ticket-track


Link an ExtraHop Reveal(x) detection to a Cortex XSOAR incident.

Base Command

extrahop-ticket-track

Input

Argument Name Description Required
incident_id The ID of the Cortex XSOAR incident to track. Required
detection_id The ID of the ExtraHop Reveal(x) detection to track. Required
incident_owner Owner of the incident. Optional
incident_status Status of the incident. Possible values are: 0, 1, 2, 3. Optional
incident_close_reason Reason the incident was closed. Optional

Context Output

Path Type Description
ExtraHop.TicketId String Cortex XSOAR incident ID successfully tracked to the ExtraHop Reveal(x) detection.

Command example

!extrahop-ticket-track detection_id=1234 incident_id=1 incident_owner=John incident_status=1

Context Example

{
    "ExtraHop": {
        "ExtraHop": {
            "TicketId": "1"
        }
    }
}

Human Readable Output

Successfully linked detection(1234) with incident(1)

extrahop-alert-rule-edit


Modify an alert rule in ExtraHop Reveal(x).

Base Command

extrahop-alert-rule-edit

Input

Argument Name Description Required
alert_id The unique identifier for the alert. Required
apply_all Indicates whether the alert is assigned to all available data sources. Possible values are: true, false. Required
disabled Indicates whether the alert is disabled. Possible values are: true, false. Required
field_name The name of the monitored metric. Only applicable to threshold alerts. Optional
field_name2 The second monitored metric when applying a ratio. Only applicable to threshold alerts. Optional
field_op The type of comparison between the field_name and field_name2 fields when applying a ratio. Only applicable to threshold alerts. Possible values are: /, null. Optional
interval_length The length of the alert interval, expressed in seconds. Only applicable to threshold alerts. Possible values are: 30, 60, 120, 300, 600, 900, 1200, 1800. Default is 30. Optional
name The unique, friendly name for the alert. Required
notify_snmp Indicates whether to send an SNMP trap when an alert is generated. Possible values are: true, false. Required
object_type The type of metric source monitored by the alert configuration. Only applicable to detection alerts. Possible values are: application, device. Optional
operand The value to compare against alert conditions. The compare method is specified by the value of the operator field. Only applicable to threshold alerts. Optional
operator The logical operator applied when comparing the value of the operand field to alert conditions. Only applicable to threshold alerts. Possible values are: ==, >, <, >=, <=. Optional
param The first alert parameter, which is either a key pattern or a data point. Only applicable to threshold alerts. Optional
param2 The second alert parameter, which is either a key pattern or a data point. Only applicable to threshold alerts. Optional
protocols The list of monitored protocols. Only applicable to detection alerts. Optional
refire_interval The time interval in which alert conditions are monitored, expressed in seconds. Possible values are: 300, 600, 900, 1800, 3600, 7200, 14400. Required
severity The severity level of the alert, which is displayed in the Alert History, email notifications, and SNMP traps. Possible values are: 0, 1, 2, 3, 4, 5, 6, 7. Required
stat_name The statistic name for the alert. Only applicable to threshold alerts. Optional
type The type of alert. Possible values are: detection, threshold. Required
units The interval in which to evaluate the alert condition. Only applicable to threshold alerts. Possible values are: none, period, 1 sec, 1 min, 1 hr. Optional

Context Output

There is no context output for this command.

Command example

!extrahop-alert-rule-edit interval_length=30 alert_id=36 apply_all=true disabled=false name="t127" notify_snmp=false refire_interval=300 severity=4 type=detection protocols="udp" object_type=device

Human Readable Output

Successfully updated alert rule.

extrahop-watchlist-edit


Add or remove devices from the advanced analysis watchlist in ExtraHop Reveal(x).

Base Command

extrahop-watchlist-edit

Input

Argument Name Description Required
add The list of IP addresses or ExtraHop API IDs of the devices to add, comma separated. Optional
remove The list of IP addresses or ExtraHop API IDs of the devices to remove, comma separated. Optional

Context Output

There is no context output for this command.

Command example

!extrahop-watchlist-edit add=0.0.0.0 remove=0.0.0.0

Human Readable Output

Successfully added new devices(0.0.0.0) in the watchlist
Successfully removed devices(0.0.0.0) from the watchlist

extrahop-metrics-list


Get metrics for specified objects from ExtraHop Reveal(x).

Base Command

extrahop-metrics-list

Input

Argument Name Description Required
cycle The aggregation period for metrics.
Supported values: “auto”, “1sec”, “30sec”, “5min”, “1hr”, “24hr”. Possible values are: auto, 1sec, 30sec, 5min, 1hr, 24hr.
Required
from_time The beginning timestamp for the request. Return only metrics collected after this time. Time is expressed in milliseconds since the epoch. 0 indicates the time of the request. A negative value is evaluated relative to the current time. The default unit for a negative value is milliseconds, but other units can be specified with a unit suffix.
For example, to request devices active in the last 30 minutes, specify the following parameter value: “-30m”.
Required
metric_category The group of metrics that are searchable in the metric catalog. Required
object_ids The list of numeric values that represent unique identifiers. Unique identifiers can be retrieved through the /networks, /devices, /applications, /vlans, /devicegroups, /activitygroups, and /appliances resources. For system health metrics, specify the ID of the sensor or console and set the object_type parameter to “system”. Required
object_type Indicates the object type of unique identifiers specified in the object_ids property.
Supported values: “network”, “device”, “application”, “vlan”, “device_group”, “system”. Possible values are: network, device, application, vlan, device_group, system.
Required
until_time The ending timestamp for the request. Return only metrics collected before this time. Time is expressed in milliseconds since the epoch. 0 indicates the time of the request. A negative value is evaluated relative to the current time. The default unit for a negative value is milliseconds, but other units can be specified with a unit suffix.
For example, to request devices active in the last 30 minutes, specify the following parameter value: “-30m”.
Required
metric_specs An array of metric specification objects.
Refer to the ExtraHop REST API Guide at https://docs.extrahop.com/current/rest-api-guide/.
Required

Context Output

Path Type Description
ExtraHop.Metrics.cycle String The aggregation period for metrics.
ExtraHop.Metrics.node_id Number Node ID of the sensor associated with the object.
ExtraHop.Metrics.clock Number The current time.
ExtraHop.Metrics.from Number The beginning time from which metrics were collected.
ExtraHop.Metrics.until Number The ending time that metrics were collected.
ExtraHop.Metrics.stats.oid Number The ID of the object.
ExtraHop.Metrics.stats.time Number The time for which metrics were collected.
ExtraHop.Metrics.stats.duration Number The duration that metrics were collected.
ExtraHop.Metrics.stats.values Unknown The count value of the metrics that were collected.

Command example

!extrahop-metrics-list cycle=auto from_time=0 metric_category=http object_ids=0 object_type=application until_time=0 metric_specs="[{\"name\": \"req\", \"key\": \"/GET/\"}]"

Context Example

{
    "ExtraHop": {
        "Metrics": {
            "clock": 1676883600000,
            "cycle": "1hr",
            "from": 0,
            "node_id": 0,
            "stats": [
                {
                    "duration": 3600000,
                    "oid": 0,
                    "time": 1637740800000,
                    "values": [
                        345
                    ]
                },
                {
                    "duration": 3600000,
                    "oid": 0,
                    "time": 1637744400000,
                    "values": [
                        178
                    ]
                },
                {
                    "duration": 3600000,
                    "oid": 0,
                    "time": 1637751600000,
                    "values": [
                        744
                    ]
                }
            ],
            "until": 1676883600000
        }
    }
}

Human Readable Output

Metrics Found:

Cycle 30 sec
Node Id 0
Clock 1676873250000
From Time 1676871390000
Until Time 1676871990000
Stats {‘oid’: 0, ‘time’: 1637740800000, ‘duration’: 30000, ‘values’: [4]},
{‘oid’: 0, ‘time’: 1676871420000, ‘duration’: 30000, ‘values’: [9]},
{‘oid’: 0, ‘time’: 1676871450000, ‘duration’: 30000, ‘values’: [4]},

extrahop-detections-list


Get detections from ExtraHop Reveal(x).

Base Command

extrahop-detections-list

Input

Argument Name Description Required
filter Detection-specific filters.
For eg:
{
“categories”: [“sec.attack”],
“risk_score_min”: 51
}

If the categories and category are not specified, then categories will be set to [“sec.attack”]. The category field is deprecated by the API, so please use the categories field instead.
Refer to the ExtraHop REST API guide at https://docs.extrahop.com/current/rest-api-guide/.
Optional
from Returns detections that occurred after the specified date, expressed in milliseconds since the epoch. Detections that started before the specified date are returned if the detection was ongoing at that time.

For eg:
from=1673508360001.
Optional
limit Returns no more than the specified number of detections.

For eg:
limit=10. Default is 200.
Optional
offset The number of detections to skip for pagination.

For eg:
offset=100.
Optional
sort Sorts returned detections by the specified fields.
Comma separated “field” “direction” is the accepted format.
By default, detections are sorted by most recent update time and then id in ascending order.

For eg:
sort=”end_time asc,id desc”.
Optional
until Return detections that ended before the specified date, expressed in milliseconds since the epoch.

For eg:
until=1673509360001.
Optional
mod_time Return detections that were modified on or after the specified date, expressed in milliseconds since the epoch.

For eg: 1675416916102 .
Optional

Context Output

Path Type Description
ExtraHop.Detections.id Number The unique detection ID of the detection.
ExtraHop.Detections.start_time Number The timestamp when the detection was identified.
ExtraHop.Detections.mod_time Number The timestamp when the detection was last modified.
ExtraHop.Detections.end_time Number The timestamp when the detection was completed.
ExtraHop.Detections.title String The title of the detection.
ExtraHop.Detections.description String The description of the event for which the detection was created.
ExtraHop.Detections.categories Unknown The categories associated with the detection.
ExtraHop.Detections.risk_score Number The risk level of the event.
ExtraHop.Detections.type String The detection type.
ExtraHop.Detections.properties Unknown The detection properties.
ExtraHop.Detections.participants Unknown The participants involved in the event.
ExtraHop.Detections.ticket_id String The unique ticket ID for the detection that is being tracked.
ExtraHop.Detections.assignee String The user assigned to the detection.
ExtraHop.Detections.status String The status of the detection.
ExtraHop.Detections.resolution String The resolution status of the detection.
ExtraHop.Detections.mitre_tactics Unknown The MITRE tactics associated with the attack.
ExtraHop.Detections.mitre_techniques Unknown The MITRE techniques associated with the attack.
ExtraHop.Detections.appliance_id Number The unique identifier of the sensor on which the attack was detected.
ExtraHop.Detections.is_user_created Boolean Indicates whether the detection is user-created.

Command example

!extrahop-detections-list limit=3

Context Example

{
    "ExtraHop": {
        "Detections": [
            {
                "appliance_id": 0,
                "categories": [
                    "sec",
                    "sec.exploit"
                ],
                "description": "The offender was recently observed carrying out a TCP SYN Scan and has now made a successful TCP 3-way handshake to the victim device. Investigate to determine if this is the result of the SYN Scan.",
                "end_time": 1676895361452,
                "id": 1110161,
                "is_user_created": true,
                "participants": [
                    {
                        "external": false,
                        "id": 2187135,
                        "object_type": "ipaddr",
                        "object_value": "0.0.0.0",
                        "role": "offender"
                    },
                    {
                        "external": true,
                        "id": 2187136,
                        "object_type": "ipaddr",
                        "object_value": "0.0.0.2",
                        "role": "victim"
                    }
                ],
                "risk_score": 50,
                "start_time": 1676895361452,
                "title": "Test_Detection_1_1676895361452",
                "type": "Test_Detection_1_1676895361452",
                "mod_time": 1676895361452
            },
            {
                "appliance_id": 0,
                "categories": [
                    "sec",
                    "sec.exploit"
                ],
                "description": "The offender was recently observed carrying out a TCP SYN Scan and has now made a successful TCP 3-way handshake to the victim device. Investigate to determine if this is the result of the SYN Scan.",
                "end_time": 1676895331451,
                "id": 1110160,
                "is_user_created": true,
                "participants": [
                    {
                        "external": false,
                        "id": 2187133,
                        "object_type": "ipaddr",
                        "object_value": "0.0.0.0",
                        "role": "offender"
                    },
                    {
                        "external": true,
                        "id": 2187134,
                        "object_type": "ipaddr",
                        "object_value": "0.0.0.2",
                        "role": "victim"
                    }
                ],
                "risk_score": 50,
                "start_time": 1676895331451,
                "title": "Test_Detection_1_1676895331451",
                "type": "Test_Detection_1_1676895331451",
                "mod_time": 1676895331451
            },
            {
                "appliance_id": 0,
                "categories": [
                    "sec",
                    "sec.exploit"
                ],
                "description": "The offender was recently observed carrying out a TCP SYN Scan and has now made a successful TCP 3-way handshake to the victim device. Investigate to determine if this is the result of the SYN Scan.",
                "end_time": 1676895301451,
                "id": 1110159,
                "is_user_created": true,
                "participants": [
                    {
                        "external": false,
                        "id": 2187131,
                        "object_type": "ipaddr",
                        "object_value": "0.0.0.0",
                        "role": "offender"
                    },
                    {
                        "external": true,
                        "id": 2187132,
                        "object_type": "ipaddr",
                        "object_value": "0.0.0.2",
                        "role": "victim"
                    }
                ],
                "risk_score": 50,
                "start_time": 1676895301451,
                "title": "Test_Detection_1_1676895301451",
                "type": "Test_Detection_1_1676895301451",
                "mod_time": 1676895301451
            }
        ]
    }
}

Human Readable Output

Found 3 Detection(s)

Detection ID Risk Score Description Categories Start Time
1110161 50 The offender was recently observed carrying out a TCP SYN Scan and has now made a successful TCP 3-way handshake to the victim device. Investigate to determine if this is the result of the SYN Scan. sec,
sec.exploit
1676895361452
1110160 50 The offender was recently observed carrying out a TCP SYN Scan and has now made a successful TCP 3-way handshake to the victim device. Investigate to determine if this is the result of the SYN Scan. sec,
sec.exploit
1676895331451
1110159 50 The offender was recently observed carrying out a TCP SYN Scan and has now made a successful TCP 3-way handshake to the victim device. Investigate to determine if this is the result of the SYN Scan. sec,
sec.exploit
1676895301451

Additional Information

ExtraHop Reveal(x) Playbooks

  • ExtraHop - Default
  • ExtraHop - CVE-2019-0708 (BlueKeep)
  • ExtraHop - Ticket Tracking
  • ExtraHop - Get Peers by Host

Use Cases

  • Create incidents for every detection that ExtraHop Reveal(x) surfaces in real-time.
  • Enable guided investigation and response through playbooks and automation scripts.
  • Query the ExtraHop Reveal(x) REST API using the simple and powerful Cortex XSOAR CLI.

Configuration parameters

  • isFetch — Fetch incidents
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval
  • on_cloud — On Cloud
  • url — URL (required)
  • apikey — API Key
  • client_id — Client ID
  • client_secret — Client Secret
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • first_fetch — First fetch time
  • max_fetch — How many incidents to fetch each time
  • advanced_filter — Advanced Filter

Commands (30)

  • extrahop-activity-map-get

    Get a link to a live activity map in ExtraHop Reveal(x).

  • extrahop-alert-rule-create

    Create a new alert rule in ExtraHop Reveal(x).

  • extrahop-alert-rule-edit

    Modify an alert rule in ExtraHop Reveal(x).

  • extrahop-alert-rules-get

    Get all alert rules from ExtraHop Reveal(x).

  • extrahop-create-alert Deprecated

    Use extrahop-alert-rule-create instead. Create a new alert rule in Reveal(x).

  • extrahop-create-alert-rule Deprecated

    Use extrahop-alert-rule-create instead. Create a new alert rule in Reveal(x).

  • extrahop-detections-list

    Get detections from ExtraHop Reveal(x).

  • extrahop-device-search Deprecated

    Use extrahop-devices-search instead. Search for devices in Reveal(x).

  • extrahop-devices-search

    Search for devices in ExtraHop Reveal(x).

  • extrahop-devices-tag

    Add or remove a tag from devices in ExtraHop Reveal(x).

  • extrahop-edit-alert Deprecated

    Use extrahop-alert-rule-edit instead. Modify an alert rule in Reveal(x).

  • extrahop-edit-alert-rule Deprecated

    Use extrahop-alert-rule-edit instead. Modify an alert rule in Reveal(x).

  • extrahop-edit-watchlist Deprecated

    Use extrahop-watchlist-edit instead. Add or remove devices from the watchlist in Reveal(x).

  • extrahop-get-activity-map Deprecated

    Use extrahop-activity-map-get instead. Get a link to a live activity map in Reveal(x).

  • extrahop-get-alert-rules Deprecated

    Use extrahop-alert-rules-get instead. Get all alert rules from Reveal(x).

  • extrahop-get-alerts Deprecated

    Use extrahop-alert-rules-get instead. Get all alert rules from Reveal(x).

  • extrahop-get-peers Deprecated

    Use extrahop-peers-get instead. Get all peers for a device from Reveal(x).

  • extrahop-get-protocols Deprecated

    Use extrahop-protocols-get instead. Get all active network protocols for a device from Reveal(x).

  • extrahop-get-watchlist Deprecated

    Use extrahop-watchlist-get instead. Get all devices on the watchlist in Reveal(x).

  • extrahop-metrics-list

    Get metrics for specified objects from ExtraHop Reveal(x).

  • extrahop-packets-search

    Search for specific packets in ExtraHop Reveal(x).

  • extrahop-peers-get

    Get all peers for a device from ExtraHop Reveal(x).

  • extrahop-protocols-get

    Get all active network protocols for a device from ExtraHop Reveal(x).

  • extrahop-query-records Deprecated

    No available replacement. Query records from Reveal(x).

  • extrahop-search-packets Deprecated

    Use extrahop-packets-search instead. Search for specific packets in Reveal(x).

  • extrahop-tag-devices Deprecated

    Use extrahop-devices-tag instead. Add or remove a tag from devices in Reveal(x).

  • extrahop-ticket-track

    Link an ExtraHop Reveal(x) detection to a Cortex XSOAR incident.

  • extrahop-track-ticket Deprecated

    Use extrahop-ticket-track instead. Link a Reveal(x) detection to a Demisto Investigation.

  • extrahop-watchlist-edit

    Add or remove devices from the advanced analysis watchlist in ExtraHop Reveal(x).

  • extrahop-watchlist-get

    Get all devices on the advanced analysis watchlist in ExtraHop Reveal(x).

"""Test File for ExtraHop Integration."""

import datetime
import json
import os
from copy import copy
from unittest import mock

import ExtraHop_v2
import pytest
from CommonServerPython import DemistoException

BASE_URL = "https://dummy-base-url.com"
API_KEY = "dummy-api-key"
CLIENT_ID = "dummy-client-id"
CLIENT_SECRET = "dummy-client-secret"
EXTRAHOP_DEVICE = "ExtraHop.Device"
GET_PROTOCOL_FILE = "get_protocol_get_peers.json"
GET_PROTOCOL_NETWORKS_FILE = "get_protocol_networks.json"

PACKETS_SEARCH_URL = "/api/v1/packets/search"
GET_ALL_TAGS_URL = "/api/v1/tags"
TAG_DEVICE_URL = "/api/v1/tags/12/devices"
INVALID_AUTH_HEADER = "Authorization header is invalid"
LIST_DETECTIONS_SUCCESS = "list_detections_success.json"


def get_packets_search_args():
    """
    A common function to return arguments for packet-search command.
    """
    return {
        "output": "zip",
        "limit_bytes": "10MB",
        "limit_search_duration": "5m",
        "query_from": "-10m",
        "query_until": "now",
        "bpf": "ip[1] != 0",
        "ip1": "0.0.0.0",
        "port1": "8080",
        "ip2": "0.0.0.0",
        "port2": "8081",
    }


def get_device_tag_args():
    """
    A common function to return arguments for devices-tag command.
    """
    return {"tag": "MyTag", "add": "1,2", "remove": "3,4"}


# pytest fixture for testing arguments of a function and calling
# BaseClient's "_http_request" method.
@pytest.fixture
def argtest():
    def _argtest(**_kwargs):
        class TestArgs:
            def __call__(self, *args, **kwargs):
                self.args = list(args)
                self.kwargs = kwargs
                return _kwargs["_http_request"](*args, **kwargs)

        return TestArgs()

    return _argtest


@pytest.fixture
def get_devices_by_ip_or_id_fixture(mocker):
    """
    A pytest fixture to mock the "get_devices_by_ip_or_id" function.

    Args:
        mocker: An object to mock specific function of ExtraHop_v2 module.
    """
    return mocker.patch("ExtraHop_v2.get_devices_by_ip_or_id", side_effect=[[1, 2], [3, 4]])


def init_mock_client(requests_mock, on_cloud):
    """
    Extrahop mock client based on on_cloud param.

    Args:
        requests_mock: Mock object of the request.
        on_cloud (bool): whether client created on cloud or on-prem instance.
    """
    if on_cloud:
        access_token_response = load_mock_response("auth_token.json")
        requests_mock.post("/oauth2/token", json=access_token_response)

    return ExtraHop_v2.ExtraHopClient(
        base_url=BASE_URL,
        api_key=API_KEY,
        client_id=CLIENT_ID,
        client_secret=CLIENT_SECRET,
        verify=True,
        use_proxy=False,
        ok_codes=(200, 201, 204),
        on_cloud=on_cloud,
    )


def load_mock_response(file_name: str) -> dict:
    """
    Load one of the mock responses to be used for assertion.

    Args:
        file_name (str): Name of the mock response JSON file to return.
    """
    with open(
        os.path.join(os.path.dirname(__file__), f"test_data/{file_name}"),
        encoding="utf-8",
    ) as json_file:
        return json.loads(json_file.read())


def load_file(file_name: str) -> str:
    """
    Load file to be used for assertion.

    Args:
        file_name (str): Name of the mock response JSON file to return.
    """
    with open(os.path.join(os.path.dirname(__file__), f"test_data/{file_name}")) as file:
        return file.read()


@pytest.mark.parametrize(
    "on_cloud, hr_output",
    [
        (False, load_file("watchlist_get_hr.md")),
        (True, load_file("watchlist_get_command_on_cloud.md")),
    ],
)
def test_watchlist_get_command(on_cloud, hr_output, requests_mock) -> None:
    """
    Test case scenario for successful execution of watchlist-get command.

    Given:
     - User has provided valid credentials.
    When:
     - watchlist_get_command is called.
    Then:
     - Ensure number of items is correct.
     - Ensure outputs prefix is correct.
    """
    watchlist_mock_response = load_mock_response("watchlist_get.json")
    requests_mock.get("/api/v1/watchlist/devices", json=watchlist_mock_response)

    network_mock_response = load_mock_response("network_get.json")
    requests_mock.get("/api/v1/networks", json=network_mock_response)
    client = init_mock_client(requests_mock, on_cloud)
    result = ExtraHop_v2.watchlist_get_command(client, on_cloud)

    assert len(result.outputs) == 3  # type: ignore
    assert result.outputs_prefix == EXTRAHOP_DEVICE
    assert result.readable_output == hr_output


@pytest.mark.parametrize("on_cloud", [False, True])
def test_create_or_edit_alert_rule_command(on_cloud, requests_mock) -> None:
    """
    Test case scenario for successful execution of create-or-edit-alert-rule command.

    Given:
     - User has provided valid credentials.
    When:
     - create_or_edit_alert_rule_command is called.
    Then:
     - Ensure number of items is correct.
     - Ensure outputs prefix is correct.
    """
    alert_mock_response = load_mock_response("/alerts/create_alert.json")
    requests_mock.post("/api/v1/alerts", status_code=201)

    client = init_mock_client(requests_mock, on_cloud)
    result = ExtraHop_v2.create_or_edit_alert_rule_command(client, alert_mock_response)

    assert result.readable_output == "Successfully created alert rule."


@pytest.mark.parametrize("on_cloud", [False, True])
def test_create_or_edit_alert_rule_command_invalid_type(on_cloud, requests_mock) -> None:
    """
    Test case scenario for invalid type of create-or-edit-alert-rule command.

    Given:
     - User has provided valid credentials.
    When:
     - create_or_edit_alert_rule_command is called.
    Then:
     - Ensure appropriate error is raised.
    """
    alert_mock_response = load_mock_response("/alerts/create_alert_validation.json")
    client = init_mock_client(requests_mock, on_cloud)

    with pytest.raises(ExtraHop_v2.InvalidValueError):
        ExtraHop_v2.create_or_edit_alert_rule_command(client, alert_mock_response)


@pytest.mark.parametrize("on_cloud", [False, True])
def test_create_or_edit_alert_rule_command_invalid_object_type(on_cloud, requests_mock) -> None:
    """
    Test case scenario for invalid object type of create-or-edit-alert-rule command.

    Given:
     - User has provided valid credentials.
    When:
     - Create_or_edit_alert_rule_command is called.
    Then:
     - Ensure appropriate error is raised.
    """
    alert_mock_response = load_mock_response("/alerts/create_alert_validation_object_type.json")
    client = init_mock_client(requests_mock, on_cloud)

    with pytest.raises(ExtraHop_v2.InvalidValueError):
        ExtraHop_v2.create_or_edit_alert_rule_command(client, alert_mock_response)


@pytest.mark.parametrize(
    ("refire_interval", "severity", "on_cloud"),
    [["10", "1", True], ["300", "8", True], ["10", "1", False], ["300", "8", False]],
)
def test_create_or_edit_alert_rule_command_invalid_rule_arguments(on_cloud, refire_interval, severity, requests_mock) -> None:
    """
    Test case scenario for invalid rule arguments of create-or-edit-alert-rule command.

    Given:
     - User has provided valid credentials.
    When:
     - create_or_edit_alert_rule_command is called.
    Then:
     - Ensure appropriate error is raised.
    """
    alert_mock_response = {"refire_interval": refire_interval, "severity": severity}
    client = init_mock_client(requests_mock, on_cloud)

    with pytest.raises(ExtraHop_v2.InvalidValueError):
        ExtraHop_v2.create_or_edit_alert_rule_command(client, alert_mock_response)


@pytest.mark.parametrize(
    ("interval_length", "operator", "units", "on_cloud"),
    [
        ["31", "==", "none", True],
        ["30", "!=", "none", True],
        ["30", "==", "nones", True],
        ["31", "==", "none", False],
        ["30", "!=", "none", False],
        ["30", "==", "nones", False],
    ],
)
def test_create_or_edit_alert_rule_command_invalid_thresold_argument(
    on_cloud, interval_length, operator, units, requests_mock
) -> None:
    """
    Test case scenario for validate type of create-or-edit-alert-rule command.

    Given:
     - User has provided valid credentials.
    When:
     - create_or_edit_alert_rule_command is called.
    Then:
     - Ensure appropriate error is raised.
    """
    alert_mock_response = {
        "type": "threshold",
        "interval_length": interval_length,
        "operator": operator,
        "units": units,
    }
    client = init_mock_client(requests_mock, on_cloud)

    with pytest.raises(ExtraHop_v2.InvalidValueError):
        ExtraHop_v2.create_or_edit_alert_rule_command(client, alert_mock_response)


def test_update_alert_rule_command(requests_mock) -> None:
    """
    Test case scenario for create-or-edit-alert-rule command.

    Given:
     - User has provided valid credentials.
    When:
     - create_or_edit_alert_rule_command is called.
    Then:
     - Ensure number of items is correct.
     - Ensure outputs prefix is correct.
    """
    alert_id = 212
    alert_mock_response = {
        "type": "threshold",
        "interval_length": "30",
        "operator": "==",
        "units": "none",
        "alert_id": alert_id,
    }
    client = init_mock_client(requests_mock, on_cloud=False)

    requests_mock.patch(f"/api/v1/alerts/{alert_id}", status_code=204)
    result = ExtraHop_v2.create_or_edit_alert_rule_command(client, alert_mock_response)

    assert result.readable_output == "Successfully updated alert rule."


@pytest.mark.parametrize(
    "args, error_msg",
    [
        (
            {
                "cycle": "dummy",
                "object_type": "device",
                "from": "-30m",
                "until": "-20m",
                "metric_category": "http",
                "objects_ids": [0],
                "metric_specs": '[{"name": "rsp"}]',
            },
            f"dummy is an invalid value for cycle. Possible values are: {ExtraHop_v2.VALID_CYCLES}",
        ),
        (
            {
                "cycle": "auto",
                "object_type": "dummy",
                "from": "-30m",
                "until": "-20m",
                "metric_category": "http",
                "objects_ids": [0],
                "metric_specs": '[{"name": "rsp"}]',
            },
            f"dummy is an invalid value for object_type. Possible values are: {ExtraHop_v2.VALID_OBJECT_TYPES}",
        ),
        (
            {
                "cycle": "auto",
                "object_type": "device",
                "from": "-30m",
                "until": "-20m",
                "metric_category": "http",
                "objects_ids": [0],
                "metric_specs": "{'test': dummy}",
            },
            "Invalid JSON string provided for metric specs.",
        ),
    ],
)
def test_metrics_list_command_invalid_args(requests_mock, args, error_msg):
    """Test case scenario for invalid arguments while execution of metrics-list command.

    Given:
     - User has provided invalid arguments.
    When:
     - metrics_list_command is called.
    Then:
     - Ensure appropriate error is raised.
    """
    client = init_mock_client(requests_mock, on_cloud=False)
    with pytest.raises(Exception) as error:
        ExtraHop_v2.metrics_list_command(client, args)
    assert str(error.value) == error_msg


def test_metrics_list_command_successful_execution(requests_mock):
    """Test case scenario for successful execution of metrics-list command.

    Given:
     - User has provided valid credentials.
    When:
     - metrics_list_command is called.
    Then:
     - Ensure human-readable output is correct.
     - Ensure outputs prefix is correct.
    """
    client = init_mock_client(requests_mock, on_cloud=False)
    args = {
        "cycle": "24hr",
        "from": "-30m",
        "until": "0",
        "metric_category": "http",
        "metric_specs": '[{"name": "rsp"}]',
        "object_ids": [0],
        "object_type": "network",
    }

    response = load_mock_response("metrics_list_success.json")

    expected_hr = load_file("metrics_list_success_hr.md")

    requests_mock.post(f"{BASE_URL}/api/v1/metrics", json=response)

    results = ExtraHop_v2.metrics_list_command(client, args=args)

    assert results.readable_output == expected_hr
    assert results.outputs_prefix == "ExtraHop.Metrics"


def test_metrics_list_command_using_advanced_filter(requests_mock):
    """Test case scenario for successful execution of metrics-list command using advanced_filter argument.

    Given:
     - User has provided valid credentials.
    When:
     - metrics_list_command is called.
    Then:
     - Ensure human-readable output is correct.
     - Ensure outputs prefix is correct.
    """
    client = init_mock_client(requests_mock, on_cloud=False)

    advanced_filter = """{\"cycle\": \"auto\",
                \"from\": \"-30m\",
                \"metric_category\": \"http\",
                \"metric_specs\": [{\"name\":\"rsp\"}],
                \"object_ids\": [0],
                \"object_type\": \"application\",
                \"until\": \"0\"}"""

    response = load_mock_response("metrics_list_success.json")

    expected_hr = load_file("metrics_list_success_hr.md")

    requests_mock.post(f"{BASE_URL}/api/v1/metrics", json=response)

    results = ExtraHop_v2.metrics_list_command(client, advanced_filter=advanced_filter)
    assert results.readable_output == expected_hr


def test_metrics_list_commands_using_advance_filter_invalid_args(requests_mock):
    """Test case scenario for failure of metrics-list command using invalid advanced_filter argument.

    Given:
     - User has provided valid credentials.
    When:
     - metrics_list_command is called.
    Then:
     - Ensure human-readable output is correct.
     - Ensure outputs prefix is correct.
    """
    client = init_mock_client(requests_mock, on_cloud=False)

    advanced_filter = """{\"cycles\": \"auto\",
                    \"from\": \"-30m\",
                    \"metric_category\": \"http\",
                    \"metric_specs\": [{\"name\":\"rsp\"}],
                    \"object_ids\": [0],
                    \"object_type\": \"application\",
                    \"until\": \"0\"}"""

    response = load_mock_response("metrics_list_success.json")

    requests_mock.post(f"{BASE_URL}/api/v1/metrics", json=response)

    with pytest.raises(Exception) as error:
        ExtraHop_v2.metrics_list_command(client, advanced_filter=advanced_filter)
    assert (
        str(error.value) == "cycles is an invalid value for keys. Possible values are: ['cycle', 'from', "
        "'metric_category', 'metric_specs', 'object_ids', 'object_type', 'until']"
    )


def test_metrics_list_command_error_code_400(requests_mock):
    """Test case scenario for failure metrics-list command when status code is 400.

    Given:
     - User has provided invalid json input.
    When:
     - metrics_list_command is called.
    Then:
     - Ensure error is raised with error code.
    """
    client = init_mock_client(requests_mock, on_cloud=False)
    args = {
        "cycle": "24hr",
        "from": "-30m",
        "until": "0",
        "metric_category": "http",
        "metric_specs": '[{"name": "rsp"}]',
        "object_ids": [0],
        "object_type": "network",
    }

    response = {
        "type": "invalid_request",
        "detail": "The JSON payload specified in the request is invalid.",
    }

    requests_mock.post(f"{BASE_URL}/api/v1/metrics", json=response, status_code=400)

    with pytest.raises(Exception) as error:
        ExtraHop_v2.metrics_list_command(client, args)
    assert (
        str(error.value) == 'Error in API call [400] - None\n{"type": "invalid_request", "detail": "The JSON '
        'payload specified in the request is invalid."}'
    )


def test_metrics_list_command_error_code_401(requests_mock):
    """Test case scenario for failure metrics-list command when status code is 401.

    Given:
     - User has provided invalid json input.
    When:
     - metrics_list_command is called.
    Then:
     - Ensure error is raised with error code.
    """
    client = init_mock_client(requests_mock, on_cloud=False)
    args = {
        "cycle": "24hr",
        "from": "-30m",
        "until": "0",
        "metric_category": "http",
        "metric_specs": '[{"name": "rsp"}]',
        "object_ids": [0],
        "object_type": "network",
    }

    response = {"error_message": "API Key is invalid"}

    requests_mock.post(f"{BASE_URL}/api/v1/metrics", json=response, status_code=401)

    with pytest.raises(Exception) as error:
        ExtraHop_v2.metrics_list_command(client, args)
    assert str(error.value) == 'Error in API call [401] - None\n{"error_message": "API Key is invalid"}'


@pytest.mark.parametrize(
    "args, error_msg",
    [
        ({"from": "123s"}, '"123s" is not a valid number'),
        (
            {"limit": 0},
            "Invalid input for field limit. It should have numeric value greater than zero.",
        ),
        (
            {"offset": -1},
            "Invalid input for field offset. It should have numeric value greater than or equal to zero.",
        ),
        (
            {"from": "1673508360001", "until": "1673508360000"},
            'Input for "from" should always be less than that of "until".',
        ),
        (
            {"from": "-1673508360001", "until": "-1673508360002"},
            'Input for "from" should always be less than that of "until".',
        ),
        ({"filter": "{'limit': 1}"}, "Invalid json string provided for filter."),
        (
            {"sort": "id asc desc"},
            'Incorrect input provided for argument "sort". Please follow the format mentioned in description.',
        ),
        (
            {"sort": "id asd"},
            'Incorrect input provided for argument "sort". Allowed values for direction are: asc, desc',
        ),
        (
            {"filter": '{"invalid_arg": 1}'},
            "invalid_arg is an invalid value for key. Possible values are: ['assignee', "
            "'categories', 'category', 'resolution', 'risk_score_min', 'status', 'ticket_id', 'types']",
        ),
    ],
)
def test_detections_list_command_invalid_args(requests_mock, args, error_msg):
    """Test case scenario for invalid arguments while execution of detections list command.

    Given:
     - User has provided invalid arguments.
    When:
     - detections_list_command is called.
    Then:
     - Ensure appropriate error is raised.
    """
    requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"})
    client = init_mock_client(requests_mock, on_cloud=False)
    with pytest.raises(Exception) as error:
        ExtraHop_v2.detections_list_command(client, args)
    assert str(error.value) == error_msg


def test_detections_list_command_failure_when_firmware_version_is_outdated(requests_mock):
    """Test case scenario for execution of detections list command when ExtraHop firmware version is less than 9.3.0.

    Given:
       - Arguments for detections list command.
    When:
       - detections_list_command is called.
    Then:
       - Returns a valid error message.
    """
    client = init_mock_client(requests_mock, on_cloud=False)
    requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.1"})
    with pytest.raises(DemistoException) as err:
        ExtraHop_v2.detections_list_command(client, {}, True, "{}")
    assert (
        str(err.value) == "This integration works with ExtraHop firmware version greater than or equal to 9.3.0 "
        "and current firmware version being used by you is 9.1"
    )


@pytest.mark.parametrize("on_cloud", [False, True])
def test_list_detections_command_successful_execution_with_categories(on_cloud, requests_mock):
    """Test case scenario for successful execution of detections-list command with categories.

    Given:
     - User has provided valid credentials.
    When:
     - detections_list_command is called.
    Then:
     - Ensure human-readable output is correct.
     - Ensure outputs prefix is correct.
    """
    requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.4"})
    client = init_mock_client(requests_mock, on_cloud)
    args = {
        "limit": "2",
        "filter": """{
        \"categories\": [\"sec.attack\"],
        \"risk_score_min\": 51
    }""",
        "from": "1573500360001",
        "offset": "2",
        "sort": "end_time asc,id desc",
        "until": "1673569370001",
    }
    response = load_mock_response(LIST_DETECTIONS_SUCCESS)

    expected_hr = load_file("list_detections_success_hr.md")

    requests_mock.post(f"{BASE_URL}/api/v1/detections/search", json=response)

    results = ExtraHop_v2.detections_list_command(client, args)

    assert results.readable_output == expected_hr
    assert results.outputs_prefix == "ExtraHop.Detections"


@pytest.mark.parametrize("on_cloud", [False, True])
def test_list_detections_command_successful_execution_with_category(on_cloud, requests_mock):
    """Test case scenario for successful execution of detections-list command with category.

    Given:
     - User has provided valid credentials.
    When:
     - detections_list_command is called.
    Then:
     - Ensure human-readable output is correct.
     - Ensure outputs prefix is correct.
    """
    requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"})
    client = init_mock_client(requests_mock, on_cloud)
    args = {
        "limit": "2",
        "filter": """{
        \"category\": \"sec.attack\",
        \"risk_score_min\": 51
    }""",
        "from": "1573500360001",
        "offset": "2",
        "sort": "end_time asc,id desc",
        "until": "1673569370001",
    }
    response = load_mock_response(LIST_DETECTIONS_SUCCESS)

    expected_hr = load_file("list_detections_success_hr.md")

    requests_mock.post(f"{BASE_URL}/api/v1/detections/search", json=response)

    results = ExtraHop_v2.detections_list_command(client, args)

    assert results.readable_output == expected_hr
    assert results.outputs_prefix == "ExtraHop.Detections"


@pytest.mark.parametrize("on_cloud", [False, True])
def test_list_detections_command_successful_execution_without_category(on_cloud, requests_mock):
    """Test case scenario for successful execution of detections-list command without categories.

    Given:
     - User has provided valid credentials.
    When:
     - detections_list_command is called.
    Then:
     - Ensure human-readable output is correct.
     - Ensure outputs prefix is correct.
    """
    requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"})
    client = init_mock_client(requests_mock, on_cloud)
    args = {
        "limit": "2",
        "filter": """{
        \"risk_score_min\": 51
    }""",
        "from": "1573500360001",
        "offset": "2",
        "sort": "end_time asc,id desc",
        "until": "1673569370001",
    }
    response = load_mock_response(LIST_DETECTIONS_SUCCESS)

    expected_hr = load_file("list_detections_success_hr.md")

    requests_mock.post(f"{BASE_URL}/api/v1/detections/search", json=response)

    results = ExtraHop_v2.detections_list_command(client, args)

    assert results.readable_output == expected_hr
    assert results.outputs_prefix == "ExtraHop.Detections"


@pytest.mark.parametrize("on_cloud", [False, True])
def test_list_detections_command_when_description_has_metric_link(on_cloud, requests_mock):
    """Test case scenario for successful execution of detections-list command when description has metrics link.

    Given:
     - User has provided valid credentials.
    When:
     - detections_list_command is called.
    Then:
     - Ensure human-readable output is correct.
     - Ensure outputs prefix is correct.
    """
    requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"})
    client = init_mock_client(requests_mock, on_cloud)
    args = {
        "limit": "1",
        "filter": """{
        \"categories\": [\"sec.attack\"],
        \"risk_score_min\": 30
    }""",
        "from": "1573500360001",
        "offset": "2",
        "sort": "end_time asc,id desc",
        "until": "1673569370001",
    }
    response = load_mock_response("list_detections_with_description_url.json")

    expected_hr = load_file("list_detections_with_description_url.md")

    requests_mock.post(f"{BASE_URL}/api/v1/detections/search", json=response)

    results = ExtraHop_v2.detections_list_command(client, args)

    assert results.readable_output == expected_hr
    assert results.outputs_prefix == "ExtraHop.Detections"


@pytest.mark.parametrize("on_cloud", [False, True])
def test_list_detections_command_when_description_has_complete_metric_link(on_cloud, requests_mock):
    """Test case scenario for successful execution of detections-list command when description has metrics link which
    has only base url missing.

    Given:
     - User has provided valid credentials.
    When:
     - detections_list_command is called.
    Then:
     - Ensure human-readable output is correct.
     - Ensure outputs prefix is correct.
    """
    requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"})
    client = init_mock_client(requests_mock, on_cloud)
    args = {
        "limit": "1",
        "filter": """{
        \"categories\": [\"sec.attack\"],
        \"risk_score_min\": 30
    }""",
        "from": "1573500360001",
        "offset": "2",
        "sort": "end_time asc,id desc",
        "until": "1673569370001",
    }
    response = load_mock_response("list_detections_with_complete_description_url.json")

    expected_hr = load_file("list_detections_with_complete_description_url.md")

    requests_mock.post(f"{BASE_URL}/api/v1/detections/search", json=response)

    results = ExtraHop_v2.detections_list_command(client, args)

    assert results.readable_output == expected_hr
    assert results.outputs_prefix == "ExtraHop.Detections"


def test_list_detections_command_using_advanced_filter(requests_mock):
    """Test case scenario for successful execution of detections list using advanced_filter argument.

    Given:
     - User has provided valid credentials.
    When:
     - detections_list_command is called.
    Then:
     - Ensure human-readable output is correct.
     - Ensure outputs prefix is correct.
    """
    requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"})
    client = init_mock_client(requests_mock, on_cloud=False)
    args = {}
    advanced_filter = """{\"filter\": {\"categories\": [\"sec.attack\"],\"risk_score_min\": 51},
                \"limit\": 1,\"offset\": 0,
                \"sort\": [
                    {
                    \"direction\": \"desc\",
                    \"field\": \"end_time\"
                    }
                ]
            }"""

    response = load_mock_response(LIST_DETECTIONS_SUCCESS)

    expected_hr = load_file("list_detections_success_hr.md")

    requests_mock.post(f"{BASE_URL}/api/v1/detections/search", json=response)

    results = ExtraHop_v2.detections_list_command(client, args, json.loads(advanced_filter))
    assert results.readable_output == expected_hr


def test_list_detections_command_using_advanced_filter_invalid_arg(requests_mock):
    """Test case scenario for execution of detections list using advanced_filter argument.

    Given:
     - User has provided invalid arguments in body.
    When:
     - detections_list_command is called.
    Then:
     - Ensure appropriate error is raised.
    """
    requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"})
    client = init_mock_client(requests_mock, on_cloud=False)
    args = {}
    advanced_filter = """{\"filter\": {\"categories\": [\"sec.attack\"],\"risk_score_min\": 51},
                \"limit\": 1,\"offset\": 0,\"invalid_arg\": 0,
                \"sort\": [
                    {
                    \"direction\": \"desc\",
                    \"field\": \"end_time\"
                    }
                ]
            }"""

    response = load_mock_response(LIST_DETECTIONS_SUCCESS)

    requests_mock.post(f"{BASE_URL}/api/v1/detections/search", json=response)

    with pytest.raises(Exception) as error:
        ExtraHop_v2.detections_list_command(client, args, advanced_filter=json.loads(advanced_filter))
    assert (
        str(error.value) == "invalid_arg is an invalid value for key. Possible values are: ['filter', 'limit', "
        "'offset', 'from', 'until', 'sort', 'mod_time']"
    )


def test_list_detections_command_error_code_400(requests_mock):
    """Test case scenario for failure detections list when status code is 400.

    Given:
     - User has provided invalid json input.
    When:
     - detections_list_command is called.
    Then:
     - Ensure error is raised with error code.
    """
    requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"})
    client = init_mock_client(requests_mock, on_cloud=False)
    args = {
        "limit": "2",
    }
    response = {
        "type": "invalid_request",
        "detail": "The JSON payload specified in the request is invalid.",
    }
    requests_mock.post(f"{BASE_URL}/api/v1/detections/search", json=response, status_code=400)
    with pytest.raises(Exception) as error:
        ExtraHop_v2.detections_list_command(client, args)
    assert (
        str(error.value) == 'Error in API call [400] - None\n{"type": "invalid_request", "detail": "The JSON '
        'payload specified in the request is invalid."}'
    )


def test_list_detections_command_error_code_502(requests_mock):
    """Test case scenario for failure detections list when status code is 502.

    Given:
     - User has provided invalid arguments.
    When:
     - detections_list_command is called.
    Then:
     - Ensure error is raised with error code.
    """
    requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"})
    client = init_mock_client(requests_mock, on_cloud=False)
    args = {}
    requests_mock.post(f"{BASE_URL}/api/v1/detections/search", status_code=502)
    with pytest.raises(Exception) as error:
        ExtraHop_v2.detections_list_command(client, args)
    assert str(error.value) == "Error in API call [502] - None\n"  # - Bad Gateway


def test_list_detections_command_error_code_401(requests_mock):
    """Test case scenario for failure detections list when status code is 401.

    Given:
     - User has provided invalid credentials.
    When:
     - detections_list_command is called.
    Then:
     - Ensure error is raised with error code.
    """
    requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"})
    client = init_mock_client(requests_mock, on_cloud=False)
    args = {"limit": "2"}
    response = {"error_message": "API Key is invalid"}
    requests_mock.post(f"{BASE_URL}/api/v1/detections/search", status_code=401, json=response)
    with pytest.raises(Exception) as error:
        ExtraHop_v2.detections_list_command(client, args)
    assert str(error.value) == 'Error in API call [401] - None\n{"error_message": "API Key is invalid"}'


@pytest.mark.parametrize(
    "detection_status, close_reason, incident_owner, expected",
    [
        (
            "2",
            "Resolved",
            "abc",
            {
                "ticket_id": "incident_id",
                "status": "closed",
                "assignee": "abc",
                "resolution": "action_taken",
            },
        ),
        (
            "2",
            "Duplicate",
            None,
            {
                "ticket_id": "incident_id",
                "status": "closed",
                "assignee": None,
                "resolution": "no_action_taken",
            },
        ),
        (
            "1",
            "Duplicate",
            None,
            {"ticket_id": "incident_id", "status": "in_progress", "assignee": None},
        ),
        (
            "2",
            "Not resolved",
            None,
            {"ticket_id": "incident_id", "status": "closed", "assignee": None},
        ),
    ],
)
def test_ticket_track_command_successful_execution(
    mocker, detection_status, close_reason, incident_owner, expected, requests_mock
) -> None:
    """
    Test case scenario for successful execution of ticket-track command.

    Given:
     - User has provided valid argument values.
    When:
     - ticket_track_command is called.
    Then:
     - Ensure outputs_prefix is correct.
     - Ensure readable_output is correct.
     - Ensure command output is correct.
     - Ensure patch_detections method is called with expected arguments.
    """
    args = {
        "incident_id": "incident_id",
        "detection_id": "detection_id",
        "incident_status": detection_status,
        "incident_close_reason": close_reason,
        "incident_owner": incident_owner,
    }

    client = init_mock_client(requests_mock, on_cloud=False)
    mocker.patch("ExtraHop_v2.ExtraHopClient.patch_detections")
    result = ExtraHop_v2.ticket_track_command(client, args)

    assert result.outputs_prefix == "ExtraHop"
    assert result.readable_output == "Successfully linked detection(detection_id) with incident(incident_id)"
    assert result.outputs == {"TicketId": "incident_id"}

    ExtraHop_v2.ExtraHopClient.patch_detections.assert_called_once_with("detection_id", expected)


def test_validate_ticket_track_arguments_failed_execution():
    """
    Test case for ticket-track command that raise error for invalid argument.

    Given:
     - User has provided invalid incident_status.
    When:
     - validate_ticket_track_arguments is called.
    Then:
     - Ensure error is raised with extecpted error message.
    """
    # Verify invalid value
    with pytest.raises(ExtraHop_v2.InvalidValueError) as err:
        ExtraHop_v2.validate_ticket_track_arguments("4")

    assert str(err.value) == "4 is an invalid value for incident_status. Possible values are: ['0', '1', '2', '3']"


def test_validate_ticket_track_arguments_successful_execution():
    """
    Test case scenarios for successful execution of ticket-track command.

    Given:
     - User has provided valid incident_status.
    When:
     - validate_ticket_track_arguments is called.
    Then:
     - Ensure error is not raised for valid argument.
    """

    # Verify valid value
    assert not ExtraHop_v2.validate_ticket_track_arguments("3")


@pytest.mark.parametrize(
    "add_arg, remove_arg, expected",
    [
        [
            "id1, id2",
            "",
            "Successfully added new devices(id1, id2) in the watchlist \n",
        ],
        ["", "id1, id2", "Successfully removed devices(id1, id2) from the watchlist"],
        [
            "id1, id2",
            "id3",
            "Successfully added new devices(id1, id2) in the watchlist \nSuccessfully removed devices(id3) from the watchlist",
        ],
    ],
)
def test_watch_list_edit_command_successful_execution(requests_mock, mocker, add_arg, remove_arg, expected):
    """
    Test case scenario for successful execution of watchlist-edit command.

    Given:
     - User has provided different arguments for watchlist-edit command.
    When:
     - watchlist-edit is called.
    Then:
     - Ensure readable_output is correct.
    """
    args = {"add": add_arg, "remove": remove_arg}

    client = init_mock_client(requests_mock, on_cloud=False)
    requests_mock.post(f"{BASE_URL}/api/v1/watchlist/devices")
    mocker.patch("ExtraHop_v2.get_devices_by_ip_or_id", return_value=[])

    actual = ExtraHop_v2.watchlist_edit_command(client, args)

    assert actual.readable_output == expected


def test_packets_search_invalid_output(requests_mock):
    """
    Test case scenario for invalid output in packets-search command.

    Given:
        User has provided valid credentials but invalid arguments.
    When:
        extrahop-packets-search command is called.
    Then:
        Raise error with right error message.
    """

    args_copy = copy(get_packets_search_args())
    args_copy["output"] = "gzip"
    with pytest.raises(ExtraHop_v2.InvalidValueError) as e:
        ExtraHop_v2.packets_search_command(init_mock_client(requests_mock, False), args_copy)
    assert str(e.value) == "gzip is an invalid value for output. Possible values are: ['pcap', 'keylog_txt', 'zip']"


def test_packets_search_204_status_code(requests_mock):
    """
    Test case scenario for packets-search command when no packets are returned from the API.

    Given:
        User has provided valid credentials.
    When:
        extrahop-packets-search command is called.
    Then:
        Return message about packets not found.
    """

    requests_mock.get(BASE_URL + PACKETS_SEARCH_URL, status_code=204)

    response = ExtraHop_v2.packets_search_command(init_mock_client(requests_mock, False), get_packets_search_args())

    assert response == "Search matched no packets."


def test_packets_search_invalid_filename_headers(requests_mock):
    """
    Test case scenario to raise error when unexpected filename format found in the response headers for packet-search
    command.

    Given:
        User has provided valid credentials and packet are found in the API.
    When:
        extrahop-packets-search command is called.
    Then:
        Return right error message.
    """

    requests_mock.get(
        BASE_URL + PACKETS_SEARCH_URL,
        headers={"content-disposition": "file=packets.zip"},
        content="this is dummy packets".encode("ascii"),
    )

    with pytest.raises(ExtraHop_v2.DemistoException) as e:
        ExtraHop_v2.packets_search_command(init_mock_client(requests_mock, False), get_packets_search_args())

    assert str(e.value) == "Error filename could not be found in response header."


def test_packets_search_file_data(requests_mock):
    """
    Test case scenario for packets-search command when packets received from the API.

    Given:
        User has provided valid credentials.
    When:
        extrahop-packets-search command is called.
    Then:
        Return file response.
    """
    requests_mock.get(
        BASE_URL + PACKETS_SEARCH_URL,
        headers={"content-disposition": "filename=packets.zip"},
        content="this is dummy packets".encode("ascii"),
    )

    response = ExtraHop_v2.packets_search_command(init_mock_client(requests_mock, False), get_packets_search_args())

    assert response.get("File") == "packets.zip"


def test_devices_tag_invalid_args(requests_mock):
    """
    Test case scenario to verify devices-tag command when "add" and "remove" tags not found in the args.

    Given:
        User has provided valid credentials.
    When:
        extrahop-devices-tag command is called.
    Then:
        Raise error with valid error message.
    """
    with pytest.raises(ExtraHop_v2.DemistoException) as e:
        ExtraHop_v2.devices_tag_command(
            init_mock_client(requests_mock, False),
            {"tag": "MyTag", "add": None, "remove": None},
        )

    assert str(e.value) == "No device id provided to add or remove arguments."


@pytest.mark.parametrize(
    "add, remove, expected_data",
    [
        ("1,2", "3,4", '{"assign": [1, 2], "unassign": [3, 4]}'),
        ("1,2", None, '{"assign": [1, 2]}'),
        (None, "1,2", '{"unassign": [1, 2]}'),
    ],
)
def test_devices_tag(add, remove, expected_data, requests_mock, get_devices_by_ip_or_id_fixture):
    """Test case scenario to verify devices-tag command when valid arguments provided.

    Given:
        User has provided valid credentials, and "add" or "remove" is present in demisto args.
    When:
        extrahop-devices-tag command is called.
    Then:
        User is informed of tag-untag devices.
    """
    args_copy = copy(get_device_tag_args())
    args_copy["add"] = add
    args_copy["remove"] = remove

    requests_mock.get(
        BASE_URL + GET_ALL_TAGS_URL,
        json=[
            {"id": "12", "name": "MyTag"},
            {"id": "13", "name": "TestTag"},
            {"id": "14", "name": "DummyTag"},
        ],
    )

    requests_mock.post(BASE_URL + TAG_DEVICE_URL)

    response = ExtraHop_v2.devices_tag_command(init_mock_client(requests_mock, False), args_copy)

    assert response == "Successfully tagged untagged the device/s."
    assert requests_mock.request_history[1].text == expected_data


@pytest.mark.parametrize(
    "add, remove, expected_data",
    [
        ("1,2", "3,4", '{"assign": [1, 2], "unassign": [3, 4]}'),
        ("1,2", None, '{"assign": [1, 2]}'),
    ],
)
def test_devices_tag_create_new_tag(add, remove, expected_data, get_devices_by_ip_or_id_fixture, requests_mock):
    """
    Test case scenario for creating new Tag with tag name if no tag is found with
    expected name from the tags list, and call "tag_untag_devices" for that tag.

    Given:
        User has provided valid credentials.
    When:
        extrahop-devices-tag command is called.
    Then:
        Verify valid tag id was present in the tag-untag URL with expected json body.
    """
    args_copy = copy(get_device_tag_args())
    args_copy["add"] = add
    args_copy["remove"] = remove

    requests_mock.get(
        BASE_URL + GET_ALL_TAGS_URL,
        json=[
            {"id": "t1", "name": "NotMyTag"},
            {"id": "t2", "name": "TestTag"},
            {"id": "t3", "name": "DummyTag"},
        ],
    )

    requests_mock.post(BASE_URL + TAG_DEVICE_URL)

    requests_mock.post(BASE_URL + GET_ALL_TAGS_URL, headers={"location": "US/12"})

    response = ExtraHop_v2.devices_tag_command(init_mock_client(requests_mock, False), args_copy)

    assert response == "Successfully tagged untagged the device/s."
    assert requests_mock.request_history[2].path == TAG_DEVICE_URL
    assert requests_mock.request_history[2].text == expected_data


def test_devices_tag_nothing_to_remove(requests_mock, get_devices_by_ip_or_id_fixture):
    """
    Test case scenario to raise error when expected Tag not found from Reveal(X)
    and "remove" is present and "add" is not present in demisto args.

    Given:
        User has provided valid credentials.
    When:
        extrahop-devices-tag command is called.
    Then:
        Raise error with informative error message.
    """
    args_copy = copy(get_device_tag_args())
    del args_copy["add"]

    requests_mock.get(
        BASE_URL + GET_ALL_TAGS_URL,
        json=[
            {"id": "t1", "name": "NotMyTag"},
            {"id": "t2", "name": "TestTag"},
            {"id": "t3", "name": "DummyTag"},
        ],
    )

    requests_mock.post(BASE_URL + TAG_DEVICE_URL)

    with pytest.raises(ExtraHop_v2.DemistoException) as e:
        ExtraHop_v2.devices_tag_command(init_mock_client(requests_mock, False), args_copy)

    assert str(e.value) == "The tag MyTag does not exist, nothing to remove."


@pytest.mark.parametrize(
    "devices_str, id_only, expected_data",
    [
        ("1", True, [1]),
        ("1", False, [{"id": 1, "ip": "0.0.0.0"}]),
        ("0.0.0.0", True, [1]),
        ("0.0.0.0", False, [{"id": 1, "ip": "0.0.0.0"}]),
    ],
)
def test_get_devices_by_ip_or_id(devices_str, id_only, expected_data, requests_mock, mocker):
    """
    Test case scenario to return devices list by IP address or device ID.

    Given:
        User has provided valid credentials, "add" or "remove" is present in the demisto args,
        ip addresses/device ids are provided in the device list string and id_only is true/false.
    When:
        extrahop-devices-tag command is called.
    Then:
        Return list of ids if id_only flag is Tue else device info.
    """
    requests_mock.get(BASE_URL + "/api/v1/devices/1", json={"id": 1, "ip": "0.0.0.0"})

    mocker.patch("ExtraHop_v2.get_device_by_ip", return_value={"id": 1, "ip": "0.0.0.0"})

    devices = ExtraHop_v2.get_devices_by_ip_or_id(init_mock_client(requests_mock, False), devices_str, id_only=id_only)

    assert devices == expected_data


def test_get_devices_by_ip_or_id_invalid_ip_address(requests_mock):
    """
    Test case scenario to raise error when invalid IP address found from the add/remove args.

    Given:
        User has provided valid credentials, "add" or "remove" is present in the
        demisto args and ip addresses are provided in the device list string.
    When:
        extrahop-devices-tag command is called.
    Then:
        Raise error with informative error message.
    """
    with pytest.raises(ExtraHop_v2.DemistoException) as e:
        ExtraHop_v2.get_devices_by_ip_or_id(init_mock_client(requests_mock, False), "10.0.0.0.1")
    assert str(e.value) == "Error parsing IP Address 10.0.0.0.1"


@pytest.mark.parametrize("location", [None, "US/NYC"])
def test_parse_location_header_invalid_location(location):
    """
    Test case scenario to raise error when no location response found from headers of "client.create_new_tag".

    Given:
        User has provided valid credentials, expected tag is not found in
        the tag list, and "add" and "remove" is present in demisto args.
    When:
        extrahop-devices-tag command is called.
    Then:
        Raise error with informative error message.
    """
    with pytest.raises(ExtraHop_v2.DemistoException) as e:
        ExtraHop_v2.parse_location_header(location)
    assert str(e.value) == "Error unable to parse ExtraHop API response location header."


def test_parse_location_header():
    """Test case scenario to parse location data from "client.create_new_tag" API response.

    Given:
        User has provided valid credentials, expected tag is not found in
        the tag list, and "add" and "remove" is present in demisto args.
    When:
        extrahop-devices-tag command is called.
    Then:
        Return tag id from the response.
    """
    tag_id = ExtraHop_v2.parse_location_header("US/12")

    assert tag_id == "12"


@pytest.mark.parametrize("on_cloud", [False, True])
def test_extrahop_devices_search_command_success(on_cloud, requests_mock):
    """
    Test case scenario for successful execution of device search command.

    Given:
     - User has provided valid arguments.
    When:
     - devices_search_command is called.
    Then:
     - Ensure human-readable output is correct.
     - Ensure outputs prefix is correct.
    """
    client = init_mock_client(requests_mock, on_cloud=on_cloud)
    args = {"limit": 2}
    expected_response = load_mock_response("devices_search_response_success.json")
    networks = load_mock_response("get_networks.json")
    expected_readable_output = load_file("devices_search_response_success.md")
    requests_mock.post(f"{BASE_URL}/api/v1/devices/search", json=expected_response, status_code=200)
    requests_mock.get(f"{BASE_URL}/api/v1/networks", json=networks, status_code=200)

    result = ExtraHop_v2.devices_search_command(client, args, False)
    assert result.outputs_prefix == EXTRAHOP_DEVICE
    assert result.readable_output == expected_readable_output


def test_extrahop_devices_search_command_success_empty_response(requests_mock):
    """
    Test case scenario for successful execution of device search command.

    Given:
     - User has provided valid arguments.
    When:
     - devices_search_command is called.
    Then:
     - Ensure human-readable output is correct.
     - Ensure outputs prefix is correct.
    """
    client = init_mock_client(requests_mock, on_cloud=False)
    args = {"limit": 2}
    expected_response = []
    networks = load_mock_response("get_networks.json")
    expected_readable_output = "No Devices found"
    requests_mock.post(f"{BASE_URL}/api/v1/devices/search", json=expected_response, status_code=200)
    requests_mock.get(f"{BASE_URL}/api/v1/networks", json=networks, status_code=200)

    result = ExtraHop_v2.devices_search_command(client, args, False)
    assert result.outputs_prefix == EXTRAHOP_DEVICE
    assert result.readable_output == expected_readable_output


@pytest.mark.parametrize(
    "args, message",
    [
        (
            {"role": "dbserver"},
            f"dbserver is an invalid value for role. Possible values are: {ExtraHop_v2.VALID_DEVICE_ROLES}",
        ),
        (
            {"software": "MacOS"},
            f"MacOS is an invalid value for software. Possible values are: {ExtraHop_v2.VALID_DEVICE_SOFTWARES}",
        ),
        (
            {"vendor": "lenovo"},
            f"lenovo is an invalid value for vendor. Possible values are: {ExtraHop_v2.VALID_DEVICE_VENDORS}",
        ),
        (
            {"match_type": "nor"},
            f"nor is an invalid value for match_type. Possible values are: {ExtraHop_v2.VALID_DEVICE_MATCH_TYPES}",
        ),
        (
            {"operator": "=="},
            f"== is an invalid value for operator. Possible values are: {ExtraHop_v2.VALID_DEVICE_OPERATORS}",
        ),
        (
            {"activity": "abc_client"},
            f"abc_client is an invalid value for activity. Possible values are: {ExtraHop_v2.VALID_DEVICE_ACTIVITIES}",
        ),
    ],
)
def test_extrahop_devices_search_command_with_invalid_arguments(args, message, requests_mock):
    """
    Test case scenario for invalid arguments while execution of device search command.

    Given:
     - User has provided invalid arguments.
    When:
     - devices_search_command is called.
    Then:
     - Ensure appropriate error is raised.
    """
    client = init_mock_client(requests_mock, on_cloud=False)
    with pytest.raises(ExtraHop_v2.InvalidValueError) as error:
        ExtraHop_v2.devices_search_command(client, args, False)

    assert str(error.value) == message


def test_extrahop_devices_search_command_failure_400(requests_mock):
    """
    Test case scenario for execution of device search command when invalid id is passed.

    Given:
     - User has provided invalid arguments.
    When:
     - devices_search_command is called.
    Then:
     - Ensure appropriate error is generated.
    """
    client = init_mock_client(requests_mock, on_cloud=False)
    args = {"limit": "2"}
    expected_response = {
        "type": "invalid_request",
        "detail": "'limit' must be one of the following types: int",
    }
    requests_mock.post(f"{BASE_URL}/api/v1/devices/search", json=expected_response, status_code=400)
    with pytest.raises(Exception) as error:
        ExtraHop_v2.devices_search_command(client, args, False)
    assert (
        str(error.value) == 'Error in API call [400] - None\n{"type": "invalid_request", "detail": "\'limit\' must be one'
        ' of the following types: int"}'
    )


def test_extrahop_devices_search_command_failure_401(requests_mock):
    """
    Test case scenario for execution of device search command when authentication is invalid.

    Given:
     - User has provided valid arguments.
    When:
     - devices_search_command is called.
    Then:
     - Ensure appropriate error is generated.
    """
    client = init_mock_client(requests_mock, on_cloud=False)
    args = {"limit": 2}
    expected_response = {"error_message": INVALID_AUTH_HEADER}

    requests_mock.post(f"{BASE_URL}/api/v1/devices/search", json=expected_response, status_code=401)
    with pytest.raises(Exception) as error:
        ExtraHop_v2.devices_search_command(client, args, False)
    assert str(error.value) == 'Error in API call [401] - None\n{"error_message": "' + INVALID_AUTH_HEADER + '"}'


@pytest.mark.parametrize(
    "args, message",
    [
        ({"ip_or_id": "10:1:1"}, "Error parsing IP Address 10:1:1"),
    ],
)
def test_extrahop_protocols_get_invalid_ip(args, message, requests_mock):
    """
    Test case scenario for passing invalid ip while execution of protocol get command.

    Given:
     - User has provided invalid ip address.
    When:
     - protocols_get_command is called.
    Then:
     - Ensure appropriate error is raised.
    """
    client = init_mock_client(requests_mock, on_cloud=False)
    with pytest.raises(Exception) as error:
        ExtraHop_v2.protocols_get_command(client, args, False)

    assert str(error.value) == message


def test_extrahop_protocols_get_failure_404(requests_mock):
    """
    Test case scenario for execution of protocol get command when id is not found.

    Given:
     - User has provided valid arguments.
    When:
     - protocols_get_command is called.
    Then:
     - Ensure appropriate error is generated.
    """
    client = init_mock_client(requests_mock, on_cloud=False)
    args = {"ip_or_id": "23133113"}
    expected_response = '{"error_message": "The specified object was not found."}'
    requests_mock.get(
        f"{BASE_URL}/api/v1/devices/{args['ip_or_id']}",
        json=expected_response,
        status_code=404,
    )
    with pytest.raises(Exception) as error:
        ExtraHop_v2.protocols_get_command(client, args, False)
    assert (
        str(error.value) == 'Error in API call [404] - None\n"{\\"error_message\\": \\"The specified object was '
        'not found.\\"}"'
    )


def test_extrahop_protocols_get_failure_401(requests_mock):
    """
    Test case scenario for execution of protocol get command when authentication is invalid.

    Given:
     - User has provided valid arguments.
    When:
     - protocols_get_command is called.
    Then:
     - Ensure appropriate error is generated.
    """
    client = init_mock_client(requests_mock, on_cloud=False)
    args = {"ip_or_id": "2"}
    expected_response = {"error_message": INVALID_AUTH_HEADER}

    requests_mock.get(
        f"{BASE_URL}/api/v1/devices/{args['ip_or_id']}",
        json=expected_response,
        status_code=401,
    )
    with pytest.raises(Exception) as error:
        ExtraHop_v2.protocols_get_command(client, args, False)
    assert str(error.value) == 'Error in API call [401] - None\n{"error_message": "' + INVALID_AUTH_HEADER + '"}'


@pytest.mark.parametrize("on_cloud", [False, True])
def test_extrahop_protocols_get_success_get_device_by_id(on_cloud, requests_mock):
    """
    Test case scenario for successful execution of protocol get command when id is passed.

    Given:
     - User has provided valid arguments.
    When:
     - protocols_get_command is called.
    Then:
     - Ensure human-readable output is correct.
     - Ensure outputs prefix is correct.
    """
    client = init_mock_client(requests_mock, on_cloud=on_cloud)
    args = {"ip_or_id": "3564"}
    expected_response = load_mock_response("protocols_get_success_get_device_by_id.json")
    expected_readable_output = load_file("get_protocol_readable_output.md")
    expected_activity_map = load_mock_response(GET_PROTOCOL_FILE)
    expected_networks = load_mock_response(GET_PROTOCOL_NETWORKS_FILE)
    requests_mock.get(
        f"{BASE_URL}/api/v1/devices/{args['ip_or_id']}",
        json=expected_response,
        status_code=200,
    )
    requests_mock.post(
        f"{BASE_URL}/api/v1/activitymaps/query",
        json=expected_activity_map,
        status_code=200,
    )
    requests_mock.get(f"{BASE_URL}/api/v1/networks", json=expected_networks, status_code=200)
    result = ExtraHop_v2.protocols_get_command(client, args, False)

    assert result.outputs_prefix == EXTRAHOP_DEVICE
    assert result.readable_output == expected_readable_output


def test_extrahop_protocols_get_success_get_device_by_ip(requests_mock):
    """
    Test case scenario for successful execution of protocol get command when valid ip is passed.

    Given:
     - User has provided valid arguments.
    When:
     - protocols_get_command is called.
    Then:
     - Ensure human-readable output is correct.
     - Ensure outputs prefix is correct.
    """
    client = init_mock_client(requests_mock, on_cloud=False)
    args = {"ip_or_id": "0.0.0.0"}
    expected_response = load_mock_response("protocols_get_success_get_device_by_ip.json")
    expected_readable_output = load_file("get_protocol_readable_output.md")
    expected_activity_map = load_mock_response(GET_PROTOCOL_FILE)
    expected_networks = load_mock_response(GET_PROTOCOL_NETWORKS_FILE)
    requests_mock.post(f"{BASE_URL}/api/v1/devices/search", json=expected_response, status_code=200)
    requests_mock.post(
        f"{BASE_URL}/api/v1/activitymaps/query",
        json=expected_activity_map,
        status_code=200,
    )
    requests_mock.get(f"{BASE_URL}/api/v1/networks", json=expected_networks, status_code=200)
    result = ExtraHop_v2.protocols_get_command(client, args, False)

    assert result.outputs_prefix == EXTRAHOP_DEVICE
    assert result.readable_output == expected_readable_output


def test_extrahop_protocols_get_ip_not_present_in_extrahop(requests_mock):
    """Test case scenario for passing valid ip but is not present in extrahop while execution of protocol get command.

    Given:
     - User has provided valid arguments.
    When:
     - protocols_get_command is called.
    Then:
     - Ensure appropriate error is raised.
    """
    client = init_mock_client(requests_mock, on_cloud=False)
    args = {"ip_or_id": "0.0.0.0"}
    expected_error_message = f"Error the IP Address {args['ip_or_id']} was not found in ExtraHop."  # noqa: E713
    expected_response = []
    requests_mock.post(f"{BASE_URL}/api/v1/devices/search", json=expected_response, status_code=200)
    with pytest.raises(DemistoException) as error:
        ExtraHop_v2.protocols_get_command(client, args, False)

    assert str(error.value) == expected_error_message


def test_extrahop_protocols_get_device_by_id_discovery(requests_mock):
    """
    Test case scenario for execution of protocol get command when the device has analysis field as discovery.

    Given:
     - User has provided valid arguments.
    When:
     - protocols_get_command is called.
    Then:
     - Ensure human-readable output is correct.
     - Ensure outputs prefix is correct.
    """
    client = init_mock_client(requests_mock, on_cloud=False)
    args = {"ip_or_id": "567"}
    expected_response = load_mock_response("protocols_get_success_get_device_by_id_discovery.json")
    expected_activity_map = load_mock_response(GET_PROTOCOL_FILE)
    expected_networks = load_mock_response(GET_PROTOCOL_NETWORKS_FILE)
    requests_mock.get(
        f"{BASE_URL}/api/v1/devices/{args['ip_or_id']}",
        json=expected_response,
        status_code=200,
    )
    requests_mock.post(
        f"{BASE_URL}/api/v1/activitymaps/query",
        json=expected_activity_map,
        status_code=200,
    )
    requests_mock.get(f"{BASE_URL}/api/v1/networks", json=expected_networks, status_code=200)
    result = ExtraHop_v2.protocols_get_command(client, args, False)

    assert result.outputs_prefix == EXTRAHOP_DEVICE
    assert result.readable_output == "No Protocol activity found"


@pytest.mark.parametrize("on_cloud", [False, True])
def test_alerts_rules_get_command_success(on_cloud, requests_mock):
    """Test case scenario for successful execution of alert rules get command.

    Given:
     - User has provided valid credentials.
    When:
     - alerts_rules_get_command is called.
    Then:
     - Ensure human-readable output is correct.
     - Ensure outputs prefix is correct.
    """
    client = init_mock_client(requests_mock, on_cloud=on_cloud)
    expected_response = load_mock_response("alerts_get.json")

    expected_readable_output = load_file("alerts_get.md")
    requests_mock.get(f"{BASE_URL}/api/v1/alerts", json=expected_response)
    result = ExtraHop_v2.alerts_rules_get_command(client)
    assert result.outputs_prefix == "ExtraHop.Alert"
    assert result.readable_output == expected_readable_output


def test_alerts_rules_get_command_success_no_alerts(requests_mock):
    """Test case scenario for successful execution of alert rules get command.

    Given:
     - User has provided valid credentials.
    When:
     - alerts_rules_get_command is called.
    Then:
     - Ensure human-readable output is correct.
     - Ensure outputs prefix is correct.
    """
    client = init_mock_client(requests_mock, on_cloud=False)
    expected_response = []
    expected_readable_output = "No Alerts were found."
    requests_mock.get(f"{BASE_URL}/api/v1/alerts", json=expected_response, status_code=200)
    result = ExtraHop_v2.alerts_rules_get_command(client)

    assert result.outputs_prefix == "ExtraHop.Alert"
    assert result.readable_output == expected_readable_output


def test_alerts_rules_get_command_failure_401(requests_mock):
    """Test case scenario for execution of alert rules get command when authentication is invalid.

    Given:
     - User has provided invalid credentials.
    When:
     - alerts_rules_get_command is called.
    Then:
     - Ensure appropriate error is generated.
    """
    client = init_mock_client(requests_mock, on_cloud=False)
    expected_response = {"error_message": INVALID_AUTH_HEADER}

    requests_mock.get(f"{BASE_URL}/api/v1/alerts", json=expected_response, status_code=401)
    with pytest.raises(Exception) as error:
        ExtraHop_v2.alerts_rules_get_command(client)
    assert str(error.value) == 'Error in API call [401] - None\n{"error_message": "' + INVALID_AUTH_HEADER + '"}'


def test_activity_map_get_command_failure_401(requests_mock):
    """Test case scenario for execution of activity map get command when authentication is invalid.

    Given:
     - User has provided invalid credentials.
    When:
     - activity_map_get_command is called.
    Then:
     - Ensure appropriate error is generated.
    """
    client = init_mock_client(requests_mock, on_cloud=False)
    args = {"ip_or_id": "3564"}
    expected_response = {"error_message": INVALID_AUTH_HEADER}

    requests_mock.get(
        f"{BASE_URL}/api/v1/devices/{args['ip_or_id']}",
        json=expected_response,
        status_code=401,
    )
    with pytest.raises(Exception) as error:
        ExtraHop_v2.activity_map_get_command(client, args, False)
    assert str(error.value) == 'Error in API call [401] - None\n{"error_message": "' + INVALID_AUTH_HEADER + '"}'


def test_activity_map_get_command_failure_404(requests_mock):
    """Test case scenario for execution of activity map get command when id is not found.

    Given:
     - User has provided valid arguments.
    When:
     - activity_map_get_command is called.
    Then:
     - Ensure appropriate error is generated.
    """
    client = init_mock_client(requests_mock, on_cloud=False)
    args = {"ip_or_id": "23133113"}
    expected_response = '{"error_message": "The specified object was not found."}'
    requests_mock.get(
        f"{BASE_URL}/api/v1/devices/{args['ip_or_id']}",
        json=expected_response,
        status_code=404,
    )
    with pytest.raises(Exception) as error:
        ExtraHop_v2.activity_map_get_command(client, args, False)
    assert (
        str(error.value) == 'Error in API call [404] - None\n"{\\"error_message\\": \\"The specified object was '
        'not found.\\"}"'
    )


@pytest.mark.parametrize(
    "args, message",
    [
        (
            {"ip_or_id": "10.1.2"},
            "'10.1.2' does not appear to be an IPv4 or IPv6 address",
        ),
        (
            {"ip_or_id": "0.0.0.0", "time_interval": "1 hour"},
            f"1 hour is an invalid value for time_interval. Possible values are: {ExtraHop_v2.VALID_TIME_INTERVALS}",
        ),
        (
            {"ip_or_id": "0.0.0.0", "from_time": "30m"},
            'Invalid number: "from_time"="30m"',
        ),
        (
            {"ip_or_id": "0.0.0.0", "until_time": "50m"},
            'Invalid number: "until_time"="50m"',
        ),
        (
            {"ip_or_id": "0.0.0.0", "peer_role": "temp"},
            f"temp is an invalid value for peer_role. Possible values are: {ExtraHop_v2.VALID_PEER_ROLES}",
        ),
        (
            {"ip_or_id": "0.0.0.0", "protocol": "ICAMP"},
            f"ICAMP is an invalid value for protocol. Possible values are: {ExtraHop_v2.VALID_PROTOCOLS}",
        ),
    ],
)
def test_activity_map_get_command_invalid_arguments(args, message, requests_mock):
    """Test case scenario for execution of activity map get command when invalid arguments are passed.

    Given:
     - User has provided invalid arguments.
    When:
     - activity_map_get_command is called.
    Then:
     - Ensure appropriate error is generated.
    """
    client = init_mock_client(requests_mock, on_cloud=False)
    with pytest.raises(Exception) as error:
        ExtraHop_v2.activity_map_get_command(client, args, False)
    assert str(error.value) == message


@pytest.mark.parametrize(
    "args, message",
    [
        (
            {"ip_or_id": "3564", "from_time": "30"},
            "When using a fixed time range both from_time and until_time timestamps need to be provided.",
        ),
        (
            {"ip_or_id": "3564", "until_time": "30"},
            "When using a fixed time range both from_time and until_time timestamps need to be provided.",
        ),
    ],
)
def test_activity_map_get_command_failure_when_either_from_time_or_until_time_is_given(args, message, requests_mock):
    """Test case scenario for execution of activity map get command when either from_time or until_time is given.

    Given:
     - User has provided valid arguments.
    When:
     - activity_map_get_command is called.
    Then:
     - Ensure appropriate error is generated.
    """
    client = init_mock_client(requests_mock, on_cloud=False)
    expected_response = load_mock_response("activity_map_devices_by_id.json")
    requests_mock.get(
        f"{BASE_URL}/api/v1/devices/{args['ip_or_id']}",
        json=expected_response,
        status_code=200,
    )
    with pytest.raises(Exception) as error:
        ExtraHop_v2.activity_map_get_command(client, args, False)
    assert str(error.value) == message


@pytest.mark.parametrize("on_cloud", [False, True])
def test_activity_map_get_command_success_id(on_cloud, requests_mock):
    """Test case scenario for successful execution of activity map get command when valid id is passed.

    Given:
     - User has provided valid arguments.
    When:
     - activity_map_get_command is called.
    Then:
     - Ensure human-readable output is correct.
     - Ensure outputs prefix is correct.
    """
    client = init_mock_client(requests_mock, on_cloud=on_cloud)
    args = {"ip_or_id": "3564"}
    expected_response = load_mock_response("activity_map_devices_by_id.json")
    expected_network = load_mock_response("activity_get_networks.json")
    expected_readable_output = load_file("activity_map_get_readable_output.md")
    requests_mock.get(
        f"{BASE_URL}/api/v1/devices/{args['ip_or_id']}",
        json=expected_response,
        status_code=200,
    )
    requests_mock.get(f"{BASE_URL}/api/v1/networks", json=expected_network, status_code=200)
    result = ExtraHop_v2.activity_map_get_command(client, args, False)

    assert result.outputs_prefix == "ExtraHop.ActivityMap"
    assert result.readable_output == str(expected_readable_output)


def test_activity_map_get_command_success_ip(requests_mock):
    """Test case scenario for successful execution of activity map get command when valid ip is passed.

    Given:
     - User has provided valid arguments.
    When:
     - activity_map_get_command is called.
    Then:
     - Ensure human-readable output is correct.
     - Ensure outputs prefix is correct.
    """
    client = init_mock_client(requests_mock, on_cloud=False)
    args = {"ip_or_id": "0.0.0.0"}
    expected_response = load_mock_response("activity_map_devices_by_ip.json")
    expected_network = load_mock_response("activity_get_networks.json")
    expected_readable_output = load_file("activity_map_get_readable_output.md")
    requests_mock.post(f"{BASE_URL}/api/v1/devices/search", json=expected_response, status_code=200)
    requests_mock.get(f"{BASE_URL}/api/v1/networks", json=expected_network, status_code=200)
    result = ExtraHop_v2.activity_map_get_command(client, args, False)

    assert result.outputs_prefix == "ExtraHop.ActivityMap"
    assert result.readable_output == str(expected_readable_output)


def setup_peers_get_command_by_id(is_discovery: bool, is_check_for_empty: bool, requests_mock) -> ExtraHop_v2.CommandResults:
    """Mock the result of extrahop-peers-get command.

    Args:
        is_discovery: Set true for mock response has analysis set to discovery.
        is_check_for_empty: Set true for test empty device output.
        requests_mock: This parameter is used for creating mock_request.

    Returns:
        Command result object which have output for extrahop-peers-get command.
    """
    mock_response_uuid = load_mock_response("appliance_uuids_get_success.json")
    mock_response_for_activity_map = load_mock_response("get_peers_activity_maps.json")
    mock_response_for_get_device_id = load_mock_response("get_peer_device_by_id.json")

    if is_discovery:
        mock_response_for_get_device_id["analysis"] = "discovery"
    if is_check_for_empty:
        mock_response_for_activity_map["edges"] = []

    requests_mock.post(
        f"{BASE_URL}/api/v1/activitymaps/query",
        status_code=200,
        json=mock_response_for_activity_map,
    )
    requests_mock.get(f"{BASE_URL}/api/v1/networks", status_code=200, json=mock_response_uuid)
    requests_mock.get(
        f"{BASE_URL}/api/v1/devices/206",
        status_code=200,
        json=mock_response_for_get_device_id,
    )

    mock_client = init_mock_client(requests_mock, on_cloud=False)
    args = {
        "ip_or_id": 206,
        "query_from": "-60m",
        "query_until": "0",
        "protocol": "any",
        "peer_role": "any",
    }
    result = ExtraHop_v2.peers_get_command(mock_client, args, False)
    return result


def test_peers_get_command_on_prem_by_ip_success(requests_mock) -> None:
    """Test case scenario to validate peers_get command with valid ip value.

    Given:
     - User has provided valid ip and arguments.
    When:
     - Peers-get-command is called.
    Then:
     - Ensure number of arguments are correct.
     - Ensure command returns proper output.

    """
    mock_response_devices = load_mock_response("get_peers_device_search.json")
    mock_response_uuid = load_mock_response("appliance_uuids_get_success.json")

    mock_response_for_activity_map = load_mock_response("get_peers_activity_maps.json")
    mock_response_for_get_device_id = load_mock_response("get_peer_device_by_id.json")

    requests_mock.post(f"{BASE_URL}/api/v1/devices/search", status_code=200, json=mock_response_devices)
    requests_mock.post(
        f"{BASE_URL}/api/v1/activitymaps/query",
        status_code=200,
        json=mock_response_for_activity_map,
    )

    requests_mock.get(f"{BASE_URL}/api/v1/networks", status_code=200, json=mock_response_uuid)
    requests_mock.get(
        f"{BASE_URL}/api/v1/devices/3564",
        status_code=200,
        json=mock_response_for_get_device_id,
    )

    mock_client = init_mock_client(requests_mock, on_cloud=False)
    args = {
        "ip_or_id": "0.0.0.0",
        "query_from": "-60m",
        "query_until": "0",
        "protocol": "any",
    }

    result = ExtraHop_v2.peers_get_command(mock_client, args, False)

    peer_get_success_result = load_mock_response("peer_get_command_on_prem_ip_success.json")

    peer_get_hr_result = load_file("peer_get_command_on_prem_ip_success.md")

    assert result.outputs == ExtraHop_v2.remove_empty_elements_from_response(peer_get_success_result)
    assert result.readable_output == peer_get_hr_result
    assert result.outputs_key_field == "id"
    assert result.raw_response == peer_get_success_result
    assert result.outputs_prefix == EXTRAHOP_DEVICE


def test_peers_get_command_on_prem_by_id_success(requests_mock) -> None:
    """Test case scenario to validate peers_get command with valid id value.

    Given:
     - User has provided valid ip and arguments.
    When:
     - Peers-get-command is called.
    Then:
     - Ensure number of arguments are correct.
     - Ensure command returns proper output.

    """
    result = setup_peers_get_command_by_id(False, False, requests_mock)
    peer_get_success_result = load_mock_response("peer_get_command_on_prem_id_success.json")

    peer_get_hr_result = load_file("peer_get_command_on_prem_id_success.md")

    assert result.outputs == ExtraHop_v2.remove_empty_elements_from_response(peer_get_success_result)
    assert result.readable_output == peer_get_hr_result
    assert result.outputs_key_field == "id"
    assert result.raw_response == peer_get_success_result
    assert result.outputs_prefix == EXTRAHOP_DEVICE


def test_peers_get_command_with_discovery_success(requests_mock) -> None:
    """Test case scenario command with mock response has set analysis field set to discovery runs successfully.

    Given:
     - User has provided valid id and arguments and response has analysis field set to discovery.
    When:
     - Peers-get-command is called.
    Then:
     - Ensure number of arguments are correct.
     - Ensure command returns proper output.
    """
    result = setup_peers_get_command_by_id(True, False, requests_mock)

    peer_discovery_success_result = load_mock_response("get_peer_discovery_output.json")
    peer_discovery_hr = load_file("get_peer_discovery_output.md")

    assert peer_discovery_success_result == result.raw_response
    assert peer_discovery_hr == result.readable_output


@pytest.mark.parametrize(
    "args, error_message",
    [
        (
            {
                "ip_or_id": 206,
                "query_from": "-60m",
                "query_until": "0",
                "protocol": "asdf",
                "peer_role": "any",
            },
            f"asdf is an invalid value for protocol. Possible values are: {ExtraHop_v2.VALID_PROTOCOLS}",
        ),
        (
            {
                "ip_or_id": 206,
                "query_from": "-60m",
                "query_until": "0",
                "protocol": "any",
                "peer_role": "asdf",
            },
            f"asdf is an invalid value for peer_role. Possible values are: {ExtraHop_v2.VALID_PEER_ROLES}",
        ),
    ],
)
def test_validate_arguments_for_get_peer_command_failure(args: dict, error_message: str, requests_mock) -> None:
    """Test case scenario for invalid arguments while execution of peers-get-command.

    Given:
     - User has provided invalid arguments.
    When:
     - Peers-get-command is called.
    Then:
     - Ensure appropriate error raised.
    """
    mock_client = init_mock_client(on_cloud=False, requests_mock=requests_mock)
    with pytest.raises(ExtraHop_v2.InvalidValueError) as error:
        _ = ExtraHop_v2.peers_get_command(mock_client, args, False)
    assert error_message == str(error.value)


def test_validate_ip_for_get_peer_command_failure(requests_mock) -> None:
    """Test case scenario for invalid ip value while execution of peers-get-command.

    Given:
     - User has provided invalid ip.
    When:
     - Peers-get-command is called.
    Then:
     - Ensure appropriate error raised.
    """
    args = {"ip_or_id": "1:1:1"}
    mock_client = init_mock_client(on_cloud=False, requests_mock=requests_mock)
    with pytest.raises(ExtraHop_v2.DemistoException) as error:
        _ = ExtraHop_v2.peers_get_command(mock_client, args, False)
    assert str(error.value) == "Error parsing IP Address 1:1:1"


def test_empty_response_of_device_failure_for_peers_get_command(requests_mock) -> None:
    """Test case scenario for validating provided ip is not found.

    Given:
     - User has provided valid arguments.
    When:
     - Peers-get-command is called but provided ip can't find on ExtraHop instance.
    Then:
     - Ensure appropriate error raised.
    """
    requests_mock.post(f"{BASE_URL}/api/v1/devices/search", status_code=404, json=[])
    mock_client = init_mock_client(on_cloud=False, requests_mock=requests_mock)
    args = {
        "ip_or_id": "0.0.0.0",
        "query_from": "-60m",
        "query_until": "0",
        "protocol": "any",
    }
    with pytest.raises(ExtraHop_v2.DemistoException) as error:
        _ = ExtraHop_v2.peers_get_command(mock_client, args, False)
    assert str(error.value) == "Error in API call [404] - None\n[]"


def test_for_prepare_device_get_output_success(requests_mock) -> None:
    """Test case scenario for validating empty response.

    Given:
     - User has provided valid ip and arguments.
    When:
     - Peers-get-command is called but ExtraHop returns empty response.
    Then:
     - Ensure command returns proper output.
    """
    result = setup_peers_get_command_by_id(False, True, requests_mock)

    assert result.readable_output == "No Devices found"
    assert result.raw_response == []


def test_module_on_prem_success(requests_mock) -> None:
    """Test case scenario for test connection with on-prem instance.

    Given:
     - User has provided valid arguments.
    When:
     - Test_module is called.
    Then:
     - Ensure command returns proper output.
    """
    mock_client = init_mock_client(on_cloud=False, requests_mock=requests_mock)
    success_response = load_mock_response("test_module_on_prem.json")
    requests_mock.get(f"{BASE_URL}/api/v1/extrahop", status_code=200, json=success_response)
    requests_mock.get(
        f"{BASE_URL}/api/v1/extrahop/version",
        status_code=200,
        json={"version": "9.9.9"},
    )
    result = ExtraHop_v2.test_module(mock_client)

    assert result == "ok"


def test_module_on_cloud(requests_mock) -> None:
    """Test case scenario for test connection with on-cloud instance returns success.

    Given:
     - User has provided valid arguments.
    When:
     - Test_module is called.
    Then:
     - Ensure command returns proper output.
    """
    mock_client = init_mock_client(on_cloud=False, requests_mock=requests_mock)
    success_response = load_mock_response("test_module_on_cloud.json")
    requests_mock.get(f"{BASE_URL}/api/v1/extrahop", status_code=200, json=success_response)
    requests_mock.get(
        f"{BASE_URL}/api/v1/extrahop/version",
        status_code=200,
        json={"version": "9.9.9"},
    )
    result = ExtraHop_v2.test_module(mock_client)

    assert result == "ok"


def test_module_failure_on_prem_failure(requests_mock) -> None:
    """Unit test case scenario for test connection with on-prem instance for validating failure.

    Given:
     - User has provided invalid arguments.
    When:
     - Test_module is called.
    Then:
     - Ensure appropriate error raised.
    """
    mock_client = init_mock_client(on_cloud=False, requests_mock=requests_mock)
    requests_mock.get(f"{BASE_URL}/api/v1/extrahop", status_code=401, json=[])
    with pytest.raises(ValueError) as error:
        _ = ExtraHop_v2.test_module(mock_client)

    assert str(error.value) == "Error code 401: Invalid credentials provided."


@pytest.mark.parametrize(
    "parameters, error_msg, last_run",
    [
        ({"first_fetch": "++"}, '"++" is not a valid date', {}),
        (
            {"advanced_filter": "{'risk_score_min':50}"},
            "Invalid JSON string provided for advanced filter.",
            {},
        ),
    ],
)
def test_fetch_detection_when_invalid_arguments_provided(parameters, error_msg, last_run, requests_mock):
    """Test case scenario for execution of fetch_detections when invalid arguments are provided.

    Given:
       - Parameters for fetch_incident
    When:
       - Calling `fetch_incidents` function
    Then:
       - Returns a valid error message.
    """
    client = init_mock_client(requests_mock, on_cloud=False)
    requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"})
    with pytest.raises(ValueError) as err:
        ExtraHop_v2.fetch_incidents(client, parameters, last_run, False)
    assert str(err.value) == error_msg


def test_fetch_detections_failure_when_firmware_version_is_outdated(requests_mock):
    """Test case scenario for execution of fetch_detections when ExtraHop firmware version is less than 9.3.0.

    Given:
       - Parameters for fetch_incident
    When:
       - Calling `fetch_incidents` function
    Then:
       - Returns a valid error message.
    """
    client = init_mock_client(requests_mock, on_cloud=False)
    requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.1.2.1943"})
    with pytest.raises(DemistoException) as err:
        ExtraHop_v2.fetch_incidents(client, {}, {}, False)
    assert (
        str(err.value) == "This integration works with ExtraHop firmware version greater than or equal to 9.3.0 "
        "and current firmware version being used by you is 9.1.2"
    )


@pytest.mark.parametrize("advanced_filter", ["{}", '{"categories":["sec.attack"]}'])
def test_fetch_detection_success_with_last_run(requests_mock, advanced_filter):
    """Test case scenario for execution of fetch_detections when last_run is present.

    Given:
        - command arguments for fetch_incident
    When:
        - Calling `fetch_incidents` function
    Then:
        - Returns a valid output
    """
    incidents = load_mock_response("mock_incidents.json")

    requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"})

    mock_response = load_mock_response("fetch_detections_success.json")
    requests_mock.post(f"{BASE_URL}/api/v1/detections/search", json=mock_response)

    mock_device_data = load_mock_response("mock_device_data.json")
    requests_mock.get(f"{BASE_URL}/api/v1/devices/1904", json=mock_device_data)

    mock_time = datetime.datetime.now()
    mock_time = int((mock_time.timestamp() + 10) * 1000)  # adding 10 seconds to time and then convert to milliseconds
    client = init_mock_client(requests_mock, on_cloud=False)
    last_run = {
        "detection_start_time": 1676896891452,
        "offset": 0,
        "version_recheck_time": mock_time,
    }
    actual_incidents, next_run = ExtraHop_v2.fetch_incidents(client, {"advanced_filter": advanced_filter}, last_run, False)

    assert next_run == {
        "detection_start_time": 1673518450001,
        "offset": 0,
        "version_recheck_time": mock_time,
        "already_fetched": [997222],
    }
    assert actual_incidents[0]["name"] == incidents[0]["name"]
    assert actual_incidents[0]["occurred"] == incidents[0]["occurred"]
    assert actual_incidents[0]["rawJSON"] == json.dumps(incidents[0]["rawJSON"])


def test_fetch_detection_participants_is_empty(requests_mock):
    """Test case scenario for execution of fetch_detections when participant is empty list.

    Given:
        - command arguments for fetch_incident
    When:
        - Calling `fetch_incidents` function
    Then:
        - Returns a valid output
    """
    incidents = load_mock_response("mock_incidents_no_participants.json")

    requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"})

    mock_response = load_mock_response("fetch_detections_empty_participants.json")
    requests_mock.post(f"{BASE_URL}/api/v1/detections/search", json=mock_response)

    mock_time = datetime.datetime.now()
    mock_time = int((mock_time.timestamp() + 10) * 1000)  # adding 10 seconds to time and then convert to milliseconds
    client = init_mock_client(requests_mock, on_cloud=False)
    last_run = {
        "detection_start_time": 1676896891452,
        "offset": 0,
        "version_recheck_time": mock_time,
    }
    actual_incidents, next_run = ExtraHop_v2.fetch_incidents(client, {}, last_run, False)

    assert next_run == {
        "detection_start_time": 1673518450001,
        "offset": 0,
        "version_recheck_time": mock_time,
        "already_fetched": [997222],
    }
    assert actual_incidents[0]["name"] == incidents[0]["name"]
    assert actual_incidents[0]["occurred"] == incidents[0]["occurred"]
    assert actual_incidents[0]["rawJSON"] == json.dumps(incidents[0]["rawJSON"])


@mock.patch("ExtraHop_v2.MAX_FETCH", 1)
def test_fetch_detections_success_when_detections_equal_to_max_fetch(requests_mock):
    """Test case scenario for execution of fetch_detections when no of records are equal or greater than max_fetch.

    Given:
        - command arguments for fetch_incidents
    When:
        - Calling `fetch_incidents` function
    Then:
        - Returns a valid output
    """
    incidents = load_mock_response("mock_incidents.json")

    requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"})

    mock_response = load_mock_response("fetch_detections_success.json")
    requests_mock.post(f"{BASE_URL}/api/v1/detections/search", json=mock_response)

    mock_device_data = load_mock_response("mock_device_data.json")
    requests_mock.get(f"{BASE_URL}/api/v1/devices/1904", json=mock_device_data)

    client = init_mock_client(requests_mock, on_cloud=False)
    actual_incidents, next_run = ExtraHop_v2.fetch_incidents(client, {}, {}, False)

    assert next_run["offset"] == 1
    assert actual_incidents[0]["name"] == incidents[0]["name"]
    assert actual_incidents[0]["occurred"] == incidents[0]["occurred"]
    assert actual_incidents[0]["rawJSON"] == json.dumps(incidents[0]["rawJSON"])


def test_fetch_incident_empty_response(requests_mock):
    """Test case scenario for execution of fetch_detections when no records are returned from API.

    Given:
        - command arguments for fetch_incidents
    When:
        - Calling `fetch_incidents` function
    Then:
        - Returns a valid output
    """
    client = init_mock_client(requests_mock, on_cloud=False)
    last_run = {"update_or_mod_time": "update_time"}
    parameters = {"first_fetch": "1 Jan"}
    requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"})
    requests_mock.post(f"{BASE_URL}/api/v1/detections/search", json=[], status_code=200)
    actual_incidents, next_run = ExtraHop_v2.fetch_incidents(client, parameters, last_run, False)

    assert actual_incidents == []


def test_test_module_failure_extrahop_version_is_outdated(requests_mock):
    """Test case scenario for execution of test_module when ExtraHop firmware version is outdated.

    Given:
        - command arguments for test_module
    When:
        - Calling `test_module` function
    Then:
        - Returns a valid error message.
    """
    client = init_mock_client(requests_mock, on_cloud=False)
    requests_mock.get(f"{BASE_URL}/api/v1/extrahop", json={})
    requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.1.2.1943"})
    with pytest.raises(DemistoException) as err:
        ExtraHop_v2.test_module(client)
    assert (
        str(err.value) == "This integration works with ExtraHop firmware version greater than or equal to 9.3.0 "
        "and current firmware version being used by you is 9.1.2"
    )


def test_test_module_failure(requests_mock):
    """Test case scenario for failure of test_module.

    Given:
        - command arguments for test_module
    When:
        - Calling `test_module` function
    Then:
        - Returns a valid error message.
    """
    client = init_mock_client(requests_mock, on_cloud=False)
    requests_mock.get(f"{BASE_URL}/api/v1/extrahop", json={})
    requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"})
    with pytest.raises(ValueError) as err:
        ExtraHop_v2.test_module(client)
    assert str(err.value) == "Failed to establish connection with provided credentials."


def test_authenticate_uses_basic_auth(requests_mock) -> None:
    """Test that authenticate sends client credentials via HTTP Basic Auth, not in the request body.

    Given:
        - A cloud ExtraHop client with client_id and client_secret.
    When:
        - authenticate is called to obtain an access token.
    Then:
        - The POST to /oauth2/token uses HTTP Basic Auth with client_id and client_secret.
        - The request body does NOT contain client_id or client_secret.
        - The returned token matches the mocked access_token.
    """
    auth_response = load_mock_response("auth_token.json")
    token_request = requests_mock.post("/oauth2/token", json=auth_response)

    client = ExtraHop_v2.ExtraHopClient(
        base_url=BASE_URL,
        api_key=API_KEY,
        client_id=CLIENT_ID,
        client_secret=CLIENT_SECRET,
        verify=False,
        use_proxy=False,
        ok_codes=(200, 201, 204),
        on_cloud=True,
    )

    token, expires_in = client.authenticate(CLIENT_ID, CLIENT_SECRET)

    assert token == auth_response["access_token"]
    assert expires_in == auth_response["expires_in"]

    last_request = token_request.last_request
    assert last_request.headers.get("Authorization", "").startswith("Basic ")
    body = last_request.text
    assert "client_id" not in body
    assert "client_secret" not in body