VectraRUXEventsDetections

This integration allows the security operations center to create and manage incidents based on Vectra Events Detections.

Network Security · Vectra RUX

Details

IDVectraRUXEventsDetections
ProviderVectra AI
CategoryNetwork Security
From Version6.10.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix Attack Surface Management Cortex Cloud Application Security Cloud Posture Security Cloud Runtime Security EDR Email Security Exposure Management Threat Intelligence Management XSIAM

README

This integration allows the security operations center to create and manage incidents based on Vectra Events Detections.
This integration was integrated and tested with Vectra API v3.5.

Configure Vectra RUX - Network Detection & Response in Cortex

Parameter Description Required
Server URL URL of the Vectra AI platform. True
Client ID Identifies a client or application for authentication and authorization in the Vectra AI platform. True
Client Secret Key Secret key used for secure communication with the Vectra AI platform. True
Fetch incidents   False
Max Fetch The maximum number of events detections to fetch each time. If the value is greater than 200, it will be considered as 200. The maximum is 200. False
First Fetch Time The date or relative timestamp from which to begin fetching events detections.

Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ.

For example: 01 Oct 2025, 01 Mar 2021 04:45:33, 2025-12-17T14:05:44Z.
False
Entity Types Filter by entity type. If not selected, it will fetch all events detections. False
Create Incidents for Prioritized Detections Enabling this checkbox generates incidents for prioritized events detections. If not selected, incidents are created for all events detections. False
Create Incidents for Escalated Detections Enabling this checkbox generates incidents for escalated events detections. If not selected, incidents are created for all events detections. False
Mirroring Direction The mirroring direction in which to mirror the detections. You can mirror ‘Incoming’ (from Vectra to XSOAR), ‘Outgoing’ (from XSOAR to Vectra), or in both directions. False
Mirror tag for notes The tag value should be used to mirror the detection note by adding the same tag in the notes. False
Open Detection on Incident Reopen Enabling this checkbox opens the detection in Vectra when the incident is reopened in XSOAR.

Note: This parameter is only used when the mirroring direction is set to ‘Outgoing’ or ‘Incoming And Outgoing’.
False
Detection Status for Incident Reopen Detection status to set in Vectra when incident is reopened in XSOAR. Default value is ‘Escalated’.

Note: This parameter is only used when open detection on incident reopen is ‘checked’ and the mirroring direction is set to ‘Outgoing’ or ‘Incoming And Outgoing’.
False
Close Detection on Incident Closure Enabling this checkbox closes the detection in Vectra when the incident is closed in XSOAR.

Note: This parameter is only used when the mirroring direction is set to ‘Outgoing’ or ‘Incoming And Outgoing’.
False
Detection Close Reason for Incident Closure Detection close reason to set in Vectra when closing incidents in XSOAR. Default value is ‘Remediated’.

Note: This parameter is only used when close detection on incident closer is ‘checked’ and the mirroring direction is set to ‘Outgoing’ or ‘Incoming And Outgoing’.
False
Incident type   False
Trust any certificate (not secure) When checked, no SSL certificates check will be done when interacting with the Vectra RUX API. It’s insecure. (Default - unchecked) False
Use system proxy settings Use the system proxy settings to reach with the Vectra RUX API. False

Configuration for fetching Vectra RUX Events Detections as an XSOAR Incident

To fetch Vectra RUX Events Detections follow the next steps:

  1. Select Fetches incidents.
  2. Under Classifier, select “N/A”.
  3. Under Incident type, select “Vectra RUX Events Detection”.
  4. Under Mapper (incoming), select “Vectra RUX - Incoming Mapper” for default mapping.
  5. Enter connection parameters. (Server URL, Client ID & Client Secret Key)
  6. Update “Max Fetch” & “First Fetch Time” based on your requirements.
  7. Filter the Detections by the “Entity Type”(Account and Host).
  8. Filter the Detections by “Create Incidents for Prioritized Detections”, “Create Incidents for Escalated Detections”:
    1. Default Behavior: By default, the integration retrieves all event detections across all entity types (Account and Host) and all detection statuses (Open, Acknowledged, Escalated, Paused). This includes both prioritized and non-prioritized detections.
    2. Fetch Only Prioritized Detections: Enable “Create Incidents for Prioritized Detections” to filter out non-prioritized detections. Incidents will be created only for prioritized event detections.
    3. Fetch Only Escalated Detections: Enable “Create Incidents for Escalated Detections” to retrieve all escalated detections, regardless of their priority level.
    4. Fetch Prioritized and Escalated Detections: Enable both “Create Incidents for Prioritized Detections” and “Create Incidents for Escalated Detections”. This configuration retrieves detections that are either prioritized or escalated.
  9. Select the Incident Mirroring Direction:
    1. Incoming - Mirrors changes from the Vectra RUX Detection into the Cortex XSOAR incident.
    2. Outgoing - Mirrors changes from the Cortex XSOAR incident to the Vectra RUX Detection.
    3. Incoming And Outgoing - Mirrors changes both Incoming and Outgoing directions on incidents.
  10. Enter the relevant tag name for mirror notes.
    Note: This value is mapped to the dbotMirrorTags incident field in Cortex XSOAR, which defines how Cortex XSOAR handles notes when you tag them in the War Room. This is required for mirroring notes from Cortex XSOAR to Vectra RUX.
  11. Uncheck the “Open Detection on Incident Reopen” option if you don’t want to open the detection in Vectra when the incident is reopened in XSOAR. This option is only used when the mirroring direction is set to ‘Outgoing’ or ‘Incoming And Outgoing’.
  12. Select the “Detection Status for Incident Reopen” option if you want to set the detection status in Vectra when the incident is reopened in XSOAR. Default value is ‘Escalated’. This option is only used when the “Open Detection on Incident Reopen” option is checked and the mirroring direction is set to ‘Outgoing’ or ‘Incoming And Outgoing’.
  13. Uncheck the “Close Detection on Incident Closure” option if you don’t want to close the detection in Vectra when the incident is closed in XSOAR. This option is only used when the mirroring direction is set to ‘Outgoing’ or ‘Incoming And Outgoing’.
  14. Select the “Detection Close Reason for Incident Closure” option if you want to set the detection close reason in Vectra when the incident is closed in XSOAR. Default value is ‘Benign’. This option is only used when the “Close Detection on Incident Closure” option is checked and the mirroring direction is set to ‘Outgoing’ or ‘Incoming And Outgoing’.
  15. Select SSL certificate validation and Proxy if required.

Notes for mirroring:

  • This feature is compliant with XSOAR version 6.0 and above.
  • When mirroring incidents, you can make changes in Vectra that will be reflected in Cortex XSOAR, or vice versa.
  • Any tags removed from the Vectra entity will not be removed in the XSOAR incident, as XSOAR doesn’t allow the removal of the tags field via the backend. However, tags removed from the XSOAR incident UI will be removed from the Vectra entity.
  • New notes from the XSOAR incident will be created as notes in the Vectra Detection. Updates to existing notes in the XSOAR incident will not be reflected in the Vectra Detection.
  • New notes from the Vectra Detection will be created as notes in the XSOAR incident. Updates to existing notes in the Vectra Detection will create new notes in the XSOAR incident.
  • If the Detection Status is updated in the Vectra Detection, it will be reflected in the XSOAR incident, or vice versa.
  • If you want to reopen a detection in Vectra when the incident is reopened in XSOAR, check the “Open Detection on Incident Reopen” option. This option is only used when the mirroring direction is set to ‘Outgoing’ or ‘Incoming And Outgoing’.
  • Set the “Detection Status for Incident Reopen” option to set the detection status in Vectra when the incident is reopened in XSOAR. Default value is ‘Escalated’. This option is only used when the “Open Detection on Incident Reopen” option is checked and the mirroring direction is set to ‘Outgoing’ or ‘Incoming And Outgoing’.
  • If you want to close a detection in Vectra when the incident is closed in XSOAR, check the “Close Detection on Incident Closure” option. This option is only used when the mirroring direction is set to ‘Outgoing’ or ‘Incoming And Outgoing’.
  • Set the “Detection Close Reason for Incident Closure” option to set the detection close reason in Vectra when the incident is closed in XSOAR. Default value is ‘Benign’. This option is only used when the “Close Detection on Incident Closure” option is checked and the mirroring direction is set to ‘Outgoing’ or ‘Incoming And Outgoing’.
  • The mirroring settings apply only for incidents that are fetched after applying the settings.
  • The mirroring is strictly tied to Incident type “Vectra RUX Events Detection” & Incoming mapper “Vectra RUX - Incoming Mapper” If you want to change or use your custom incident type/mapper then make sure changes related to these are present.
  • If you want to use the mirror mechanism and you’re using custom mappers, then the incoming mapper must contain the following fields: dbotMirrorDirection, dbotMirrorId, dbotMirrorInstance, and dbotMirrorTags.
  • To use a custom mapper, you must first duplicate the mapper and update the fields in the copy of the mapper. (Refer to the “Create a custom mapper consisting of the default Vectra RUX mapper” section for more information.)
  • Following new fields are introduced in the response of the incident to enable the mirroring:
    • mirror_direction: This field determines the mirroring direction for the incident. It is a required field for XSOAR to enable mirroring support.
    • mirror_tags: This field determines what would be the tag needed to mirror the XSOAR entry out to Vectra RUX. It is a required field for XSOAR to enable mirroring support.
    • mirror_instance: This field determines from which instance the XSOAR incident was created. It is a required field for XSOAR to enable mirroring support.

Expire Inactive Detections

  • Use the Expire Inactive Detections - Vectra RUX playbook to expire inactive detections that are fetched in XSOAR.
  • You can also schedule a job with the Expire Inactive Detections - Vectra RUX playbook in Cortex XSOAR to expire inactive detections periodically. Refer to Cortex XSOAR documentation for more information. To create a job with a 24-hour recurring schedule, follow these steps:
    1. In Cortex XSOAR, navigate to Jobs (via the top menu or sidebar).
    2. Click New Job.
    3. Select Time triggered and enable Recurring.
    4. Set the schedule to Every 24 hours (or configure a specific daily time using a cron expression such as 0 0 * * *).
    5. Set the Name for the job (e.g., Expire Inactive Detections - Daily).
    6. Under Playbook, select Expire Inactive Detections - Vectra RUX.
    7. Click Create new job to activate the job.

Commands

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

vectra-detections-mark-asclosed


Mark detections as closed with provided detection IDs in the argument.

Base Command

vectra-detections-mark-asclosed

Input

Argument Name Description Required
detection_ids Provide a list of detection IDs separated by commas or a single detection ID. Required
close_reason Provide the close reason. Possible values are: benign, remediated. Required

Context Output

There is no context output for this command.

Command example

!vectra-detections-mark-asclosed detection_ids=123,345 close_reason=remediated

Human Readable Output

The provided detection IDs have been successfully closed as remediated

vectra-user-list


Returns a list of users.

Base Command

vectra-user-list

Input

Argument Name Description Required
email Filter by email. Optional
role Filter users with the specified role. Use the role standardized name. Possible values are: Admin, Auditor, Global Analyst, Read-Only, Restricted Admin, Security Analyst, Setting Admin, Super Admin. Optional
last_login_timestamp Return only the users which have a last login timestamp equal to or after the given timestamp.

Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ.

For example: 01 May 2023, 01 Mar 2021 04:45:33, 2022-04-17T14:05:44Z.
Optional

Context Output

Path Type Description
Vectra.User.id Number The ID of the User.
Vectra.User.user_id Number The ID of the User.
Vectra.User.name String Username of the user.
Vectra.User.email String The email associated with the user.
Vectra.User.role String The role associated with the user.
Vectra.User.last_login_timestamp String Last login timestamp in UTC format of the user.
Vectra.User.last_login String Last login timestamp of the user.

Command example


#### Context Example

```json
{
  "Vectra": {
    "User": [
      {
        "id": 59,
        "user_id": 59,
        "username": "user.name1",
        "email": "",
        "role": "Security Analyst",
        "last_login_timestamp": "2023-08-22T09:24:44Z",
        "last_login": "2023-08-22T09:24:44Z"
      },
      {
        "id": 32,
        "user_id": 32,
        "username": "user.name2",
        "email": "",
        "role": "Super Admin",
        "last_login_timestamp": "2023-07-02T18:41:19Z",
        "last_login": "2023-07-02T18:41:19Z"
      },
      {
        "id": 23,
        "user_id": 23,
        "username": "vectra_mdr",
        "email": "",
        "role": "Vectra MDR"
      }
    ]
  }
}

Human Readable Output

Users Table

User ID User Name Role Last Login Timestamp
59 user.name1 Security Analyst 2023-08-22T09:24:44Z
32 user.name2 Super Admin 2023-07-02T18:41:19Z
23 vectra_mdr Vectra MDR  

vectra-entity-list


Returns a list of entities.

Base Command

vectra-entity-list

Input

Argument Name Description Required
prioritized Fetch only entities whose priority score is above the configured priority threshold will be included in the response. Possible values are: true, false. Optional
entity_type Specify the type of the entity. Possible values are: account, host. Optional
name Filter by matching entity name. Optional
tags Filter by a tag or a comma-separated list of tags. Optional
state Filter on entity activation state. Possible values are: active, inactive. Optional
ordering Orders records by last timestamp or urgency score. Default sorting is by urgency score in descending order. Use the minus symbol (-) to sort scores in descending order. Multiple ordering fields can be specified with a comma-separated list (e.g., ordering=urgency_score,-name). Optional
last_detection_timestamp Return only the entities which have a last detection timestamp equal to or after the given timestamp.

Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ.

For example: 01 May 2023, 01 Mar 2021 04:45:33, 2022-04-17T14:05:44Z.
Optional
page Enables the caller to specify a particular page of results. Default is 1. Optional
page_size Specify the desired page size for the request. Maximum is 5000. Default is 50. Optional
last_modified_timestamp Return only the entities which have a last modified timestamp equal to or after the given timestamp.

Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ.

For example: 01 May 2023, 01 Mar 2021 04:45:33, 2022-04-17T14:05:44Z.
Optional

Context Output

Path Type Description
Vectra.Entity.id Number ID of the entity.
Vectra.Entity.name String Name of the entity.
Vectra.Entity.breadth_contrib Number Breadth contribution of the entity.
Vectra.Entity.importance Number Entity importance.
Vectra.Entity.type String Type of the entity.
Vectra.Entity.is_prioritized Boolean Entity is prioritized or not.
Vectra.Entity.severity String Severity of the entity.
Vectra.Entity.urgency_score Number Urgency score of the entity.
Vectra.Entity.velocity_contrib Number Velocity contribution of the entity.
Vectra.Entity.detection_set String Set of detections related to entity.
Vectra.Entity.last_detection_timestamp Date Time of the last detection activity related to entity.
Vectra.Entity.notes.id String Notes of the entity.
Vectra.Entity.notes.dateCreated String Created date of the Note.
Vectra.Entity.notes.dateModified String Modified date of the Note.
Vectra.Entity.notes.createdBy String Created user of the Note.
Vectra.Entity.notes.ModifiedBy String Modified user of the Note.
Vectra.Entity.notes.note String Note of the entity.
Vectra.Entity.attack_rating Number Attack Ratting of the entity.
Vectra.Entity.privilege_level String Privilege Level of the entity.
Vectra.Entity.privilege_category String Privilege Category of the entity.
Vectra.Entity.attack_profile String Attack Profile of the entity.
Vectra.Entity.sensors Unknown Sensors of the entity.
Vectra.Entity.state String State of the entity.
Vectra.Entity.tags Unknown Tags of the entity.
Vectra.Entity.url String Url link of the entity.
Vectra.Entity.host_type Unknown Host type of the entity.
Vectra.Entity.account_type String Account type of the entity.

Command example

!vectra-entity-list entity_type=account page=1 page_size=4 tags=test,test1 prioritized=true state=active

Context Example

{
  [
    {
      "id": 334,
      "name": "account_name",
      "breadth_contrib": 2,
      "entity_importance": 1,
      "importance": 2,
      "entity_type": "account",
      "type": "account",
      "is_prioritized": true,
      "severity": "Critical",
      "urgency_score": 100,
      "velocity_contrib": 2,
      "detection_set": [
        "http://server_url.com/api/v3.3/detections/1933",
        "http://server_url.com/api/v3.3/detections/1934"
      ],
      "last_detection_timestamp": "2023-05-15T09:39:24Z",
      "last_modified_timestamp": "2023-07-27T08:56:09Z",
      "notes": [],
      "attack_rating": 10,
      "attack_profile": "AWS Threat Actor",
      "sensors": [
        "test"
      ],
      "state": "active",
      "tags": [
        "test"
      ],
      "url": "http://server_url.com/api/v3.3/accounts/334",
      "account_type": [
        "o365"
      ]
    },
    {
      "id": 335,
      "name": "account_name_1",
      "breadth_contrib": 2,
      "entity_importance": 1,
      "importance": 2,
      "entity_type": "account",
      "type": "account",
      "is_prioritized": true,
      "severity": "Critical",
      "urgency_score": 80,
      "velocity_contrib": 2,
      "detection_set": [
        "http://server_url.com/api/v3.3/detections/1935",
        "http://server_url.com/api/v3.3/detections/1937"
      ],
      "last_detection_timestamp": "2023-05-15T09:41:24Z",
      "last_modified_timestamp": "2023-07-27T08:56:09Z",
      "notes": [],
      "attack_rating": 6,
      "attack_profile": "attack1",
      "sensors": [],
      "state": "active",
      "tags": [
        "test",
        "test1"
      ],
      "url": "http://server_url.com/api/v3.3/accounts/335",
      "account_type": [
        "o365"
      ]
    },
    {
      "id": 337,
      "name": "account_name_2",
      "breadth_contrib": 2,
      "entity_importance": 1,
      "importance": 1,
      "entity_type": "account",
      "type": "account",
      "is_prioritized": true,
      "severity": "Critical",
      "urgency_score": 40,
      "velocity_contrib": 2,
      "detection_set": [
        "http://server_url.com/api/v3.3/detections/1835",
        "http://server_url.com/api/v3.3/detections/1837"
      ],
      "last_detection_timestamp": "2023-05-15T09:40:24Z",
      "last_modified_timestamp": "2023-07-27T08:56:09Z",
      "notes": [],
      "attack_rating": 9,
      "attack_profile": "attack2",
      "sensors": [],
      "state": "active",
      "tags": [
        "test1"
      ],
      "url": "http://server_url.com/api/v3.3/accounts/337",
      "account_type": [
        "aws"
      ]
    },
    {
      "id": 339,
      "name": "account_name_3",
      "breadth_contrib": 2,
      "entity_importance": 1,
      "importance": 2,
      "entity_type": "account",
      "type": "account",
      "is_prioritized": true,
      "severity": "Critical",
      "urgency_score": 21,
      "velocity_contrib": 2,
      "detection_set": [
        "http://server_url.com/api/v3.3/detections/1735",
        "http://server_url.com/api/v3.3/detections/1737"
      ],
      "last_detection_timestamp": "2023-05-15T09:44:24Z",
      "last_modified_timestamp": "2023-07-27T08:56:09Z",
      "notes": [],
      "attack_rating": 5,
      "attack_profile": "attack3",
      "sensors": [],
      "state": "active",
      "tags": [
        "test"
      ],
      "url": "http://server_url.com/api/v3.3/accounts/339",
      "account_type": [
        "o365"
      ]
    }
  ]
}

Human Readable Output

Entities Table (Showing Page 1 out of 1)

ID Name Entity Type Urgency Score Entity Importance Last Detection Timestamp Last Modified Timestamp Detections IDs Prioritize State Tags
334 account_name account 100 High 2023-05-15T09:39:24Z 2023-07-18T09:44:24Z 1933, 1934 true active test
335 account_name_1 account 80 High 2023-05-15T09:41:24Z 2023-07-17T09:44:24Z 1935, 1937 true active test, test1
337 account_name_2 account 40 Medium 2023-05-15T09:40:24Z 2023-07-16T09:44:24Z 1835, 1837 true active test1
339 account_name_3 account 21 High 2023-05-15T09:44:24Z 2023-07-15T09:44:24Z 1735, 1737 true active test

vectra-entity-describe


Describes an entity by ID.

Base Command

vectra-entity-describe

Input

Argument Name Description Required
entity_id Specify the id of the entity. Required
entity_type Specify the type of the entity. Possible values are: host, account. Required

Context Output

Path Type Description
Vectra.Entity.id Number ID of the entity.
Vectra.Entity.name String Name of the entity.
Vectra.Entity.breadth_contrib Number Breadth contribution of the entity.
Vectra.Entity.importance Number Entity importance.
Vectra.Entity.type String Type of the entity.
Vectra.Entity.is_prioritized Boolean Entity is prioritized or not.
Vectra.Entity.severity String Severity of the entity.
Vectra.Entity.urgency_score Number Urgency score of the entity.
Vectra.Entity.velocity_contrib Number Velocity contribution of the entity.
Vectra.Entity.detection_set String Set of detections related to the entity.
Vectra.Entity.last_detection_timestamp Date Time of the last detection activity related to the entity.
Vectra.Entity.last_modified_timestamp Date Time of the last modification activity related to the entity.
Vectra.Entity.notes.id String Notes of the entity.
Vectra.Entity.notes.dateCreated String Created date of the Note.
Vectra.Entity.notes.dateModified String Modified date of the Note.
Vectra.Entity.notes.createdBy String Created user of the Note.
Vectra.Entity.notes.ModifiedBy String Modified user of the Note.
Vectra.Entity.notes.note String Note of the entity.
Vectra.Entity.attack_rating Number Attack Ratting of the entity.
Vectra.Entity.privilege_level String Privilege Level of the entity.
Vectra.Entity.privilege_category String Privilege Category of the entity.
Vectra.Entity.attack_profile String Attack Profile of the entity.
Vectra.Entity.sensors Unknown Sensors of the entity.
Vectra.Entity.state String State of the entity.
Vectra.Entity.tags Unknown Tags of the entity.
Vectra.Entity.url String Url link of the entity.
Vectra.Entity.host_type Unknown Host type of the entity.
Vectra.Entity.account_type Unknown Account type of the entity.

Command example

!vectra-entity-describe entity_type=account entity_id=334

Context Example

{
    "id": 334,
    "name": "account_name",
    "breadth_contrib": 2,
    "entity_importance": 1,
    "importance": 2,
    "entity_type": "account",
    "type": "account",
    "is_prioritized": true,
    "severity": "Critical",
    "urgency_score": 100,
    "velocity_contrib": 2,
    "detection_set": [
      "http://server_url.com/api/v3.3/detections/1933",
      "http://server_url.com/api/v3.3/detections/1934"
    ],
    "last_detection_timestamp": "2023-05-15T09:39:24Z",
    "last_modified_timestamp": "2023-07-28T05:25:47Z",
    "notes": [],
    "attack_rating": 10,
    "attack_profile": "test_attack",
    "sensors": [
      "test"
    ],
    "state": "active",
    "tags": [
      "test"
    ],
    "url": "http://server_url.com/api/v3.3/accounts/334",
    "account_type": [
      "o365"
    ]
  }
}

Human Readable Output

Entity detail

Entity ID: 334

Name Entity Type Urgency Score Entity Importance Last Detection Timestamp Last Modified Timestamp Detections IDs Prioritize State Tags
account_name account 100 High 2023-05-15T09:39:24Z 2023-07-28T05:25:47Z 1933, 1934 true active test

vectra-entity-detection-list


Returns a list of detections for a specified entity.

Base Command

vectra-entity-detection-list

Input

Argument Name Description Required
entity_id Specify the id of the entity. Required
entity_type Specify the type of the entity. Possible values are: account, host. Required
page Enables the caller to specify a particular page of results. Default is 1. Optional
page_size Specify the desired page size for the request. Maximum is 5000. Default is 50. Optional
detection_category The category of the detection. Possible values are: Command & Control, Botnet, Reconnaissance, Lateral Movement, Exfiltration, Info. Optional
detection_type Filter by detection type. Optional
last_timestamp Return only the detections which have a last timestamp equal to or after the given timestamp.
Formats: YYYY-MM-ddTHH:mm:ssZ, YYYY-MM-dd, N days, N hours.
Example: 2023-04-25T00:00:00Z, 2023-04-25, 2 days, 5 hours, 01 Mar 2023, 01 Feb 2023 04:45:33, 15 Jun.
Optional
detection_name Filter by detection name. Optional
state Filter by state. Default is active. Optional
tags Filter by a tag or a comma-separated list of tags. Optional

Context Output

Path Type Description
Vectra.Entity.Detections.id Number Entity detection ID.
Vectra.Entity.Detections.assigned_date Unknown Date assigned to the detection.
Vectra.Entity.Detections.assigned_to Unknown User or entity assigned to the detection.
Vectra.Entity.Detections.category String Category of the detection.
Vectra.Entity.Detections.certainty Number Certainty level of the detection.
Vectra.Entity.Detections.c_score Number Confidence score of the detection.
Vectra.Entity.Detections.description String Description of the detection.
Vectra.Entity.Detections.detection String Detection information.
Vectra.Entity.Detections.detection_category String Category of the detection.
Vectra.Entity.Detections.detection_type String Type of the detection.
Vectra.Entity.Detections.grouped_details.external_target.ip String IP address of the external target in the detection group.
Vectra.Entity.Detections.grouped_details.external_target.name String Name of the external target in the detection group.
Vectra.Entity.Detections.grouped_details.num_sessions Number Number of sessions in the detection group.
Vectra.Entity.Detections.grouped_details.bytes_received Number Total bytes received in the detection group.
Vectra.Entity.Detections.grouped_details.bytes_sent Number Total bytes sent in the detection group.
Vectra.Entity.Detections.grouped_details.ja3_hashes String JA3 hashes in the detection group.
Vectra.Entity.Detections.grouped_details.ja3s_hashes String JA3S hashes in the detection group.
Vectra.Entity.Detections.grouped_details.sessions.tunnel_type String Tunnel type used in the sessions of the detection group.
Vectra.Entity.Detections.grouped_details.sessions.protocol String Protocol used in the sessions of the detection group.
Vectra.Entity.Detections.grouped_details.sessions.app_protocol String Application protocol used in the sessions of the detection group.
Vectra.Entity.Detections.grouped_details.sessions.dst_port Number Destination port in the sessions of the detection group.
Vectra.Entity.Detections.grouped_details.sessions.dst_ip String Destination IP address in the sessions of the detection group.
Vectra.Entity.Detections.grouped_details.sessions.bytes_received Number Total bytes received in the sessions of the detection group.
Vectra.Entity.Detections.grouped_details.sessions.bytes_sent Number Total bytes sent in the sessions of the detection group.
Vectra.Entity.Detections.grouped_details.sessions.first_timestamp Date First timestamp of the sessions in the detection group.
Vectra.Entity.Detections.grouped_details.sessions.last_timestamp Date Last timestamp of the sessions in the detection group.
Vectra.Entity.Detections.grouped_details.sessions.dst_geo Unknown Geolocation of the destination IP in the sessions of the detection group.
Vectra.Entity.Detections.grouped_details.sessions.dst_geo_lat Unknown Latitude of the destination IP in the sessions of the detection group.
Vectra.Entity.Detections.grouped_details.sessions.dst_geo_lon Unknown Longitude of the destination IP in the sessions of the detection group.
Vectra.Entity.Detections.grouped_details.first_timestamp Date First timestamp of the detection group.
Vectra.Entity.Detections.grouped_details.last_timestamp Date Last timestamp of the detection group.
Vectra.Entity.Detections.grouped_details.dst_ips String Destination IP addresses in the detection group.
Vectra.Entity.Detections.grouped_details.dst_ports Number Destination ports in the detection group.
Vectra.Entity.Detections.grouped_details.target_domains String Target domains in the detection group.
Vectra.Entity.Detections.is_targeting_key_asset Boolean Indicates if the detection is targeting a key asset.
Vectra.Entity.Detections.last_timestamp Date Last timestamp of the detection.
Vectra.Entity.Detections.note Unknown Note associated with the detection.
Vectra.Entity.Detections.note_modified_by Unknown User or entity who last modified the note.
Vectra.Entity.Detections.note_modified_timestamp Unknown Timestamp when the note was last modified.
Vectra.Entity.Detections.notes Unknown Additional notes related to the detection.
Vectra.Entity.Detections.sensor_name String Name of the sensor associated with the detection.
Vectra.Entity.Detections.src_account.id Number ID of the source account associated with the detection.
Vectra.Entity.Detections.src_account.name String Name of the source account associated with the detection.
Vectra.Entity.Detections.src_account.url String URL of the source account associated with the detection.
Vectra.Entity.Detections.src_account.threat Number Threat level of the source account associated with the detection.
Vectra.Entity.Detections.src_account.certainty Number Certainty level of the source account associated with the detection.
Vectra.Entity.Detections.src_account.privilege_level Number Privilege level of the source account associated with the detection.
Vectra.Entity.Detections.src_account.privilege_category String Privilege category of the source account associated with the detection.
Vectra.Entity.Detections.src_host.id Number ID of the source host in the detection.
Vectra.Entity.Detections.src_host.ip String IP address of the source host in the detection.
Vectra.Entity.Detections.src_host.name String Name of the source host in the detection.
Vectra.Entity.Detections.src_host.url String URL associated with the source host in the detection.
Vectra.Entity.Detections.src_host.is_key_asset Boolean Indicates if the source host is a key asset.
Vectra.Entity.Detections.src_host.groups Unknown Groups associated with the source host in the detection.
Vectra.Entity.Detections.src_host.threat Number Threat level associated with the source host in the detection.
Vectra.Entity.Detections.src_host.certainty Number Certainty level associated with the source host in the detection.
Vectra.Entity.Detections.src_ip String Source IP address in the detection.
Vectra.Entity.Detections.state String State of the detection.
Vectra.Entity.Detections.summary.bytes_received Number Total bytes received in the detection summary.
Vectra.Entity.Detections.summary.bytes_sent Number Total bytes sent in the detection summary.
Vectra.Entity.Detections.summary.cnc_server String CNC server associated with the detection summary.
Vectra.Entity.Detections.summary.num_events Number Total number of events related to the detection.
Vectra.Entity.Detections.summary.probable_owner Unknown Probable owner of the detection summary.
Vectra.Entity.Detections.summary.sessions Number Total sessions in the detection summary.
Vectra.Entity.Detections.tags Unknown Tags associated with the detection.
Vectra.Entity.Detections.threat Number Threat level of the detection.
Vectra.Entity.Detections.t_score Number T-score of the detection.
Vectra.Entity.Detections.type String Type of the detection.
Vectra.Entity.Detections.url String URL associated with the detection.

Command example

!vectra-entity-detection-list entity_id=1

Context Example

{
  [
    {
      "id": 132,
      "category": "exfiltration",
      "certainty": 70,
      "c_score": 70,
      "description": "",
      "detection": "Data Smuggler",
      "detection_category": "exfiltration",
      "detection_type": "smuggler",
      "grouped_details": [
        {
          "event_id": "ec2162c7-e526-4446-a549-71558743a1d7",
          "event_name": "UpdateAssumeRolePolicy",
          "aws_account_id": "aws_account_id",
          "src_external_host": {
            "ip": "0.0.0.0"
          },
          "aws_region": "us-east-1",
          "access_key_id": [
            "123456"
          ],
          "identity_type": "Federated Account",
          "assumed_role": "AWSReservedSSO_AdministratorAccess_a670eb90f07e2960",
          "request_parameters": [
            "{\"roleName\": \"stratus-red-team-backdoor-r-role\", \"policyDocument\": \"{\\\"Version\\\": \\\"2012-10-17\\\", \\\"Statement\\\": {\\\"Effect\\\": \\\"Allow\\\", \\\"Principal\\\": {\\\"AWS\\\": \\\"arn:aws:iam::123456789012:root\\\"}, \\\"Action\\\": \\\"sts:AssumeRole\\\"}}\"}"
          ],
          "response_elements": [],
          "role_sequence": [
            "account_id",
            "AWSReservedSSO_AdministratorAccess_a670eb90f07e2960"
          ],
          "user_agent": [
            "stratus-red-team_06e15b96-ee6b-482c-aff4-4f2f4a46a67c"
          ],
          "last_timestamp": "2023-06-06T17:01:04Z"
        },
        {
          "event_id": "89a098eb-1198-4e2a-9fa4-ef568ae39403",
          "event_name": "UpdateAssumeRolePolicy",
          "aws_account_id": "884414556547",
          "src_external_host": {
            "ip": "0.0.0.0"
          },
          "aws_region": "us-east-1",
          "access_key_id": [
            "123456"
          ],
          "identity_type": "Federated Account",
          "assumed_role": "AWSReservedSSO_AdministratorAccess_a670eb90f07e2960",
          "request_parameters": [
            "{\"policyDocument\": \"{\\\"Version\\\": \\\"2012-10-17\\\", \\\"Statement\\\": {\\\"Effect\\\": \\\"Allow\\\", \\\"Principal\\\": {\\\"AWS\\\": \\\"arn:aws:iam::123456789012:root\\\"}, \\\"Action\\\": \\\"sts:AssumeRole\\\"}}\", \"roleName\": \"stratus-red-team-backdoor-r-role\"}"
          ],
          "response_elements": [],
          "role_sequence": [
            "account_name",
            "AWSReservedSSO_AdministratorAccess_a670eb90f07e2960"
          ],
          "user_agent": [
            "stratus-red-team_a24eac3a-4fee-46ce-bc37-b4e675343fc9"
          ],
          "last_timestamp": "2023-06-06T15:40:43Z"
        }
      ],
      "is_targeting_key_asset": false,
      "last_timestamp": "2023-06-06T17:01:04Z",
      "notes": [],
      "sensor_name": "mafosb50",
      "src_account": {
        "id": 21,
        "name": "account_name",
        "url": "http://server_url.com/api/v3.3/accounts/21",
        "threat": 76,
        "certainty": 35
      },
      "src_ip": "0.0.0.0",
      "state": "active",
      "tags": [],
      "threat": 80,
      "t_score": 80,
      "type": "account",
      "url": "http://server_url.com/api/v3.3/detections/132"
    },
    {
      "id": 135,
      "category": "lateral_movement",
      "certainty": 50,
      "c_score": 50,
      "description": "",
      "detection": "AWS Suspect Admin Privilege Granting",
      "detection_category": "lateral_movement",
      "detection_type": "aws_admin_privilege_granted",
      "grouped_details": [
        {
          "event_id": "85d88db5-cf2d-4b6e-9411-d3119d9920e0",
          "event_name": "AttachRolePolicy",
          "aws_account_id": "884414556547",
          "src_external_host": {
            "ip": "0.0.0.0"
          },
          "aws_region": "us-east-1",
          "access_key_id": [
            "123456"
          ],
          "identity_type": "Federated Account",
          "assumed_role": "AWSReservedSSO_AdministratorAccess_a670eb90f07e2960",
          "request_parameters": [
            "{\"roleName\":\"stratus-red-team-backdoor-r-role\",\"policyArn\":\"arn:aws:iam::aws:policy/AdministratorAccess\"}"
          ],
          "response_elements": [],
          "role_sequence": [
            "account_name",
            "AWSReservedSSO_AdministratorAccess_a670eb90f07e2960"
          ],
          "user_agent": [
            "APN/1.0 HashiCorp/1.0 Terraform/1.1.2 (+https://www.terraform.io) terraform-provider-aws/3.76.1 (+https://registry.terraform.io/providers/hashicorp/aws) aws-sdk-go/1.44.157 (go1.19.3; linux; amd64) stratus-red-team_5077134d-32ea-4403-996b-de30d7f278d7 HashiCorp-terraform-exec/0.17.3"
          ],
          "last_timestamp": "2023-06-06T17:00:46Z"
        },
        {
          "event_id": "ca157e7c-9a53-4012-9288-e6ac1c488fbc",
          "event_name": "AttachRolePolicy",
          "aws_account_id": "884414556547",
          "src_external_host": {
            "ip": "0.0.0.0"
          },
          "aws_region": "us-east-1",
          "access_key_id": [
            "123456"
          ],
          "identity_type": "Federated Account",
          "assumed_role": "AWSReservedSSO_AdministratorAccess_a670eb90f07e2960",
          "request_parameters": [
            "{\"roleName\":\"stratus-red-team-backdoor-r-role\",\"policyArn\":\"arn:aws:iam::aws:policy/AdministratorAccess\"}"
          ],
          "response_elements": [],
          "role_sequence": [
            "account_name",
            "AWSReservedSSO_AdministratorAccess_a670eb90f07e2960"
          ],
          "user_agent": [
            "APN/1.0 HashiCorp/1.0 Terraform/1.1.2 (+https://www.terraform.io) terraform-provider-aws/3.76.1 (+https://registry.terraform.io/providers/hashicorp/aws) aws-sdk-go/1.44.157 (go1.19.3; linux; amd64) stratus-red-team_01be8427-d1b5-4c18-8edb-0301c8e66c8e HashiCorp-terraform-exec/0.17.3"
          ],
          "last_timestamp": "2023-06-06T15:40:07Z"
        }
      ],
      "is_targeting_key_asset": false,
      "last_timestamp": "2023-06-06T17:00:46Z",
      "notes": [],
      "sensor_name": "mafosb50",
      "src_account": {
        "id": 21,
        "name": "account_name",
        "url": "http://server_url.com/api/v3.3/accounts/21",
        "threat": 76,
        "certainty": 35
      },
      "src_ip": "0.0.0.0",
      "state": "fixed",
      "summary": {
      },
      "tags": [],
      "threat": 60,
      "t_score": 60,
      "type": "account",
      "url": "http://server_url.com/api/v3.3/detections/135"
    },
    {
      "id": 140,
      "category": "reconnaissance",
      "certainty": 40,
      "c_score": 40,
      "description": "",
      "detection": "RPC Targeted Recon",
      "detection_category": "reconnaissance",
      "detection_type": "rpc_recon_1to1",
      "grouped_details": [
        {
          "event_id": "cf9f469b-0a8e-47c6-85eb-5a0486292e58",
          "event_name": "ModifySnapshotAttribute",
          "aws_account_id": "884414556547",
          "src_external_host": {
            "ip": "0.0.0.0"
          },
          "aws_region": "us-west-2",
          "access_key_id": [
            "123456"
          ],
          "identity_type": "Federated Account",
          "assumed_role": "AWSReservedSSO_AdministratorAccess_a670eb90f07e2960",
          "request_parameters": [
            "{\"snapshotId\":\"snap-0f7d022a2f4f67e08\",\"createVolumePermission\":{\"add\":{\"items\":[{\"userId\":\"012345678912\"}]}},\"attributeType\":\"CREATE_VOLUME_PERMISSION\"}"
          ],
          "response_elements": [
            "{\"requestId\":\"350c1eb8-b696-4d94-88d4-a764a0eed08b\",\"_return\":true}"
          ],
          "role_sequence": [
            "account_name",
            "AWSReservedSSO_AdministratorAccess_a670eb90f07e2960"
          ],
          "user_agent": [
            "stratus-red-team_a4dd596b-7a8d-4e77-a74d-13f19adf4403"
          ],
          "last_timestamp": "2023-06-06T15:46:28Z"
        }
      ],
      "is_targeting_key_asset": false,
      "last_timestamp": "2023-06-06T15:46:28Z",
      "notes": [],
      "sensor_name": "mafosb50",
      "src_account": {
        "id": 21,
        "name": "account_name",
        "url": "http://server_url.com/api/v3.3/accounts/21",
        "threat": 76,
        "certainty": 35
      },
      "src_ip": "0.0.0.0",
      "state": "fixed",
      "summary": {
      },
      "tags": [],
      "threat": 60,
      "t_score": 60,
      "type": "account",
      "url": "http://server_url.com/api/v3.3/detections/140"
    }
  ]
}

Human Readable Output

Detections Table (Showing Page 1 out of 1)

ID Detection Name Detection Type Category Account Name Src IP Threat Score Certainty Score Number Of Events State Last Timestamp
132 Data Smuggler smuggler exfiltration account_name 0.0.0.0 80 70 0 active 2023-06-06T17:01:04Z
135 AWS Suspect Admin Privilege Granting aws_admin_privilege_granted lateral_movement account_name 0.0.0.0 60 50 0 fixed 2023-06-06T17:00:46Z
140 RPC Targeted Recon rpc_recon_1to1 reconnaissance account_name 0.0.0.0 60 40 0 fixed 2023-06-06T15:46:28Z

vectra-detection-describe


Returns a list of detections for the specified detection ID(s).

Base Command

vectra-detection-describe

Input

Argument Name Description Required
detection_ids Specify the ID(s) of the detections. Required
page Enables the caller to specify a particular page of results. Default is 1. Optional
page_size Specify the desired page size for the request. Maximum is 5000. Default is 50. Optional

Context Output

Path Type Description
Vectra.Entity.Detections.id Number Entity detection ID.
Vectra.Entity.Detections.assigned_date Unknown Date assigned to the detection.
Vectra.Entity.Detections.assigned_to Unknown User or entity assigned to the detection.
Vectra.Entity.Detections.category String Category of the detection.
Vectra.Entity.Detections.certainty Number Certainty level of the detection.
Vectra.Entity.Detections.c_score Number Confidence score of the detection.
Vectra.Entity.Detections.description String Description of the detection.
Vectra.Entity.Detections.detection String Detection information.
Vectra.Entity.Detections.detection_category String Category of the detection.
Vectra.Entity.Detections.detection_type String Type of the detection.
Vectra.Entity.Detections.grouped_details.external_target.ip String IP address of the external target in the detection group.
Vectra.Entity.Detections.grouped_details.external_target.name String Name of the external target in the detection group.
Vectra.Entity.Detections.grouped_details.num_sessions Number Number of sessions in the detection group.
Vectra.Entity.Detections.grouped_details.bytes_received Number Total bytes received in the detection group.
Vectra.Entity.Detections.grouped_details.bytes_sent Number Total bytes sent in the detection group.
Vectra.Entity.Detections.grouped_details.ja3_hashes String JA3 hashes in the detection group.
Vectra.Entity.Detections.grouped_details.ja3s_hashes String JA3S hashes in the detection group.
Vectra.Entity.Detections.grouped_details.sessions.tunnel_type String Tunnel type used in the sessions of the detection group.
Vectra.Entity.Detections.grouped_details.sessions.protocol String Protocol used in the sessions of the detection group.
Vectra.Entity.Detections.grouped_details.sessions.app_protocol String Application protocol used in the sessions of the detection group.
Vectra.Entity.Detections.grouped_details.sessions.dst_port Number Destination port in the sessions of the detection group.
Vectra.Entity.Detections.grouped_details.sessions.dst_ip String Destination IP address in the sessions of the detection group.
Vectra.Entity.Detections.grouped_details.sessions.bytes_received Number Total bytes received in the sessions of the detection group.
Vectra.Entity.Detections.grouped_details.sessions.bytes_sent Number Total bytes sent in the sessions of the detection group.
Vectra.Entity.Detections.grouped_details.sessions.first_timestamp Date First timestamp of the sessions in the detection group.
Vectra.Entity.Detections.grouped_details.sessions.last_timestamp Date Last timestamp of the sessions in the detection group.
Vectra.Entity.Detections.grouped_details.sessions.dst_geo Unknown Geolocation of the destination IP in the sessions of the detection group.
Vectra.Entity.Detections.grouped_details.sessions.dst_geo_lat Unknown Latitude of the destination IP in the sessions of the detection group.
Vectra.Entity.Detections.grouped_details.sessions.dst_geo_lon Unknown Longitude of the destination IP in the sessions of the detection group.
Vectra.Entity.Detections.grouped_details.first_timestamp Date First timestamp of the detection group.
Vectra.Entity.Detections.grouped_details.last_timestamp Date Last timestamp of the detection group.
Vectra.Entity.Detections.grouped_details.dst_ips String Destination IP addresses in the detection group.
Vectra.Entity.Detections.grouped_details.dst_ports Number Destination ports in the detection group.
Vectra.Entity.Detections.grouped_details.target_domains String Target domains in the detection group.
Vectra.Entity.Detections.is_targeting_key_asset Boolean Indicates if the detection is targeting a key asset.
Vectra.Entity.Detections.last_timestamp Date Last timestamp of the detection.
Vectra.Entity.Detections.note Unknown Note associated with the detection.
Vectra.Entity.Detections.note_modified_by Unknown User or entity who last modified the note.
Vectra.Entity.Detections.note_modified_timestamp Unknown Timestamp when the note was last modified.
Vectra.Entity.Detections.notes Unknown Additional notes related to the detection.
Vectra.Entity.Detections.sensor_name String Name of the sensor associated with the detection.
Vectra.Entity.Detections.src_account.id Number ID of the source account associated with the detection.
Vectra.Entity.Detections.src_account.name String Name of the source account associated with the detection.
Vectra.Entity.Detections.src_account.url String URL of the source account associated with the detection.
Vectra.Entity.Detections.src_account.threat Number Threat level of the source account associated with the detection.
Vectra.Entity.Detections.src_account.certainty Number Certainty level of the source account associated with the detection.
Vectra.Entity.Detections.src_account.privilege_level Number Privilege level of the source account associated with the detection.
Vectra.Entity.Detections.src_account.privilege_category String Privilege category of the source account associated with the detection.
Vectra.Entity.Detections.src_host.id Number ID of the source host in the detection.
Vectra.Entity.Detections.src_host.ip String IP address of the source host in the detection.
Vectra.Entity.Detections.src_host.name String Name of the source host in the detection.
Vectra.Entity.Detections.src_host.url String URL associated with the source host in the detection.
Vectra.Entity.Detections.src_host.is_key_asset Boolean Indicates if the source host is a key asset.
Vectra.Entity.Detections.src_host.groups Unknown Groups associated with the source host in the detection.
Vectra.Entity.Detections.src_host.threat Number Threat level associated with the source host in the detection.
Vectra.Entity.Detections.src_host.certainty Number Certainty level associated with the source host in the detection.
Vectra.Entity.Detections.src_ip String Source IP address in the detection.
Vectra.Entity.Detections.state String State of the detection.
Vectra.Entity.Detections.summary.bytes_received Number Total bytes received in the detection summary.
Vectra.Entity.Detections.summary.bytes_sent Number Total bytes sent in the detection summary.
Vectra.Entity.Detections.summary.cnc_server String CNC server associated with the detection summary.
Vectra.Entity.Detections.summary.num_events Number Total number of events related to the detection.
Vectra.Entity.Detections.summary.probable_owner Unknown Probable owner of the detection summary.
Vectra.Entity.Detections.summary.sessions Number Total sessions in the detection summary.
Vectra.Entity.Detections.tags Unknown Tags associated with the detection.
Vectra.Entity.Detections.threat Number Threat level of the detection.
Vectra.Entity.Detections.t_score Number T-score of the detection.
Vectra.Entity.Detections.type String Type of the detection.
Vectra.Entity.Detections.url String URL associated with the detection.

Command example

!vectra-detection-describe detection_ids=132,135,140

Context Example

{
  [
    {
      "id": 132,
      "category": "exfiltration",
      "certainty": 70,
      "c_score": 70,
      "description": "",
      "detection": "Data Smuggler",
      "detection_category": "exfiltration",
      "detection_type": "smuggler",
      "grouped_details": [
        {
          "event_id": "ec2162c7-e526-4446-a549-71558743a1d7",
          "event_name": "UpdateAssumeRolePolicy",
          "aws_account_id": "aws_account_id",
          "src_external_host": {
            "ip": "0.0.0.0"
          },
          "aws_region": "us-east-1",
          "access_key_id": [
            "123456"
          ],
          "identity_type": "Federated Account",
          "assumed_role": "AWSReservedSSO_AdministratorAccess_a670eb90f07e2960",
          "request_parameters": [
            "{\"roleName\": \"stratus-red-team-backdoor-r-role\", \"policyDocument\": \"{\\\"Version\\\": \\\"2012-10-17\\\", \\\"Statement\\\": {\\\"Effect\\\": \\\"Allow\\\", \\\"Principal\\\": {\\\"AWS\\\": \\\"arn:aws:iam::123456789012:root\\\"}, \\\"Action\\\": \\\"sts:AssumeRole\\\"}}\"}"
          ],
          "response_elements": [],
          "role_sequence": [
            "account_id",
            "AWSReservedSSO_AdministratorAccess_a670eb90f07e2960"
          ],
          "user_agent": [
            "stratus-red-team_06e15b96-ee6b-482c-aff4-4f2f4a46a67c"
          ],
          "last_timestamp": "2023-06-06T17:01:04Z"
        },
        {
          "event_id": "89a098eb-1198-4e2a-9fa4-ef568ae39403",
          "event_name": "UpdateAssumeRolePolicy",
          "aws_account_id": "884414556547",
          "src_external_host": {
            "ip": "0.0.0.0"
          },
          "aws_region": "us-east-1",
          "access_key_id": [
            "123456"
          ],
          "identity_type": "Federated Account",
          "assumed_role": "AWSReservedSSO_AdministratorAccess_a670eb90f07e2960",
          "request_parameters": [
            "{\"policyDocument\": \"{\\\"Version\\\": \\\"2012-10-17\\\", \\\"Statement\\\": {\\\"Effect\\\": \\\"Allow\\\", \\\"Principal\\\": {\\\"AWS\\\": \\\"arn:aws:iam::123456789012:root\\\"}, \\\"Action\\\": \\\"sts:AssumeRole\\\"}}\", \"roleName\": \"stratus-red-team-backdoor-r-role\"}"
          ],
          "response_elements": [],
          "role_sequence": [
            "account_name",
            "AWSReservedSSO_AdministratorAccess_a670eb90f07e2960"
          ],
          "user_agent": [
            "stratus-red-team_a24eac3a-4fee-46ce-bc37-b4e675343fc9"
          ],
          "last_timestamp": "2023-06-06T15:40:43Z"
        }
      ],
      "is_targeting_key_asset": false,
      "last_timestamp": "2023-06-06T17:01:04Z",
      "notes": [],
      "sensor_name": "mafosb50",
      "src_account": {
        "id": 21,
        "name": "account_name",
        "url": "http://server_url.com/api/v3.3/accounts/21",
        "threat": 76,
        "certainty": 35
      },
      "src_ip": "0.0.0.0",
      "state": "active",
      "summary": {
      },
      "tags": [],
      "threat": 80,
      "t_score": 80,
      "type": "account",
      "url": "http://server_url.com/api/v3.3/detections/132"
    },
    {
      "id": 135,
      "category": "lateral_movement",
      "certainty": 50,
      "c_score": 50,
      "description": "",
      "detection": "AWS Suspect Admin Privilege Granting",
      "detection_category": "lateral_movement",
      "detection_type": "aws_admin_privilege_granted",
      "grouped_details": [
        {
          "event_id": "85d88db5-cf2d-4b6e-9411-d3119d9920e0",
          "event_name": "AttachRolePolicy",
          "aws_account_id": "884414556547",
          "src_external_host": {
            "ip": "0.0.0.0"
          },
          "aws_region": "us-east-1",
          "access_key_id": [
            "123456"
          ],
          "identity_type": "Federated Account",
          "assumed_role": "AWSReservedSSO_AdministratorAccess_a670eb90f07e2960",
          "request_parameters": [
            "{\"roleName\":\"stratus-red-team-backdoor-r-role\",\"policyArn\":\"arn:aws:iam::aws:policy/AdministratorAccess\"}"
          ],
          "response_elements": [],
          "role_sequence": [
            "account_name",
            "AWSReservedSSO_AdministratorAccess_a670eb90f07e2960"
          ],
          "user_agent": [
            "APN/1.0 HashiCorp/1.0 Terraform/1.1.2 (+https://www.terraform.io) terraform-provider-aws/3.76.1 (+https://registry.terraform.io/providers/hashicorp/aws) aws-sdk-go/1.44.157 (go1.19.3; linux; amd64) stratus-red-team_5077134d-32ea-4403-996b-de30d7f278d7 HashiCorp-terraform-exec/0.17.3"
          ],
          "last_timestamp": "2023-06-06T17:00:46Z"
        },
        {
          "event_id": "ca157e7c-9a53-4012-9288-e6ac1c488fbc",
          "event_name": "AttachRolePolicy",
          "aws_account_id": "884414556547",
          "src_external_host": {
            "ip": "0.0.0.0"
          },
          "aws_region": "us-east-1",
          "access_key_id": [
            "123456"
          ],
          "identity_type": "Federated Account",
          "assumed_role": "AWSReservedSSO_AdministratorAccess_a670eb90f07e2960",
          "request_parameters": [
            "{\"roleName\":\"stratus-red-team-backdoor-r-role\",\"policyArn\":\"arn:aws:iam::aws:policy/AdministratorAccess\"}"
          ],
          "response_elements": [],
          "role_sequence": [
            "account_name",
            "AWSReservedSSO_AdministratorAccess_a670eb90f07e2960"
          ],
          "user_agent": [
            "APN/1.0 HashiCorp/1.0 Terraform/1.1.2 (+https://www.terraform.io) terraform-provider-aws/3.76.1 (+https://registry.terraform.io/providers/hashicorp/aws) aws-sdk-go/1.44.157 (go1.19.3; linux; amd64) stratus-red-team_01be8427-d1b5-4c18-8edb-0301c8e66c8e HashiCorp-terraform-exec/0.17.3"
          ],
          "last_timestamp": "2023-06-06T15:40:07Z"
        }
      ],
      "is_targeting_key_asset": false,
      "last_timestamp": "2023-06-06T17:00:46Z",
      "notes": [],
      "sensor_name": "mafosb50",
      "src_account": {
        "id": 21,
        "name": "account_name",
        "url": "http://server_url.com/api/v3.3/accounts/21",
        "threat": 76,
        "certainty": 35
      },
      "src_ip": "0.0.0.0",
      "state": "fixed",
      "summary": {
      },
      "tags": [],
      "threat": 60,
      "t_score": 60,
      "type": "account",
      "url": "http://server_url.com/api/v3.3/detections/135"
    },
    {
      "id": 140,
      "category": "reconnaissance",
      "certainty": 40,
      "c_score": 40,
      "description": "",
      "detection": "RPC Targeted Recon",
      "detection_category": "reconnaissance",
      "detection_type": "rpc_recon_1to1",
      "grouped_details": [
        {
          "event_id": "cf9f469b-0a8e-47c6-85eb-5a0486292e58",
          "event_name": "ModifySnapshotAttribute",
          "aws_account_id": "884414556547",
          "src_external_host": {
            "ip": "0.0.0.0"
          },
          "aws_region": "us-west-2",
          "access_key_id": [
            "123456"
          ],
          "identity_type": "Federated Account",
          "assumed_role": "AWSReservedSSO_AdministratorAccess_a670eb90f07e2960",
          "request_parameters": [
            "{\"snapshotId\":\"snap-0f7d022a2f4f67e08\",\"createVolumePermission\":{\"add\":{\"items\":[{\"userId\":\"012345678912\"}]}},\"attributeType\":\"CREATE_VOLUME_PERMISSION\"}"
          ],
          "response_elements": [
            "{\"requestId\":\"350c1eb8-b696-4d94-88d4-a764a0eed08b\",\"_return\":true}"
          ],
          "role_sequence": [
            "account_name",
            "AWSReservedSSO_AdministratorAccess_a670eb90f07e2960"
          ],
          "user_agent": [
            "stratus-red-team_a4dd596b-7a8d-4e77-a74d-13f19adf4403"
          ],
          "last_timestamp": "2023-06-06T15:46:28Z"
        }
      ],
      "is_targeting_key_asset": false,
      "last_timestamp": "2023-06-06T15:46:28Z",
      "notes": [],
      "sensor_name": "mafosb50",
      "src_account": {
        "id": 21,
        "name": "account_name",
        "url": "http://server_url.com/api/v3.3/accounts/21",
        "threat": 76,
        "certainty": 35
      },
      "src_ip": "0.0.0.0",
      "state": "fixed",
      "summary": {
      },
      "tags": [],
      "threat": 60,
      "t_score": 60,
      "type": "account",
      "url": "http://server_url.com/api/v3.3/detections/140"
    }
  ]
}

Human Readable Output

Detections Table (Showing Page 1 out of 1)

ID Detection Name Detection Type Category Account Name Src IP Threat Score Certainty Score Number Of Events State Last Timestamp
132 Data Smuggler smuggler exfiltration account_name 0.0.0.0 80 70 0 active 2023-06-06T17:01:04Z
135 AWS Suspect Admin Privilege Granting aws_admin_privilege_granted lateral_movement account_name 0.0.0.0 60 50 0 fixed 2023-06-06T17:00:46Z
140 RPC Targeted Recon rpc_recon_1to1 reconnaissance account_name 0.0.0.0 60 40 0 fixed 2023-06-06T15:46:28Z

vectra-entity-note-add


Add a note to the entity.

Base Command

vectra-entity-note-add

Input

Argument Name Description Required
entity_id Specify the id of the entity. Required
entity_type Specify the type of the entity. Possible values are: account, host. Required
note Note to be added in the specified entity_id. Required

Context Output

Path Type Description
Vectra.Entity.Notes.entity_id String ID of the entity associated with the note.
Vectra.Entity.Notes.note_id Number ID of the note.
Vectra.Entity.Notes.date_created Date Date when the note was created.
Vectra.Entity.Notes.date_modified Unknown Date when the note was last modified.
Vectra.Entity.Notes.created_by String User who created the note.
Vectra.Entity.Notes.modified_by Unknown User who last modified the note.
Vectra.Entity.Notes.note String Content of the note.

Context Output

Path Type Description
Vectra.Entity.Notes.entity_id String The ID of the entity associated with the note.
Vectra.Entity.Notes.note_id Number The ID of the note.
Vectra.Entity.Notes.date_created Date The date when the note was created.
Vectra.Entity.Notes.date_modified Unknown The date when the note was last modified.
Vectra.Entity.Notes.created_by String The user who created the note.
Vectra.Entity.Notes.modified_by Unknown The user who last modified the note.
Vectra.Entity.Notes.note String The content of the note.

Command example

!vectra-entity-note-add entity_id=1 entity_type=account note="test note"

Context Example

{
  {
    "date_created": "2023-06-21T06:19:15.224449Z",
    "created_by": "test_user",
    "note": "test_note",
    "note_id": 19,
    "entity_id": 1
  }
}

Human Readable Output

The note has been successfully added to the entity

Returned Note ID: 19

vectra-entity-note-update


Update a note in the entity.

Base Command

vectra-entity-note-update

Input

Argument Name Description Required
entity_id Specify the id of the entity. Required
entity_type Specify the type of the entity. Possible values are: account, host. Required
note_id Specify the ID of the note. Required
note Note to be updated for the specified note_id. Required

Context Output

Path Type Description
Vectra.Entity.Notes.entity_id String ID of the entity associated with the note.
Vectra.Entity.Notes.note_id Number ID of the note.
Vectra.Entity.Notes.date_created Date Date when the note was created.
Vectra.Entity.Notes.date_modified Unknown Date when the note was last modified.
Vectra.Entity.Notes.created_by String User who created the note.
Vectra.Entity.Notes.modified_by Unknown User who last modified the note.
Vectra.Entity.Notes.note String Content of the note.

Command example

!vectra-entity-note-update entity_id=1 entity_type=account note_id=1 note="note modified"

Context Example

{
  {
    "date_created": "2023-06-16T04:55:58Z",
    "date_modified": "2023-06-22T04:57:09Z",
    "created_by": "test_user",
    "modified_by": "test_user",
    "note": "note modified",
    "note_id": 8,
    "entity_id": 1
  }
}

Human Readable Output

The note has been successfully updated in the entity

vectra-entity-note-remove


Remove a note from the entity.

Base Command

vectra-entity-note-remove

Input

Argument Name Description Required
entity_id Specify the ID of the entity. Required
entity_type Specify the type of the entity. Possible values are: account, host. Required
note_id Specify the ID of the note. Required

Context Output

There is no context output for this command.

Command Example

!vectra-entity-note-remove entity_id=1 entity_type=account note_id=1"

Context Example

{}

Human Readable Output

The note has been successfully removed from the entity

vectra-entity-tag-add


Add tags in the entity.

Base Command

vectra-entity-tag-add

Input

Argument Name Description Required
entity_id Specify the id of the entity. Required
entity_type Specify the type of the entity. Possible values are: account, host. Required
tags Comma-separated values of tags to be included in the entity. Required

Context Output

Path Type Description
Vectra.Entity.Tags.tag_id String ID of the tag.
Vectra.Entity.Tags.entity_id String ID of the entity associated with the tag.
Vectra.Entity.Tags.entity_type String Type of the entity.
Vectra.Entity.Tags.tags Unknown A list of tags linked to an entity.

Command example

!vectra-entity-tag-add entity_id=1 entity_type=host tags="tag1, tag2"

Context Example

{
  {
    "tag_id": "1",
    "tags": [
        "tag1",
        "tag2"
    ],
    "entity_type": "host",
    "entity_id": 1
  }
}

Human Readable Output

Tags have been successfully added to the entity

Updated list of tags: tag1, tag2

vectra-entity-tag-remove


Remove tags from the entity.

Base Command

vectra-entity-tag-remove

Input

Argument Name Description Required
entity_id Specify the id of the entity. Required
entity_type Specify the type of the entity. Possible values are: account, host. Required
tags Comma-separated values of tags to be removed from the entity. Required

Context Output

Path Type Description
Vectra.Entity.Tags.tag_id String ID of the tag.
Vectra.Entity.Tags.entity_id String ID of the entity associated with the tag.
Vectra.Entity.Tags.entity_type String Type of the entity.
Vectra.Entity.Tags.tags Unknown A list of tags linked to an entity.

Command example

!vectra-entity-tag-remove entity_id=1 entity_type=host tags="tag2"

Context Example

{
  {
    "tag_id": "1",
    "tags": ["tag1"],
    "entity_type": "host",
    "entity_id": 1
  }
}

Human Readable Output

Specified tags have been successfully removed for the entity

Updated list of tags: tag1

vectra-entity-tag-list


Returns a list of tags for a specified entity.

Base Command

vectra-entity-tag-list

Input

Argument Name Description Required
entity_id Specify the id of the entity. Required
entity_type Specify the type of the entity. Possible values are: account, host. Required

Context Output

Path Type Description
Vectra.Entity.Tags.tag_id String ID of the tag.
Vectra.Entity.Tags.entity_id String ID of the entity associated with the tag.
Vectra.Entity.Tags.entity_type String Type of the entity.
Vectra.Entity.Tags.tags Unknown A list of tags linked to an entity.

Command example

!vectra-entity-tag-list entity_id=1 entity_type=host

Context Example

{
  "Vectra": {
    "Entity": {
      "Tags": {
        "tag_id": "1",
        "tags": [
            "tag1",
            "tag2"
        ],
        "entity_type": "host",
        "entity_id": 1
      }
    }
  }
}

Human Readable Output

List of tags: tag1, tag2

vectra-entity-assignment-add


Add an assignment for the entity.

Base Command

vectra-entity-assignment-add

Input

Argument Name Description Required
entity_id Specify the ID of the entity. Required
entity_type Specify the type of the entity. Possible values are: account, host. Required
user_id Specify the ID of the user. Required

Context Output

Path Type Description
Vectra.Entity.Assignments.id Number ID of the assignment.
Vectra.Entity.Assignments.assignment_id Number ID of the assignment.
Vectra.Entity.Assignments.assigned_by.id Number ID of the user who assigned the entity.
Vectra.Entity.Assignments.assigned_by.username String Username of the user who assigned the entity.
Vectra.Entity.Assignments.date_assigned Date Date when the entity was assigned.
Vectra.Entity.Assignments.date_resolved Date Date when the entity was resolved.
Vectra.Entity.Assignments.events.assignment_id Number ID of the assignment event.
Vectra.Entity.Assignments.events.actor Number ID of the actor who performed the assignment event.
Vectra.Entity.Assignments.events.event_type String Type of assignment event.
Vectra.Entity.Assignments.events.datetime Date Date of the assignment event.
Vectra.Entity.Assignments.events.context.to Number ID of the entity that was assigned to.
Vectra.Entity.Assignments.events.context.entity_t_score Number Threat score of the entity that was assigned to.
Vectra.Entity.Assignments.events.context.entity_c_score Number Certainty score of the entity that was assigned to.
Vectra.Entity.Assignments.outcome.id String ID of the assignment outcome.
Vectra.Entity.Assignments.outcome.builtin String Whether the assignment outcome is builtin or not.
Vectra.Entity.Assignments.outcome.user_selectable String Whether the assignment outcome is user selectable or not.
Vectra.Entity.Assignments.outcome.title String Title of the assignment outcome.
Vectra.Entity.Assignments.outcome.category String Category of the assignment outcome.
Vectra.Entity.Assignments.resolved_by.id Number ID of the user who resolved the entity.
Vectra.Entity.Assignments.resolved_by.username String Username of the user who resolved the entity.
Vectra.Entity.Assignments.triaged_detections Unknown Number of detections that have been triaged for the entity.
Vectra.Entity.Assignments.host_id Number ID of the host that the entity is associated with.
Vectra.Entity.Assignments.account_id Unknown ID of the account that the entity is associated with.
Vectra.Entity.Assignments.assigned_to.id Number ID of the user who is currently assigned to the entity.
Vectra.Entity.Assignments.assigned_to.username String Username of the user who is currently assigned to the entity.

Command Example

!vectra-entity-assignment-add entity_id=1 entity_type=account user_id=1

Context Example

{
  {
    "assigned_by": {
      "id": 2,
      "username": "test_user_2"
    },
    "date_assigned": "2023-07-24T08:52:59.367115Z",
    "events": [
      {
        "assignment_id": 74,
        "actor": 65,
        "event_type": "created",
        "datetime": "2023-07-24T08:52:59Z",
        "context": {
          "to": 60,
          "entity_t_score": 0,
          "entity_c_score": 0
        }
      }
    ],
    "host_id": 10,
    "assigned_to": {
      "id": 1,
      "username": "test.user@example.com"
    },
    "assignment_id": 1,
    "id":1
  }
}

Human Readable Output

The assignment has been successfully created

Assignment detail

Assignment ID Assigned By Assigned Date Assigned To Event Type
1 test_user_2 2023-07-24T08:52:59.367115Z test.user@example.com created

vectra-entity-assignment-update


Update an assignment in the entity.

Base Command

vectra-entity-assignment-update

Input

Argument Name Description Required
assignment_id Specify the ID of the assignment. Required
user_id Specify the ID of the user. Required

Context Output

Path Type Description
Vectra.Entity.Assignments.id Number ID of the assignment.
Vectra.Entity.Assignments.assignment_id Number ID of the assignment.
Vectra.Entity.Assignments.assigned_by.id Number ID of the user who assigned the entity.
Vectra.Entity.Assignments.assigned_by.username String Username of the user who assigned the entity.
Vectra.Entity.Assignments.date_assigned Date Date when the entity was assigned.
Vectra.Entity.Assignments.date_resolved Date Date when the entity was resolved.
Vectra.Entity.Assignments.events.assignment_id Number ID of the assignment event.
Vectra.Entity.Assignments.events.actor Number ID of the actor who performed the assignment event.
Vectra.Entity.Assignments.events.event_type String Type of assignment event.
Vectra.Entity.Assignments.events.datetime Date Date of the assignment event.
Vectra.Entity.Assignments.events.context.to Number ID of the entity that was assigned to.
Vectra.Entity.Assignments.events.context.from Number ID of the entity that was assigned.
Vectra.Entity.Assignments.events.context.entity_t_score Number Threat score of the entity that was assigned to.
Vectra.Entity.Assignments.events.context.entity_c_score Number Certainty score of the entity that was assigned to.
Vectra.Entity.Assignments.outcome.id String ID of the assignment outcome.
Vectra.Entity.Assignments.outcome.builtin String Whether the assignment outcome is builtin or not.
Vectra.Entity.Assignments.outcome.user_selectable String Whether the assignment outcome is user selectable or not.
Vectra.Entity.Assignments.outcome.title String Title of the assignment outcome.
Vectra.Entity.Assignments.outcome.category String Category of the assignment outcome.
Vectra.Entity.Assignments.resolved_by.id Number ID of the user who resolved the entity.
Vectra.Entity.Assignments.resolved_by.username String Username of the user who resolved the entity.
Vectra.Entity.Assignments.triaged_detections Unknown Number of detections that have been triaged for the entity.
Vectra.Entity.Assignments.host_id Number ID of the host that the entity is associated with.
Vectra.Entity.Assignments.account_id Unknown ID of the account that the entity is associated with.
Vectra.Entity.Assignments.assigned_to.id Number ID of the user who is currently assigned to the entity.
Vectra.Entity.Assignments.assigned_to.username String Username of the user who is currently assigned to the entity.

Command Example

!vectra-entity-assignment-update assignment_id=1 user_id=2

Context Example

{
  {
    "assigned_by": {
      "id": 65,
      "username": "api_client"
    },
    "date_assigned": "2023-07-21T12:44:10Z",
    "events": [
      {
        "assignment_id": 1,
        "actor": 65,
        "event_type": "reassigned",
        "datetime": "2023-07-25T06:26:10Z",
        "context": {
          "from": 1,
          "to": 2,
          "entity_t_score": 68,
          "entity_c_score": 90
        }
      },
      {
        "assignment_id": 1,
        "actor": 65,
        "event_type": "created",
        "datetime": "2023-07-21T12:44:10Z",
        "context": {
          "to": 1,
          "entity_t_score": 68,
          "entity_c_score": 90
        }
      }
    ],
    "host_id": 97,
    "assigned_to": {
      "id": 2,
      "username": "test_user_2"
    },
    "assignment_id": 1,
    "id": 1
  }
}

Human Readable Output

The assignment has been successfully updated

Assignment detail

Assignment ID Assigned By Assigned Date Assigned To Event Type
1 api_client 2023-07-21T12:44:10Z test_user_2 reassigned

vectra-detection-pcap-download


Download pcap of the detection.

Base Command

vectra-detection-pcap-download

Input

Argument Name Description Required
detection_id Specify the ID of the detection. Required

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.SHA512 String The SHA512 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

!vectra-detection-pcap-download detection_id="116"

Context Example

{
    "File": {
      "EntryID": "1703@7e0f6637-f0a4-46b3-8c61-2f94b3432428",
      "Extension": "pcap",
      "Info": "pcap-ng capture file - version 1.0",
      "MD5": "709db6e1f8f5054ca57caf43ba248ed6",
      "Name": "IP-192.168.55.10_hidden_dns_tunnel_1382.pcap",
      "SHA1": "49fe55c6aef85549261b46dd2e54f8d485306ee5",
      "SHA256": "8615bde9332584b4fd4fe4dc2cc6fc4c75504f6d44667814456c089fd413aa4d",
      "SHA512": "3fa29be0e20884c850b62d2a99aa09b24488289ba0bc9aff37ebe982c21d3a78fb26d9c9ac7fbf2a0839ba649dc0a845f30e7f13de3a0c6284c3c2ac54102143",
      "SSDeep": "384:dN+Pm11R0XPmts64kZog9ZaikYngk+SnRxFyeyCEyuAOasucOcakca0/rHfcjOUI:dI+t25caEPjRSnmuNasxRana4DgOUDcX",
      "Size": 23988,
      "Type": "application/vnd.tcpdump.pcap"
  }
}

Human Readable Output

Uploaded file: IP-192.168.55.10_hidden_dns_tunnel_1382.pcap

Property Type Size Info MD5 SHA1 SHA256 SHA512 SSDeep
Value application/vnd.tcpdump.pcap 23,988 bytes pcap-ng capture file - version 1.0 709db6e1f8f5054ca57caf43ba248ed6 49fe55c6aef85549261b46dd2e54f8d485306ee5 8615bde9332584b4fd4fe4dc2cc6fc4c75504f6d44667814456c089fd413aa4d 3fa29be0e20884c850b62d2a99aa09b24488289ba0bc9aff37ebe982c21d3a78fb26d9c9ac7fbf2a0839ba649dc0a845f30e7f13de3a0c6284c3c2ac54102143 384:dN+Pm11R0XPmts64kZog9ZaikYngk+SnRxFyeyCEyuAOasucOcakca0/rHfcjOUI:dI+t25caEPjRSnmuNasxRana4DgOUDcX

vectra-assignment-list


Returns a list of all assignments.

Base Command

vectra-assignment-list

Input

Argument Name Description Required
entity_ids Specify the IDs of the entities. Comma-separated values supported. Optional
entity_type Specify the type of the entity. Possible values are: account, host. Optional
resolved Filter by resolved status. Possible values are: True, False. Optional
assignees Filter by user ids of the assignment. Comma-separated values supported. Optional
resolution Filter by outcome ids of the resolution. Comma-separated values supported. Optional
created_after Filter by created after the timestamp.

Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ.

For example: 01 May 2023, 01 Mar 2021 04:45:33, 2022-04-17T14:05:44Z.
Optional
page Enables the caller to specify a particular page of results. Default is 1. Optional
page_size Specify the desired page size for the request. Default is 50. Optional

Context Output

Path Type Description
Vectra.Entity.Assignments.id Number ID of the assignment.
Vectra.Entity.Assignments.assignment_id Number ID of the assignment.
Vectra.Entity.Assignments.assigned_by.id Number ID of the user who assigned the entity.
Vectra.Entity.Assignments.assigned_by.username String Username of the user who assigned the entity.
Vectra.Entity.Assignments.date_assigned Date Date when the entity was assigned.
Vectra.Entity.Assignments.date_resolved Date Date when the entity was resolved.
Vectra.Entity.Assignments.events.assignment_id Number ID of the assignment event.
Vectra.Entity.Assignments.events.actor Number ID of the actor who performed the assignment event.
Vectra.Entity.Assignments.events.event_type String Type of the assignment event.
Vectra.Entity.Assignments.events.datetime Date Date of the assignment event.
Vectra.Entity.Assignments.events.context.to Number ID of the entity that was assigned to.
Vectra.Entity.Assignments.events.context.entity_t_score Number Threat score of the entity that was assigned to.
Vectra.Entity.Assignments.events.context.entity_c_score Number Certainty score of the entity that was assigned to.
Vectra.Entity.Assignments.events.context.triage_as String Triage status of the entity.
Vectra.Entity.Assignments.events.context.triaged_detection_ids Array IDs of the detections that have been triaged for the entity.
Vectra.Entity.Assignments.events.context.fixed_detection_ids Array IDs of the detections that have been fixed.
Vectra.Entity.Assignments.events.context.created_rule_ids Array IDs of the rules that have been created for the entity.
Vectra.Entity.Assignments.outcome.id Number ID of the assignment outcome.
Vectra.Entity.Assignments.outcome.builtin Boolean Whether the assignment outcome is builtin or not.
Vectra.Entity.Assignments.outcome.user_selectable Boolean Whether the assignment outcome is user selectable or not.
Vectra.Entity.Assignments.outcome.title String Title of the assignment outcome.
Vectra.Entity.Assignments.outcome.category String Category of the assignment outcome.
Vectra.Entity.Assignments.resolved_by.id Number ID of the user who resolved the entity.
Vectra.Entity.Assignments.resolved_by.username String Username of the user who resolved the entity.
Vectra.Entity.Assignments.triaged_detections Array Number of detections that have been triaged for the entity.
Vectra.Entity.Assignments.host_id Number ID of the host that the entity is associated with.
Vectra.Entity.Assignments.account_id Number ID of the account that the entity is associated with.
Vectra.Entity.Assignments.assigned_to.id Number ID of the user who is currently assigned to the entity.
Vectra.Entity.Assignments.assigned_to.username String Username of the user who is currently assigned to the entity.

Command Example


#### Context Example

```json
{
    "Vectra": {
      "Entity": {
        "Assignments": [
          {
            "id": 214,
            "assigned_by": {
              "id": 64,
              "username": "test.user4@example.com"
            },
            "date_assigned": "2023-08-18T10:55:29Z",
            "events": [
              {
                "assignment_id": 214,
                "actor": 64,
                "event_type": "reassigned",
                "datetime": "2023-08-18T10:56:11Z",
                "context": {
                  "from": 39,
                  "to": 59,
                  "entity_t_score": 0,
                  "entity_c_score": 0
                }
              },
              {
                "assignment_id": 214,
                "actor": 64,
                "event_type": "created",
                "datetime": "2023-08-18T10:55:29Z",
                "context": {
                  "to": 39,
                  "entity_t_score": 0,
                  "entity_c_score": 0
                }
              }
            ],
            "host_id": 220,
            "assigned_to": {
              "id": 59,
              "username": "test.user2@example.com"
            },
            "assignment_id": 214
          },
          {
            "id": 212,
            "assigned_by": {
              "id": 65,
              "username": "test.user4@example.com"
            },
            "date_assigned": "2023-08-18T06:29:56Z",
            "date_resolved": "2023-08-18T06:32:09Z",
            "events": [
              {
                "assignment_id": 212,
                "actor": 65,
                "event_type": "resolved",
                "datetime": "2023-08-18T06:32:09Z",
                "context": {
                  "entity_t_score": 77,
                  "entity_c_score": 53
                }
              },
              {
                "assignment_id": 212,
                "actor": 65,
                "event_type": "reassigned",
                "datetime": "2023-08-18T06:31:02Z",
                "context": {
                  "from": 59,
                  "to": 60,
                  "entity_t_score": 77,
                  "entity_c_score": 53
                }
              },
              {
                "assignment_id": 212,
                "actor": 65,
                "event_type": "created",
                "datetime": "2023-08-18T06:29:56Z",
                "context": {
                  "to": 59,
                  "entity_t_score": 77,
                  "entity_c_score": 53
                }
              }
            ],
            "outcome": {
              "id": 1,
              "builtin": true,
              "user_selectable": true,
              "title": "Benign True Positive",
              "category": "benign_true_positive"
            },
            "resolved_by": {
              "id": 65,
              "username": "test.user4@example.com"
            },
            "account_id": 108,
            "assigned_to": {
              "id": 60,
              "username": "test.user1@example.com"
            },
            "assignment_id": 212
          }
        ]
      }
    }
  }

Human Readable Output

Assignments Table (Showing Page 1 out of 1)

Account ID Host ID Assignment ID Assigned By Assigned To Date Assigned Resolved By Date Resolved Outcome ID Outcome
  220 214 test.user4@example.com test.user2@example.com 2023-08-18T10:55:29Z        
108   212 test.user4@example.com test.user1@example.com 2023-08-18T06:29:56Z test.user4@example.com 2023-08-18T06:32:09Z 1 Benign True Positive

vectra-entity-note-list


Returns a list of notes for a specified entity.

Base Command

vectra-entity-note-list

Input

Argument Name Description Required
entity_id Specify the ID of the entity. Required
entity_type Specify the type of the entity. Possible values are: host, account. Required

Context Output

Path Type Description
Vectra.Entity.Notes.note_id Number ID of the note.
Vectra.Entity.Notes.id Number ID of the note.
Vectra.Entity.Notes.date_created Date Date when the note was created.
Vectra.Entity.Notes.date_modified Unknown Date when the note was last modified.
Vectra.Entity.Notes.created_by String User who created the note.
Vectra.Entity.Notes.modified_by Unknown User who last modified the note.
Vectra.Entity.Notes.note String Content of the note.
Vectra.Entity.Notes.entity_id String ID of the entity associated with the note.
Vectra.Entity.Notes.entity_type String Type of the entity associated with the note.

Command Example

!vectra-entity-note-list entity_id="107" entity_type="account"

Context Example

{
  "Vectra": {
    "Entity": {
      "Notes": [
        {
          "created_by": "test_user@example.com",
          "date_created": "2023-08-25T07:09:08Z",
          "entity_id": 107,
          "entity_type": "account",
          "id": 1070,
          "modified_by": "test_user@example.com",
          "note": "From XSOAR",
          "note_id": 1070
        },
        {
          "created_by": "test_user@example.com",
          "date_created": "2023-08-25T07:08:58Z",
          "entity_id": 107,
          "entity_type": "account",
          "id": 1069,
          "modified_by": "test_user@example.com",
          "note": "Test note",
          "note_id": 1069
        },
        {
          "created_by": "api_client",
          "date_created": "2023-08-16T05:23:33Z",
          "entity_id": 107,
          "entity_type": "account",
          "id": 922,
          "note": "[Mirrored From XSOAR] XSOAR Incident ID: 14228\n\nNote: **bold**\n\n_Italic_\n\n+Underline+\n\n~~strikethrough~~\n\nAdded By: admin",
          "note_id": 922
        }
      ]
    }
  }
}

Human Readable Output

Entity Notes Table

Note ID Note Created By Created Date Modified By Modified Date
1070 From XSOAR test_user@example.com 2023-08-25T07:09:08Z test_user@example.com 2023-08-25T08:10:08Z
1069 Test note test_user@example.com 2023-08-25T07:08:58Z test_user@example.com 2023-08-25T08:10:08Z
922 [Mirrored From XSOAR] XSOAR Incident ID: 14228
Note:XSOAR note
Added By: admin
api_client 2023-08-16T05:23:33Z    

vectra-group-list


Returns a list of all groups.

Base Command

vectra-group-list

Input

Argument Name Description Required
group_type Filter by group type. Possible values are: account, host, ip, domain. Optional
account_names Filter by Account Names. Supports comma-separated values.

Note: Only valid when the group_type parameter is set to “account”.
Optional
domains Filter by Domains. Supports comma-separated values.

Note: Only valid when the group_type parameter is set to “domain”.
Optional
host_ids Filter by Host IDs. Supports comma-separated values.

Note: Only valid when the group_type parameter is set to “host”.
Optional
host_names Filter by Host Names. Supports comma-separated values.

Note: Only valid when the group_type parameter is set to “host”.
Optional
importance Filter by group importance. Possible values are: high, medium, low, never_prioritize. Optional
ips Filter by IPs. Supports comma-separated values.

Note: Only valid when the group_type parameter is set to “ip”.
Optional
description Filter by group description. Optional
last_modified_timestamp Return only the groups which have a last modification timestamp equal to or after the given timestamp.

Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ.

For example: 01 May 2023, 01 Mar 2023 04:45:33, 2023-04-17T14:05:44Z.
Optional
last_modified_by Filters by the user id who made the most recent modification to the group. Optional
group_name Filters by group name. Optional

Context Output

Path Type Description
Vectra.Group.group_id Number ID of the group.
Vectra.Group.id Number ID of the group.
Vectra.Group.name String Name of the group.
Vectra.Group.description String Description of the group.
Vectra.Group.last_modified Date Date when the group was last modified.
Vectra.Group.last_modified_by String Name of the user who last modified the group.
Vectra.Group.type String Type of the group.
Vectra.Group.members Unknown Members of the group.
Vectra.Group.members.id Number Entity ID of member.
Vectra.Group.members.name String Entity name of member.
Vectra.Group.members.is_key_asset Boolean Indicates key asset.
Vectra.Group.members.url String Entity URL of member.
Vectra.Group.members.uid String Entity UID of member.
Vectra.Group.rules.triage_category String Triage category of rule.
Vectra.Group.rules.id Number Id of the rule.
Vectra.Group.rules.description String Description of the rule.
Vectra.Group.importance String Importance level of the group.
Vectra.Group.cognito_managed Boolean Whether the group is managed by Cognito or not.

Command Example


#### Context Example

```json
{
  "Vectra": {
    "Group": [
      {
        "id": 1,
        "group_id": 1,
        "name": "Cognito - Box",
        "description": "Domains used by the Box service",
        "last_modified": "2023-05-31T13:57:53Z",
        "last_modified_by": "cognito",
        "type": "domain",
        "members": [
          "*.abc.com",
          "*.xyz.net"
        ],
        "rules": [
          {
            "triage_category": "Box",
            "id": 175,
            "description": "data storage to Box service"
          }
        ],
        "importance": "medium",
        "cognito_managed": true
      },
      {
        "id": 8,
        "group_id": 8,
        "name": "Cognito - IPAM",
        "description": "IPAM, created by Cognito",
        "last_modified": "2023-08-18T09:16:54Z",
        "last_modified_by": "cognito",
        "type": "host",
        "members": [
          {
            "is_key_asset": false,
            "id": 97,
            "name": "IP-0.0.0.0",
            "url": "https://server_url.com/api/v3.3/hosts/97"
          },
          {
            "is_key_asset": false,
            "id": 212,
            "name": "IP-0.0.0.1",
            "url": "https://server_url.com/api/v3.3/hosts/212"
          }
        ],
        "rules": [
          {
            "triage_category": "Expected IPAM Behavior",
            "id": 189,
            "description": "Expected behavior from these devices"
          },
          {
            "triage_category": "Expected IPAM Behavior",
            "id": 193,
            "description": "Expected behavior from these devices"
          }
        ],
        "importance": "medium"
      },
      {
        "id": 16,
        "group_id": 16,
        "name": "Cognito - Guest Wifi",
        "description": "IP space used by Guest Wifi",
        "last_modified": "2023-08-18T08:55:54Z",
        "last_modified_by": "cognito",
        "type": "ip",
        "members": [
          "0.0.0.0",
          "0.0.0.1"
        ],
        "importance": "medium",
        "cognito_managed": false
      },
      {
        "id": 22,
        "group_id": 22,
        "name": "Dev-Group-Account-High",
        "description": "",
        "last_modified": "2023-08-25T10:17:37Z",
        "last_modified_by": "cognito",
        "type": "account",
        "members": [
          {
            "uid": "O300:service-principal_00000000-0000-0000-0000-000000000001"
          },
          {
            "uid": "administrator@fictotech.com"
          }
        ],
        "importance": "high"
      }
    ]
  }
}

Human Readable Output

Groups Table

Group ID Name Group Type Description Importance Members Last Modified Timestamp
1 Cognito - Box domain Domains used by the Box service medium *.abc.com, *.xyz.net 2023-05-31T13:57:53Z
8 Cognito - IPAM host IPAM, created by Cognito medium 97, 212 2023-08-18T09:16:54Z
16 Cognito - Guest Wifi ip IP space used by Guest Wifi medium 0.0.0.0, 0.0.0.1 2023-08-18T08:55:54Z
22 Dev-Group-Account-High account   high O300:service-principal_00000000-0000-0000-0000-000000000001, administrator@fictotech.com 2023-08-25T10:17:37Z

vectra-group-unassign


Unassign members from the specified group.

Base Command

vectra-group-unassign

Input

Argument Name Description Required
group_id Specify Group ID to unassign members. Required
members Member values based on the group type. Supports comma-separated values.

Note:
If the group type is host, then the “Host IDs”.
If the group type is account, then “Account Names”.
If the group type is ip, then the list of “IPs”.
If the group type is domain, then the list of “Domains” .
Required

Context Output

Path Type Description
Vectra.Group.group_id Number ID of the group.
Vectra.Group.id Number ID of the group.
Vectra.Group.name String Name of the group.
Vectra.Group.description String Description of the group.
Vectra.Group.last_modified Date Date when the group was last modified.
Vectra.Group.last_modified_by String Name of the user who last modified the group.
Vectra.Group.type String Type of the group.
Vectra.Group.members Unknown Members of the group.
Vectra.Group.members.id Number Entity ID of member.
Vectra.Group.members.name String Entity name of member.
Vectra.Group.members.is_key_asset Boolean Indicates key asset.
Vectra.Group.members.url String Entity URL of member.
Vectra.Group.members.uid String Entity UID of member.
Vectra.Group.rules.triage_category String Triage category of rule.
Vectra.Group.rules.id Number Id of the rule.
Vectra.Group.rules.description String Description of the rule.

Command Example

!vectra-group-unassign group_id=23 members="*.domain4.com,*.domain5.com"

Context Example

{
  "Vectra": {
    "Group": {
      "cognito_managed": false,
      "description": "xsoar-group-account-test",
      "group_id": 23,
      "id": 23,
      "last_modified": "2023-09-04T12:03:02Z",
      "last_modified_by": "API Client a7f5be37",
      "members": ["*.domain1.net", "*.domain2.com", "*.domain3.com"],
      "name": "xsoar-group-account-test",
      "type": "domain"
    }
  }
}

Human Readable Output

Member(s) *.domain4.com, *.domain5.com have been unassigned from the group

Updated group details

Group ID Name Group Type Description Members Last Modified Timestamp
1 xsoar-group-account-test domain xsoar-group-account-test *.domain1.net, *.domain2.com, *.domain3.com 2023-09-04T07:30:01Z

vectra-group-assign


Assign members to the specified group.

Base Command

vectra-group-assign

Input

Argument Name Description Required
group_id Specify Group ID to assign members. Required
members Member values based on the group type. Supports comma-separated values.

Note:
If the group type is host, then the “Host IDs”.
If the group type is account, then “Account Names”.
If the group type is ip, then the list of “IPs”.
If the group type is domain, then the list of “Domains” .
Required

Context Output

Path Type Description
Vectra.Group.group_id Number ID of the group.
Vectra.Group.id Number ID of the group.
Vectra.Group.name String Name of the group.
Vectra.Group.description String Description of the group.
Vectra.Group.last_modified Date Date when the group was last modified.
Vectra.Group.last_modified_by String Name of the user who last modified the group.
Vectra.Group.type String Type of the group.
Vectra.Group.members Unknown Members of the group.
Vectra.Group.members.id Number Entity ID of member.
Vectra.Group.members.name String Entity name of member.
Vectra.Group.members.is_key_asset Boolean Indicates key asset.
Vectra.Group.members.url String Entity URL of member.
Vectra.Group.members.uid String Entity UID of member.
Vectra.Group.rules.triage_category String Triage category of rule.
Vectra.Group.rules.id Number Id of the rule.
Vectra.Group.rules.description String Description of the rule.

Command Example

!vectra-group-assign group_id=23 members="*.domain4.com,*.domain5.com"

Context Example

{
  "Vectra": {
    "Group": {
      "cognito_managed": false,
      "description": "xsoar-group-account-test",
      "group_id": 23,
      "id": 23,
      "last_modified": "2023-09-04T11:59:15Z",
      "last_modified_by": "API Client a7f5be37",
      "members": [
        "*.domain1.net",
        "*.domain2.com",
        "*.domain3.com",
        "*.domain4.com",
        "*.domain5.com"
      ],
      "name": "xsoar-group-account-test",
      "type": "domain"
    }
  }
}

Human Readable Output

Member(s) *.domain4.com, *.domain5.com have been assigned to the group

Updated group details

Group ID Name Group Type Description Members Last Modified Timestamp
1 xsoar-group-account-test domain xsoar-group-account-test *.domain1.net, *.domain2.com, *.domain3.com, *.domain4.com, *.domain5.com 2023-09-04T06:30:01Z

vectra-entity-detections-mark-asclosed


Mark the detections of the entity as closed with the provided entity ID in the argument.

Base Command

vectra-entity-detections-mark-asclosed

Input

Argument Name Description Required
entity_id Specify the ID of the entity. Required
entity_type Specify the type of the entity. Possible values are: account, host. Required
close_reason Specify the close reason. Possible values are: benign, remediated. Required

Context Output

There is no context output for this command.

Command example

!vectra-entity-detections-mark-asclosed entity_id=1 entity_type=account close_reason=benign

Human Readable Output

The detections (34122, 35097) of the provided entity ID have been successfully closed as benign

vectra-detections-mark-asopen


Open detections with provided detection IDs in the argument.

Base Command

vectra-detections-mark-asopen

Input

Argument Name Description Required
detection_ids Provide a list of detection IDs separated by commas or a single detection ID. Required

Context Output

There is no context output for this command.

Command example

!vectra-detections-mark-asopen detection_ids=1,2,3

Human Readable Output

The provided detection IDs have been successfully re-opened

vectra-detection-tag-list


Returns a list of tags for a specified detection.

Base Command

vectra-detection-tag-list

Input

Argument Name Description Required
detection_id Specify the ID of the detection. Required

Context Output

Path Type Description
Vectra.Detection.Tags.tag_id String The ID of the tag.
Vectra.Detection.Tags.detection_id String The ID of the Detection associated with the tag.
Vectra.Detection.Tags.tags Unknown A list of tags linked to a detection.

Command example

!vectra-detection-tag-list detection_id=123

Context Example

{
    "Vectra": {
        "Detection": {
            "Tags": {
                "detection_id": 123,
                "tag_id": "123",
                "tags": [
                    "tag1",
                    "tag2"
                ]
            }
        }
    }
}

Human Readable Output

List of tags: tag1, tag2

vectra-detection-tag-add


Add tags to a detection.

Base Command

vectra-detection-tag-add

Input

Argument Name Description Required
detection_id Specify the ID of the detection. Required
tags Comma-separated values of tags to be added to the detection. Required

Context Output

Path Type Description
Vectra.Detection.Tags.tag_id String The ID of the tag.
Vectra.Detection.Tags.detection_id String The ID of the detection associated with the tag.
Vectra.Detection.Tags.tags Unknown A list of tags linked to a detection.

Command example

!vectra-detection-tag-add detection_id=1 tags="tag1,tag2"

Context Example

{
    "Vectra": {
        "Detection": {
            "Tags": {
                "detection_id": 1,
                "tag_id": 1,
                "tags": [
                    "tag",
                    "tag1",
                    "tag2"
                ]
            }
        }
    }
}

Human Readable Output

Tags have been successfully added to the detection

Updated list of tags: tag, tag1, tag2

vectra-detection-tag-remove


Remove tags from the detection.

Base Command

vectra-detection-tag-remove

Input

Argument Name Description Required
detection_id Specify the ID of the detection. Required
tags Comma-separated values of tags to be removed from the detection. Required

Context Output

Path Type Description
Vectra.Detection.Tags.tag_id String The ID of the tag.
Vectra.Detection.Tags.detection_id String The ID of the detection associated with the tag.
Vectra.Detection.Tags.tags Unknown A list of tags linked to a detection.

Command example

!vectra-detection-tag-remove detection_id="2" tags="tag3,tag4"

Context Example

{
    "Vectra": {
        "Detection": {
            "Tags": {
                "detection_id": 2,
                "tag_id": "2",
                "tags": [
                    "tag",
                    "tag1",
                    "tag2"
                ]
            }
        }
    }
}

Human Readable Output

Specified tags have been successfully removed for the detection

Updated list of tags: tag, tag1, tag2

vectra-detection-note-list


Returns a list of notes for a specified detection.

Base Command

vectra-detection-note-list

Input

Argument Name Description Required
detection_id Specify the ID of the detection. Required

Context Output

Path Type Description
Vectra.Detection.Notes.note_id Number ID of the note.
Vectra.Detection.Notes.id Number ID of the note.
Vectra.Detection.Notes.date_created Date Date when the note was created (ISO8601).
Vectra.Detection.Notes.date_modified Date Date when the note was last modified (ISO8601).
Vectra.Detection.Notes.created_by String User who created the note.
Vectra.Detection.Notes.modified_by String User who last modified the note.
Vectra.Detection.Notes.note String Content of the note.
Vectra.Detection.Notes.detection_id String ID of the detection associated with the note.

Command example

!vectra-detection-note-list detection_id=1

Context Example

{
  "Vectra": {
    "Detection": {
      "Notes": [
        {
          "created_by": "test_user@example.com",
          "date_created": "2023-08-25T07:09:08Z",
          "detection_id": 1,
          "id": 1070,
          "modified_by": "test_user@example.com",
          "note": "From XSOAR",
          "note_id": 1070
        },
        {
          "created_by": "test_user@example.com",
          "date_created": "2023-08-25T07:08:58Z",
          "detection_id": 1,
          "id": 1069,
          "modified_by": "test_user@example.com",
          "note": "Test note",
          "note_id": 1069
        },
        {
          "created_by": "api_client",
          "date_created": "2023-08-16T05:23:33Z",
          "detection_id": 1,
          "id": 922,
          "note": "[Mirrored From XSOAR] XSOAR Incident ID: 14228\n\nNote: **bold**\n\n_Italic_\n\n+Underline+\n\n~~strikethrough~~\n\nAdded By: admin",
          "note_id": 922
        }
      ]
    }
  }
}

Human Readable Output

Detection Notes Table

Note ID Note Created By Created Date Modified By Modified Date
1070 From XSOAR test_user@example.com 2023-08-25T07:08:58Z test_user@example.com 2023-08-25T07:08:58Z
1069 Test note test_user@example.com 2023-08-25T07:08:58Z test_user@example.com 2023-08-25T07:08:58Z
922 [Mirrored From XSOAR] XSOAR Incident ID: 14228\n\nNote: bold\n\n_Italic_\n\n+Underline+\n\nstrikethrough\n\nAdded By: admin api_client 2023-08-16T05:23:33Z    

vectra-detection-note-add


Add a note to the detection.

Base Command

vectra-detection-note-add

Input

Argument Name Description Required
detection_id Specify the ID of the detection. Required
note Note to be added in the specified detection_id. Required

Context Output

Path Type Description
Vectra.Detection.Notes.detection_id String ID of the detection associated with the note.
Vectra.Detection.Notes.note_id Number ID of the note.
Vectra.Detection.Notes.id Number ID of the note.
Vectra.Detection.Notes.date_created Date Date when the note was created (ISO8601).
Vectra.Detection.Notes.created_by String User who created the note.
Vectra.Detection.Notes.note String Content of the note.

Command example

!vectra-detection-note-add detection_id=1 note="test note"

Context Example

{
  {
    "date_created": "2023-06-21T06:19:15.224449Z",
    "created_by": "test_user",
    "note": "test note",
    "note_id": 19,
    "id": 19,
    "detection_id": 1
  }
}

Human Readable Output

The note has been successfully added to the detection

Returned Note ID: 19

vectra-detection-note-update


Update a note in the detection.

Base Command

vectra-detection-note-update

Input

Argument Name Description Required
detection_id Specify the ID of the detection. Required
note_id Specify the ID of the note. Required
note Note to be updated for the specified note_id. Required

Context Output

Path Type Description
Vectra.Detection.Notes.detection_id String ID of the detection associated with the note.
Vectra.Detection.Notes.note_id Number ID of the note.
Vectra.Detection.Notes.id Number ID of the note.
Vectra.Detection.Notes.date_created Date Date when the note was created (ISO8601).
Vectra.Detection.Notes.date_modified Date Date when the note was last modified (ISO8601).
Vectra.Detection.Notes.created_by String User who created the note.
Vectra.Detection.Notes.modified_by String User who last modified the note.
Vectra.Detection.Notes.note String Content of the note.

Command example

!vectra-detection-note-update detection_id=1 note_id=1 note="note modified"

Context Example

{
  {
    "date_created": "2023-06-16T04:55:58Z",
    "date_modified": "2023-06-22T04:57:09Z",
    "created_by": "test_user",
    "modified_by": "test_user",
    "note": "note modified",
    "note_id": 8,
    "id": 8,
    "detection_id": 1
  }
}

Human Readable Output

The note has been successfully updated in the detection

vectra-detection-note-remove


Remove a note from the detection.

Base Command

vectra-detection-note-remove

Input

Argument Name Description Required
detection_id Specify the ID of the detection. Required
note_id Specify the ID of the note. Required

Context Output

There is no context output for this command.

Command Example

!vectra-detection-note-remove detection_id=1 note_id=1

Context Example

{}

Human Readable Output

The note has been successfully removed from the detection

vectra-entity-unresolved-priority-reset


Update the unresolved priority of an entity to false.

Base Command

vectra-entity-unresolved-priority-reset

Input

Argument Name Description Required
entity_id Specify the ID of the entity.

Note: Users can get the entity ID by executing the “vectra-entity-list” command.
Required
entity_type Specify the type of the entity. Possible values are: account, host. Required

Context Output

Path Type Description
Vectra.Entity.id String An ID of the entity.
Vectra.Entity.type String The type of the entity.
Vectra.Entity.unresolved_priority Boolean An entity unresolved priority status.

Command Example

!vectra-entity-unresolved-priority-reset entity_id=1 entity_type=account

Context Example

{
    "Vectra": {
        "Entity": [
            {
                "id": "1",
                "type": "account",
                "unresolved_priority": false
            }
        ]
    }
}

Human Readable Output

The unresolved priority of the provided entity has been successfully changed as ‘false’

vectra-detection-investigation-status-update


Update the investigation status of the detection by detection ID(s).

Base Command

vectra-detection-investigation-status-update

Input

Argument Name Description Required
detection_ids Provide a list of detection IDs separated by comma or a single detection ID.

Note: Users can get the detection ID by executing the “vectra-detection-list” command.
Required
investigation_status Specify the investigation status. Possible values are: open, acknowledged, escalated, paused, closed, expired. Required

Context Output

Path Type Description
Vectra.Detection.id String The detection ID.
Vectra.Detection.investigation_status String The detection investigation status.

Command Example

!vectra-detection-investigation-status-update detection_ids=1 investigation_status=escalated

Context Example

{
    "Vectra": {
        "Detection": [
            {
                "id": "1",
                "investigation_status": "escalated"
            }
        ]
    }
}

Human Readable Output

The investigation Status for provided Detection ID(s) [‘1’] have been updated as escalated

vectra-detection-external-id-update


Update the external reference ID for the provided detection ID(s).

Base Command

vectra-detection-external-id-update

Input

Argument Name Description Required
detection_ids Provide a list of detection IDs separated by comma or a single detection ID.

Note: Users can get the detection ID by executing the “vectra-detection-list” command.
Required
external_reference_id Provide the external reference ID. Required

Context Output

Path Type Description
Vectra.Detection.id String The detection ID.
Vectra.Detection.external_reference_id String The external reference ID of the detection.

Command Example

!vectra-detection-external-id-update detection_ids=1 external_reference_id=12345

Context Example

{
    "Vectra": {
        "Detection": [
            {
                "id": "1",
                "external_reference_id": "12345"
            }
        ]
    }
}

Human Readable Output

The external reference ID for provided Detection ID(s) [‘1’] have been updated as 12345

vectra-entity-external-id-update


Update the external reference ID for the provided entity.

Base Command

vectra-entity-external-id-update

Input

Argument Name Description Required
entity_id Specify the ID of the entity.

Note: Users can get the entity ID by executing the “vectra-entity-list” command.
Required
entity_type Specify the type of the entity. Possible values are: account, host. Required
external_reference_id Provide the external reference ID. Required

Context Output

Path Type Description
Vectra.Entity.id String An ID of the entity.
Vectra.Entity.type String The type of the entity.
Vectra.Entity.external_reference_id String The external reference ID of the entity.

Command Example

!vectra-entity-external-id-update entity_id=1 entity_type=account external_reference_id=12345

Context Example

{
    "Vectra": {
        "Entity": [
            {
                "id": "1",
                "type": "account",
                "external_reference_id": "12345"
            }
        ]
    }
}

Human Readable Output

The external reference ID for provided Entity have been updated as 12345

vectra-detection-list


Returns a list of detections based on the specified filters.

Base Command

vectra-detection-list

Input

Argument Name Description Required
created_after Filter the detections by created on or after the specified time.

Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ.

For example: 01 March 2026, 01 Mar 2026 04:45:33, 2026-04-17T14:05:44Z.
Optional
created_before Filter the detections by created on or before the specified time.

Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ.

For example: 01 March 2026, 01 Mar 2026 04:45:33, 2026-04-17T14:05:44Z.
Optional
last_detected_after Filter the detections by last detected on or after the specified time.

Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ.

For example: 01 March 2026, 01 Mar 2026 04:45:33, 2026-04-17T14:05:44Z.
Optional
last_detected_before Filter the detections by last detected on or before the specified time.

Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ.

For example: 01 March 2026, 01 Mar 2026 04:45:33, 2026-04-17T14:05:44Z.
Optional
description Filter by description containing specified value. Optional
detection_name Filter by detection name. Optional
detection_type Filter by detection type. Optional
detection_category Filter by detections category. Possible values are: Command & Control, Botnet, Reconnaissance, Lateral Movement, Exfiltration, Info. Optional
include_info_category_detections Include the info category detections which are excluded by default. Possible values are: true, false. Default is true. Optional
close_reason Filter by close reason of the detection. Possible values are: benign, remediated. Optional
detection_state Filter by detection state. Possible values are: active, inactive, fixed. Optional
entity_type Filter by Entity type. Possible values are: account, host. Optional
tags Filter by detection tags. Comma-separated values supported. Optional
is_triaged Filter by detection triage status. Possible values are: true, false. Default is false. Optional
page Provide page number to retrieve. Default is 1. Optional
page_size Provide a number of results per page. Default is 50. Optional

Context Output

Path Type Description
Vectra.Detection.id Number A unique identifier for the detection.
Vectra.Detection.assigned_date Date The date when the detection was assigned.
Vectra.Detection.assigned_to String The email or user to whom the detection is assigned.
Vectra.Detection.certainty Number The certainty level associated with the detection.
Vectra.Detection.created_timestamp Date The timestamp when the detection was created.
Vectra.Detection.custom_detection Unknown The custom detection configuration or settings.
Vectra.Detection.data_source.type String The type of data source for the detection.
Vectra.Detection.data_source.connection_name String The name of the connection used for data ingestion.
Vectra.Detection.data_source.connection_id String A unique identifier for the data source connection.
Vectra.Detection.description String The description of the detection.
Vectra.Detection.detection String The name of the detection.
Vectra.Detection.detection_category String The category of the detection.
Vectra.Detection.detection_type String The type of the detection.
Vectra.Detection.detection_url String The URL to access the detection details.
Vectra.Detection.filtered_by_ai Boolean Indicates if the detection was filtered by AI.
Vectra.Detection.filtered_by_rule Boolean Indicates if the detection was filtered by a rule.
Vectra.Detection.filtered_by_user Boolean Indicates if the detection was filtered by a user.
Vectra.Detection.first_timestamp Date The first timestamp when the detection was observed.
Vectra.Detection.grouped_details.role String The role associated with the detection group.
Vectra.Detection.grouped_details.last_timestamp Date The last timestamp of the detection group.
Vectra.Detection.groups.id Number A unique identifier for the group.
Vectra.Detection.groups.name String The name of the group.
Vectra.Detection.groups.description String The description of the group.
Vectra.Detection.groups.type String The type of the group.
Vectra.Detection.groups.last_modified Date The timestamp when the group was last modified.
Vectra.Detection.groups.last_modified_by String The email or user who last modified the group.
Vectra.Detection.is_custom_model Boolean Indicates if the detection uses a custom model.
Vectra.Detection.is_marked_custom Boolean Indicates if the detection is marked as custom.
Vectra.Detection.is_triaged Boolean Indicates if the detection has been triaged.
Vectra.Detection.last_timestamp Date The last timestamp when the detection was observed.
Vectra.Detection.note String A note associated with the detection.
Vectra.Detection.note_modified_by String The email or user who modified the note.
Vectra.Detection.note_modified_timestamp Date The timestamp when the note was last modified.
Vectra.Detection.notes.created_by String The email or user who created the note.
Vectra.Detection.notes.date_created Date The date when the note was created.
Vectra.Detection.notes.date_modified Date The date when the note was modified.
Vectra.Detection.notes.id Number A unique identifier for the note.
Vectra.Detection.notes.modified_by String The email or user who modified the note.
Vectra.Detection.notes.note String The content of the note.
Vectra.Detection.reason String The reason for the detection state or triage action.
Vectra.Detection.sensor String The sensor identifier that detected the activity.
Vectra.Detection.sensor_name String The name of the sensor that detected the activity.
Vectra.Detection.src_account.id Number A unique identifier for the source account.
Vectra.Detection.src_account.name String The name of the source account.
Vectra.Detection.src_account.url String The URL to access the source account details.
Vectra.Detection.src_account.threat Number The threat level associated with the source account.
Vectra.Detection.src_account.certainty Number The certainty level associated with the source account.
Vectra.Detection.src_account.privilege_level Number The privilege level associated with the source account.
Vectra.Detection.src_account.privilege_category String The privilege category associated with the source account.
Vectra.Detection.src_host.id Number A unique identifier for the source host.
Vectra.Detection.src_host.name String The name of the source host.
Vectra.Detection.src_host.ip String The IP address of the source host.
Vectra.Detection.src_host.url String The URL to access the source host details.
Vectra.Detection.src_host.is_key_asset Boolean Indicates if the source host is a key asset.
Vectra.Detection.src_host.group.id Number A unique identifier for the source host group.
Vectra.Detection.src_host.group.name String The name of the source host group.
Vectra.Detection.src_host.group.description String The description of the source host group.
Vectra.Detection.src_host.group.type String The type of the source host group.
Vectra.Detection.src_host.group.last_modified Date The timestamp when the source host group was last modified.
Vectra.Detection.src_host.group.last_modified_by String The email or user who last modified the source host group.
Vectra.Detection.src_host.threat Number The threat level associated with the source host.
Vectra.Detection.src_host.certainty Number The certainty level associated with the source host.
Vectra.Detection.src_ip String The source IP address in the detection.
Vectra.Detection.src_groups.id Number A unique identifier for the source group.
Vectra.Detection.src_groups.name String The name of the source group.
Vectra.Detection.src_groups.description String The description of the source group.
Vectra.Detection.src_groups.type String The type of the source group.
Vectra.Detection.src_groups.last_modified Date The timestamp when the source group was last modified.
Vectra.Detection.src_groups.last_modified_by String The email or user who last modified the source group.
Vectra.Detection.dst_groups.id Number A unique identifier for the destination group.
Vectra.Detection.dst_groups.name String The name of the destination group.
Vectra.Detection.dst_groups.description String The description of the destination group.
Vectra.Detection.dst_groups.type String The type of the destination group.
Vectra.Detection.dst_groups.last_modified Date The timestamp when the destination group was last modified.
Vectra.Detection.dst_groups.last_modified_by String The email or user who last modified the destination group.
Vectra.Detection.state String The current state of the detection.
Vectra.Detection.summary.artifact Array The artifacts associated with the detection summary.
Vectra.Detection.summary.last_timestamp Date The last timestamp in the detection summary.
Vectra.Detection.summary.description String The description in the detection summary.
Vectra.Detection.summary.roles Array The roles associated with the detection summary.
Vectra.Detection.tags Array The tags associated with the detection.
Vectra.Detection.is_targeting_key_asset Boolean Indicates if the detection is targeting a key asset.
Vectra.Detection.threat Number The threat level of the detection.
Vectra.Detection.triage_rule_id Unknown A unique identifier for the triage rule applied to the detection.
Vectra.Detection.type String The type of the detection.
Vectra.Detection.url String The URL to access the detection details.

Command Example

!vectra-detection-list page=1 page_size=2

Context Example

{
    "Vectra": {
        "Detection": [
            {
                "summary": {
                    "app_name": "Exchange",
                    "operations": [
                        "Add-MailboxPermission"
                    ],
                    "src_ips": [
                        "10.0.0.1"
                    ],
                    "description": "This account performed Exchange operations that were unusual for the account."
                },
                "src_account": {
                    "id": 1001,
                    "name": "user@example.com",
                    "url": "https://example.vectra.ai/api/v3.5/accounts/1001",
                    "threat": 45,
                    "certainty": 60
                },
                "state": "active",
                "created_timestamp": "2026-01-15T10:30:00Z",
                "filtered_by_user": false,
                "type": "account",
                "detection_type": "M365 Risky Exchange Operation",
                "data_source": {
                    "type": "o365",
                    "connection_name": "M365-Production",
                    "connection_id": "abc123"
                },
                "filtered_by_rule": false,
                "detection": "M365 Risky Exchange Operation",
                "url": "https://example.vectra.ai/api/v3.5/detections/5001",
                "sensor": "abc123",
                "threat": 50,
                "is_custom_model": false,
                "is_triaged": false,
                "detection_category": "lateral_movement",
                "filtered_by_ai": false,
                "detection_url": "https://example.vectra.ai/api/v3.5/detections/5001",
                "last_timestamp": "2026-01-15T12:00:00Z",
                "first_timestamp": "2026-01-15T10:00:00Z",
                "certainty": 50,
                "is_marked_custom": false,
                "id": 5001,
                "sensor_name": "Vectra NDR",
                "is_targeting_key_asset": false,
                "grouped_details": [
                    {
                        "parameters": [
                            {
                                "data": [
                                    {
                                        "name": "Identity",
                                        "value": "mailbox@example.com"
                                    }
                                ],
                                "timestamp": "2026-01-15T11:30:00Z"
                            }
                        ],
                        "operation": "Add-MailboxPermission",
                        "behavior": "Mailbox management",
                        "user_type": "Admin",
                        "last_timestamp": "2026-01-15T12:00:00Z",
                        "src_ip": "10.0.0.1",
                        "app_name": "Exchange"
                    }
                ]
            },
            {
                "state": "active",
                "created_timestamp": "2026-01-15T09:00:00Z",
                "filtered_by_user": false,
                "type": "host",
                "detection_type": "Suspicious Domain",
                "groups": [
                    {
                        "id": 10,
                        "name": "Production Servers",
                        "description": "Production server subnet",
                        "type": "ip",
                        "last_modified": "2026-01-10T08:00:00Z",
                        "last_modified_by": "admin@example.com"
                    }
                ],
                "data_source": {
                    "type": "sensor",
                    "connection_name": "Network Sensor 1",
                    "connection_id": "xyz789"
                },
                "filtered_by_rule": false,
                "detection": "Suspicious Domain",
                "url": "https://example.vectra.ai/api/v3.5/detections/5002",
                "sensor": "xyz789",
                "threat": 30,
                "is_custom_model": false,
                "is_triaged": false,
                "detection_category": "command_and_control",
                "filtered_by_ai": false,
                "detection_url": "https://example.vectra.ai/api/v3.5/detections/5002",
                "src_ip": "10.0.1.50",
                "last_timestamp": "2026-01-15T09:45:00Z",
                "first_timestamp": "2026-01-15T09:00:00Z",
                "src_host": {
                    "id": 2001,
                    "ip": "10.0.1.50",
                    "name": "workstation-01",
                    "url": "https://example.vectra.ai/api/v3.5/hosts/2001",
                    "is_key_asset": false,
                    "groups": [
                        {
                            "id": 10,
                            "name": "Production Servers",
                            "description": "Production server subnet",
                            "last_modified": "2026-01-10T08:00:00Z",
                            "last_modified_by": "admin@example.com",
                            "type": "ip"
                        }
                    ],
                    "threat": 35,
                    "certainty": 40
                },
                "certainty": 25,
                "is_marked_custom": false,
                "id": 5002,
                "sensor_name": "Network Sensor 1",
                "is_targeting_key_asset": false,
                "grouped_details": [
                    {
                        "protocol": "dns",
                        "last_timestamp": "2026-01-15T09:45:00Z",
                        "grouping_field": "last_timestamp",
                        "response_code": "NXDomain",
                        "target_domains": [
                            "suspicious-domain.example"
                        ],
                        "dst_ips": [
                            "8.8.8.8"
                        ]
                    }
                ],
                "summary": {
                    "num_failures": 5,
                    "num_successes": 0,
                    "num_sessions": 10
                }
            }
        ]
    }
}

Human Readable Output

Detections Table (Showing Page 1 out of 100)

ID Detection Name Detection Type Account Name Host Name Src IP Threat Score Certainty Score Number Of Events State Last Timestamp
5001 M365 Risky Exchange Operation M365 Risky Exchange Operation user@example.com     50 50 0 active 2026-01-15T12:00:00Z
5002 Suspicious Domain Suspicious Domain   workstation-01 10.0.1.50 30 25 0 active 2026-01-15T09:45:00Z

vectra-investigation-query-send


Submit an investigation query and receive a request ID for retrieving results.

Base Command

vectra-investigation-query-send

Input

Argument Name Description Required
query Provide an investigation query in the supported query language. Required
version Specify the version of the query language. Optional

Context Output

Path Type Description
Vectra.Investigation.request_id String The unique identifier for the query request. Use this to retrieve results.
Vectra.Investigation.searchable_range.searchable_days_allowed Number A Maximum number of days of data that can be searched.

Command Example

!vectra-investigation-query-send query="SELECT * FROM detections" version=v1

Context Example

{
    "Vectra": {
        "Investigation": {
            "request_id": "b57d7a27-28ad-4c0c-b28a-0e7b3",
            "searchable_range": {
                "searchable_days_allowed": 14
            }
        }
    }
}

Human Readable Output

The Vectra investigation has started. You can view the results by executing the below command

!vectra-investigation-result-get id=b57d7a27-28ad-4c0c-b28a-0e7b3

vectra-investigation-result-get


Retrieve the results of a previously submitted investigation query using the request ID.

Base Command

vectra-investigation-result-get

Input

Argument Name Description Required
id Provide the unique request ID of investigation. Required
page Provide page number to retrieve. Default is 1. Optional
page_size Provide a number of results per page to retrieve. Default is 50. Optional

Context Output

Path Type Description
Vectra.Investigation.request_id String The unique identifier for the query request.
Vectra.Investigation.data Unknown An array of query results.
Vectra.Investigation.meta.query_status String The status of the query.
Vectra.Investigation.meta.num_rows_available Number Total rows returned by the query.
Vectra.Investigation.meta.page Number The current page number.
Vectra.Investigation.meta.page_size Number The rows returned on this page.
Vectra.Investigation.meta.estimated_file_size_bytes Number The estimated size of the full result set in bytes.
Vectra.Investigation.meta.columns Unknown An array of tuples describing the result schema.

Command Example

!vectra-investigation-result-get id=b57d7a27-28ad-4c0c-b28a-0e7b3

Context Example

{
    "Vectra": {
        "Investigation": {
            "request_id": "b57d7a27-28ad-4c0c-b28a-0e7b3",
            "meta": {
                "page": 1,
                "page_size": 50,
                "estimated_file_size_bytes": 0,
                "num_rows_available": 0,
                "query_status": "SUCCESS",
                "columns": [
                    [
                        "timestamp",
                        [
                            {
                                "type": "timestamp"
                            },
                            ""
                        ]
                    ],
                    [
                        "orig_h",
                        [
                            {
                                "type": "string"
                            },
                            ""
                        ]
                    ],
                    [
                        "resp_h",
                        [
                            {
                                "type": "string"
                            },
                            ""
                        ]
                    ],
                    [
                        "resp_p",
                        [
                            {
                                "type": "number"
                            },
                            ""
                        ]
                    ]
                ]
            }
        }
    }
}

Human Readable Output

Investigation Result for Request ID: b57d7a27-28ad-4c0c-b28a-0e7b3

Query Status Page Number Page size Total Rows File Size (bytes) Columns
SUCCESS 1 50 0 0 - values: timestamp, [{‘type’: ‘timestamp’}, ‘’]
- values: orig_h, [{‘type’: ‘string’}, ‘’]
- values: resp_h, [{‘type’: ‘string’}, ‘’]
- values: resp_p, [{‘type’: ‘number’}, ‘’]

Investigation Results Data

No entries.

Configuration parameters

  • server_url — Server URL (required)
  • credentials — Client ID (required)
  • isFetch — Fetch incidents
  • max_fetch — Max Fetch
  • first_fetch — First Fetch Time
  • entity_types — Entity Types
  • only_prioritized_detections — Create Incidents for Prioritized Detections
  • only_escalated_detections — Create Incidents for Escalated Detections
  • mirror_direction — Mirroring Direction
  • note_tag — Mirror tag for notes
  • open_detection_on_incident_reopen — Open Detection on Incident Reopen
  • detection_status_for_reopen — Detection Status for Incident Reopen
  • close_detection_on_incident_closure — Close Detection on Incident Closure
  • close_reason_of_detection — Detection Close Reason for Incident Closure
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (36)

  • vectra-assignment-list

    Returns a list of all assignments.

  • vectra-detection-describe

    Returns a list of detections for the specified detection ID(s).

  • vectra-detection-external-id-update

    Update the external reference ID for the provided detection ID(s).

  • vectra-detection-investigation-status-update

    Update the investigation status of the detection by detection ID(s).

  • vectra-detection-list

    Returns a list of detections based on the specified filters.

  • vectra-detection-note-add

    Add a note to the detection.

  • vectra-detection-note-list

    Returns a list of notes for a specified detection.

  • vectra-detection-note-remove

    Remove a note from the detection.

  • vectra-detection-note-update

    Update a note in the detection.

  • vectra-detection-pcap-download

    Download pcap of the detection.

  • vectra-detection-tag-add

    Add tags to a detection.

  • vectra-detection-tag-list

    Returns a list of tags for a specified detection.

  • vectra-detection-tag-remove

    Remove tags from the detection.

  • vectra-detections-mark-asclosed

    Mark detections as closed with provided detection IDs in the argument.

  • vectra-detections-mark-asopen

    Open detections with provided detection IDs in the argument.

  • vectra-entity-assignment-add

    Add an assignment for the entity.

  • vectra-entity-assignment-update

    Update an assignment in the entity.

  • vectra-entity-describe

    Describes an entity by ID.

  • vectra-entity-detection-list

    Returns a list of detections for a specified entity.

  • vectra-entity-detections-mark-asclosed

    Mark the detections of the entity as closed with the provided entity ID in the argument.

  • vectra-entity-external-id-update

    Update the external reference ID for the provided entity.

  • vectra-entity-list

    Returns a list of entities.

  • vectra-entity-note-add

    Add a note to the entity.

  • vectra-entity-note-list

    Returns a list of notes for a specified entity.

  • vectra-entity-note-remove

    Remove a note from the entity.

  • vectra-entity-note-update

    Update a note in the entity.

  • vectra-entity-tag-add

    Add tags in the entity.

  • vectra-entity-tag-list

    Returns a list of tags for a specified entity.

  • vectra-entity-tag-remove

    Remove tags from the entity.

  • vectra-entity-unresolved-priority-reset

    Update the unresolved priority of an entity to false.

  • vectra-group-assign

    Assign members to the specified group.

  • vectra-group-list

    Returns a list of all groups.

  • vectra-group-unassign

    Unassign members from the specified group.

  • vectra-investigation-query-send

    Submit an investigation query and receive a request ID for retrieving results.

  • vectra-investigation-result-get

    Retrieve the results of a previously submitted investigation query using the request ID.

  • vectra-user-list

    Returns a list of users.

from pathlib import Path
import pytest
from requests.models import Response  # type: ignore[import]
from urllib.parse import urlencode

import demistomock as demisto
from CommonServerPython import *  # noqa # pylint: disable=unused-wildcard-import

import VectraRUXEventsDetections
from VectraRUXEventsDetections import (
    VectraEventsDetectionsClient,
    fetch_incidents,
    test_module as module_function,
    validate_fetch_params,
    map_severity,
    get_modified_remote_data_command,
    get_remote_data_command,
    update_remote_system_command,
    vectra_user_list_command,
    vectra_entity_list_command,
    vectra_entity_describe_command,
    vectra_entity_detection_list_command,
    vectra_detection_describe_command,
    vectra_entity_note_list_command,
    vectra_entity_note_add_command,
    vectra_entity_note_update_command,
    vectra_entity_note_remove_command,
    vectra_entity_tag_add_command,
    vectra_entity_tag_remove_command,
    vectra_entity_tag_list_command,
    vectra_assignment_list_command,
    vectra_entity_assignment_add_command,
    vectra_entity_assignment_update_command,
    vectra_detection_pcap_download_command,
    vectra_group_list_command,
    vectra_group_assign_command,
    vectra_group_unassign_command,
    vectra_entity_detections_mark_asclosed_command,
    vectra_detections_mark_asclosed_command,
    vectra_detections_mark_asopen_command,
    vectra_detection_tag_list_command,
    vectra_detection_tag_add_command,
    vectra_detection_tag_remove_command,
    vectra_detection_note_list_command,
    vectra_detection_note_add_command,
    vectra_detection_note_remove_command,
    vectra_detection_note_update_command,
    vectra_entity_unresolved_priority_reset_command,
    vectra_detection_investigation_status_update_command,
    vectra_detection_external_id_update_command,
    vectra_entity_external_id_update_command,
    vectra_detection_list_command,
    vectra_investigation_query_send_command,
    vectra_investigation_result_get_command,
    ERRORS,
    VALID_ENTITY_TYPES,
    VALID_DETECTION_STATUS,
    ENDPOINTS,
    VALID_CLOSE_REASON,
    VALID_ENTITY_TYPE,
    VALID_ENTITY_STATE,
    DETECTION_CATEGORY_TO_ARG,
    VALID_GROUP_TYPE,
    VALID_IMPORTANCE_VALUE,
    USER_ROLE_MAPPING,
    VALID_BOOL_VALUES,
)

# Constants
TEST_DATA_DIR = Path(__file__).parent / "test_data"
BASE_URL = "https://serverurl.com"


# Helper Functions
def util_load_json(path):
    with open(path, encoding="utf-8") as f:
        return json.loads(f.read())


@pytest.fixture
def client(mocker):
    mocker.patch.object(VectraEventsDetectionsClient, "_generate_tokens", return_value="test_token")
    return VectraEventsDetectionsClient(BASE_URL, "client_id", "client_secret_key", verify=False, proxy=False)


def add_params_in_url(base_url: str, params: dict):
    encoded_params = urlencode(params)

    base_url = f"{base_url}?{encoded_params}"
    return base_url


def test_generate_tokens(requests_mock):
    """
    Given
    - Mocked response for generating access tokens.
    - VectraEventsDetectionsClient instance.

    When
    - Calling the `_generate_tokens` method.

    Then
    - Ensure the generated access token matches the expected access token.
    """
    # Set up
    access_token = "access_token"
    refresh_token = "refresh_token"
    response_data = {"access_token": access_token, "refresh_token": refresh_token}
    requests_mock.post(os.path.join(BASE_URL, ENDPOINTS["AUTH_ENDPOINT"]), json=response_data, status_code=200)
    client = VectraEventsDetectionsClient(BASE_URL, "client_id", "client_secret_key", verify=True, proxy=False)
    token = client._generate_tokens()
    assert token == access_token


def test_generate_tokens_failure(requests_mock):
    """
    Given
    - Mocked failed response for generating access tokens.
    - VectraEventsDetectionsClient instance.

    When
    - Calling the `_generate_tokens` method.

    Then
    - Ensure the method raises an exception.
    """
    access_token = "access_token"
    refresh_token = "refresh_token"
    response_data = {"access_token": access_token, "refresh_token": refresh_token}
    requests_mock.post(os.path.join(BASE_URL, ENDPOINTS["AUTH_ENDPOINT"]), json=response_data, status_code=500)
    client = VectraEventsDetectionsClient(BASE_URL, "client_id", "client_secret_key", verify=True, proxy=False)

    # Call the method
    with pytest.raises(Exception):
        client._generate_tokens()


def test_generate_access_token_using_refresh_token(requests_mock, mocker):
    """
    Given
    - Mocked response for generating access token using refresh token.
    - VectraEventsDetectionsClient instance.
    - Mocked `get_integration_context` method.

    When
    - Calling the `_generate_access_token_using_refresh_token` method.

    Then
    - Ensure the generated access token matches the expected access token.
    """
    # Set up
    access_token = "access_token"
    response_data = {
        "access_token": access_token,
    }
    requests_mock.post(os.path.join(BASE_URL, ENDPOINTS["AUTH_ENDPOINT"]), json=response_data, status_code=200)
    client = VectraEventsDetectionsClient(BASE_URL, "client_id", "client_secret_key", verify=True, proxy=False)
    mocker.patch("CommonServerPython.get_integration_context", return_value={"refresh_token": "refresh_token"})
    token = client._generate_access_token_using_refresh_token()
    assert token == access_token


def test_generate_access_token_using_refresh_token_failure(requests_mock):
    """
    Given
    - Mocked failed response for generating access token using refresh token.
    - VectraClient instance.

    When
    - Calling the `_generate_access_token_using_refresh_token` method.

    Then
    - Ensure the method raises an exception.
    """
    access_token = "access_token"
    refresh_token = "refresh_token"
    response_data = {"access_token": access_token, "refresh_token": refresh_token}
    requests_mock.post(os.path.join(BASE_URL, ENDPOINTS["AUTH_ENDPOINT"]), json=response_data, status_code=500)
    client = VectraEventsDetectionsClient(BASE_URL, "client_id", "client_secret_key", verify=True, proxy=False)

    # Call the method
    with pytest.raises(Exception):
        client._generate_access_token_using_refresh_token()


def test_generate_access_token_using_refresh_token_401_status_code(requests_mock, mocker, client):
    """
    Given:
    - A client object.
    - A mocked HTTP POST request to the token endpoint with a status code of 401.
    - A mocked '_generate_tokens' method that raises an exception.

    When:
    - Calling the '_generate_access_token_using_refresh_token' method.

    Then:
    - Assert that an exception is raised.
    - Assert that the '_generate_tokens' method is called once.
    """
    requests_mock.post(os.path.join(BASE_URL, ENDPOINTS["AUTH_ENDPOINT"]), status_code=401)
    generate_token = mocker.patch.object(client, "_generate_tokens", side_effect=Exception())

    # Call the method
    with pytest.raises(Exception):
        client._generate_access_token_using_refresh_token()

    generate_token.assert_called_once()


def test_http_request_with_valid_parameters(mocker, client):
    """
    Given:
    - A mocked `_http_request` method.
    - A client object.

    When:
    - Making a request with valid parameters.

    Then:
    - Assert that the response status code is 200 (indicating a successful request).
    """
    response = Response()
    response.status_code = 200
    mocker.patch.object(BaseClient, "_http_request", return_value=response)

    response = client.http_request(method="GET", url_suffix="/test")

    assert response.status_code == 200


def test_http_request_with_invalid_parameters(mocker, client):
    """
    Given:
    - A mocked `_http_request` method that raises an exception.
    - A client object.

    When:
    - Making a request with invalid parameters.

    Then:
    - Assert that the raised exception matches the expected exception.
    """
    # Mock the `_http_request` method to raise an exception.
    mocker.patch.object(BaseClient, "_http_request", side_effect=Exception())

    # Make a request with invalid parameters.
    with pytest.raises(Exception):
        client.http_request(method="GET", url_suffix="/test")


def test_http_request_with_401_status_code(mocker, client):
    """
    Given:
    - A mocked `_http_request` method that returns a response with a 401 status code.
    - A client object.

    When:
    - Making a request that results in a 401 status code.

    Then:
    - Assert that an exception is raised.
    - Assert that the `_generate_access_token_using_refresh_token` method is called once.
    """
    response = Response()
    response.status_code = 401
    mocker.patch.object(BaseClient, "_http_request", return_value=response)
    generate_token = mocker.patch.object(client, "_generate_access_token_using_refresh_token", side_effect=Exception())
    with pytest.raises(Exception):
        client.http_request(method="GET", url_suffix="/test")
    generate_token.assert_called_once()


def test_list_events_detections_request(mocker, client):
    """
    Given:
    - A client object.
    - Mocked http_request method.

    When:
    - Calling list_events_detections_request with parameters.

    Then:
    - Assert that the method calls http_request with correct parameters.
    """
    mock_response: dict = {"events": [], "next_checkpoint": None, "remaining_count": 0}
    mocker.patch.object(client, "http_request", return_value=mock_response)

    params = {
        "type": "host,account",
        "status": "open,escalated",
        "unresolved_priority": True,
        "limit": 50,
        "event_timestamp_gte": "2025-12-21T00:00:00Z",
        "ordering": "event_timestamp",
    }

    result = client.list_events_detections_request(params=params)

    assert result == mock_response
    client.http_request.assert_called_once_with(
        method="GET",
        url_suffix=ENDPOINTS["EVENTS_DETECTIONS_ENDPOINT"],
        params=params,
        response_type="json",
    )


def test_list_events_detections_request_with_from_checkpoint(mocker, client):
    """
    Given:
    - A client object.
    - Mocked http_request method.
    - Parameters including 'from' checkpoint.

    When:
    - Calling list_events_detections_request with 'from' parameter.

    Then:
    - Assert that the 'from' parameter is correctly added to params.
    """
    mock_response = {"events": [], "next_checkpoint": "checkpoint123", "remaining_count": 0}
    mocker.patch.object(client, "http_request", return_value=mock_response)

    params = {
        "type": "host",
        "limit": 50,
        "from": "checkpoint123",
    }

    result = client.list_events_detections_request(params=params)

    assert result == mock_response
    call_args = client.http_request.call_args
    assert call_args[1]["params"]["from"] == "checkpoint123"


def test_test_module_success(mocker, client):
    """
    Given
    - VectraRUXEventsDetections test module

    When
    - mock the demisto params.
    - mock the VectraEventsDetectionsClient's generate_tokens.
    - mock the VectraEventsDetectionsClient.
    - mock the VectraEventsDetectionsClient's list_events_detections_request.

    Then
    - run the test_module command using the Client
    Validate The response is ok.
    """
    mocker.patch.object(demisto, "params", return_value={"isFetch": False})
    mock_response: dict = {"events": [], "next_checkpoint": None, "remaining_count": 0}
    mocker.patch.object(client, "list_events_detections_request", return_value=mock_response)
    result = module_function(client, {})

    assert result == "ok"


def test_test_module_with_fetch_enabled(mocker, client):
    """
    Given
    - VectraRUXEventsDetections test module and fetch incident is enabled

    When
    - mock the VectraEventsDetectionsClient's generate_tokens.
    - mock the VectraEventsDetectionsClient.
    - mock the fetch_incidents function.

    Then
    - run the test_module command using the Client
    Validate The response is ok.
    """
    params = {
        "isFetch": True,
        "first_fetch": "1 hour",
        "max_fetch": "50",
        "entity_types": "Host,Account",
        "unresolved_priority": "Yes",
        "detection_status": "Escalated",
    }
    mocker.patch.object(demisto, "params", return_value=params)
    mocker.patch("VectraRUXEventsDetections.fetch_incidents", return_value=([], {}))
    result = module_function(client, params)

    assert result == "ok"


def test_validate_fetch_params_success():
    """
    Given:
    - Valid fetch parameters and last_run data.

    When:
    - Calling validate_fetch_params with valid parameters.

    Then:
    - Assert that the validated parameters are correctly formatted.
    """
    params = {
        "first_fetch": "2025-12-25T00:00:00Z",
        "max_fetch": 50,
        "entity_types": "Host,Account",
        "only_prioritized_detections": "Yes",
        "only_escalated_detections": "No",
    }
    last_run = {"event_timestamp": "2025-12-21T00:00:00Z", "from": "", "was_fetched": []}

    result = validate_fetch_params(params, last_run)

    assert result["type"] == "account,host"
    assert result["investigation_status"] == "acknowledged,escalated,open,paused"
    assert result["unresolved_priority"] is True
    assert result["limit"] == 50
    assert result["event_timestamp_gte"] == last_run.get("event_timestamp")


@pytest.mark.parametrize("max_fetch", [201, -1, 0])
def test_validate_fetch_params_invalid_max_fetch(max_fetch):
    """
    Given:
    - Invalid max_fetch parameter.

    When:
    - Calling validate_fetch_params with invalid max_fetch.

    Then:
    - Assert that ValueError is raised.
    """
    params = {
        "first_fetch": "1 hour",
        "max_fetch": max_fetch,
    }
    last_run: dict = {}

    with pytest.raises(ValueError) as exception:
        validate_fetch_params(params, last_run, is_test=True)

    assert ERRORS["INVALID_MAX_FETCH"].format(max_fetch) in str(exception.value)


def test_validate_fetch_params_max_fetch_exceeds_limit(mocker):
    """
    Given:
    - max_fetch parameter exceeding MAX_FETCH limit.

    When:
    - Calling validate_fetch_params with max_fetch > 200.

    Then:
    - Assert that max_fetch is capped at 200.
    """
    params = {
        "first_fetch": "1 hour",
        "max_fetch": 300,
    }
    last_run: dict = {}
    mocker.patch.object(demisto, "debug")

    result = validate_fetch_params(params, last_run)

    assert result["limit"] == 200


def test_validate_fetch_params_invalid_entity_type():
    """
    Given:
    - Invalid entity_type parameter.

    When:
    - Calling validate_fetch_params with invalid entity_type.

    Then:
    - Assert that ValueError is raised.
    """
    params = {
        "first_fetch": "1 hour",
        "max_fetch": 50,
        "entity_types": "InvalidType",
    }
    last_run: dict = {}

    with pytest.raises(ValueError) as exception:
        validate_fetch_params(params, last_run, is_test=True)

    assert ERRORS["INVALID_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPES)) in str(exception.value)


def test_validate_fetch_params_invalid_entity_type_no_test_module():
    """
    Given:
    - Invalid entity_type parameter.

    When:
    - Calling validate_fetch_params with invalid entity_type.

    Then:
    - Assert that ValueError is raised.
    """
    params = {
        "first_fetch": "1 hour",
        "max_fetch": 50,
        "entity_types": "InvalidType",
    }
    last_run: dict = {}

    valid_params = validate_fetch_params(params, last_run)

    assert valid_params["type"] == "account,host"


def test_validate_fetch_params_invalid_detection_status():
    """
    Given:
    - Valid parameters (detection_status is no longer validated as it's not used).

    When:
    - Calling validate_fetch_params.

    Then:
    - Assert that default detection statuses are used.
    """
    params = {
        "first_fetch": "1 hour",
        "max_fetch": 50,
        "entity_types": "Host",
    }
    last_run: dict = {}

    result = validate_fetch_params(params, last_run, is_test=True)

    assert result["investigation_status"] == "acknowledged,escalated,open,paused"


def test_validate_fetch_params_invalid_detection_status_no_test_module():
    """
    Given:
    - Valid parameters (detection_status is no longer validated as it's not used).

    When:
    - Calling validate_fetch_params.

    Then:
    - Assert that default detection statuses are used.
    """
    params = {
        "first_fetch": "1 hour",
        "max_fetch": 50,
        "entity_types": "Host",
    }
    last_run: dict = {}

    valid_params = validate_fetch_params(params, last_run)

    assert valid_params["investigation_status"] == "acknowledged,escalated,open,paused"


def test_validate_fetch_params_with_from_checkpoint():
    """
    Given:
    - Valid parameters with from checkpoint in last_run.

    When:
    - Calling validate_fetch_params with from checkpoint.

    Then:
    - Assert that 'from' parameter is included in result.
    """
    params = {
        "first_fetch": "1 hour",
        "max_fetch": 50,
        "only_prioritized_detections": False,
        "only_escalated_detections": False,
    }
    last_run = {
        "event_timestamp": "2025-12-21T00:00:00Z",
        "from": "checkpoint123",
        "was_fetched": [],
        "selected_statuses": "acknowledged,escalated,open,paused",
        "selected_types": "account,host",
        "unresolved_priority": "",
    }

    result = validate_fetch_params(params, last_run)

    assert result.get("from") == "checkpoint123"


@pytest.mark.parametrize(
    "urgency_score,expected_severity",
    [
        (90, 4),
        (80, 3),
        (70, 3),
        (60, 3),
        (50, 2),
        (40, 2),
        (30, 1),
        (20, 1),
        (10, 1),
        (0, 0.5),
    ],
)
def test_map_severity(urgency_score, expected_severity):
    """
    Given:
    - Different severity values.

    When:
    - Calling map_severity with various severity values.

    Then:
    - Assert that severity is correctly mapped to incident severity.
    """
    # map_severity logic: > 8 -> 4, > 5 -> 3, > 3 -> 2, else -> 1
    assert map_severity(urgency_score) == expected_severity


def test_fetch_incidents_first_run(mocker, client):
    """
    Given:
    - A client object.
    - A mocked 'getLastRun' method that returns an empty dictionary.
    - A mocked 'list_events_detections_request' method that returns sample events data.

    When:
    - Fetching incidents using the 'fetch_incidents' function with no previous run.

    Then:
    - Assert that the number of fetched incidents matches the number of events.
    - Assert incident properties are correctly set.
    """
    events_data = util_load_json(f"{TEST_DATA_DIR}/events_detections_response.json")
    mocker.patch.object(demisto, "getLastRun", return_value={})
    mocker.patch.object(demisto, "setLastRun")
    mocker.patch.object(client, "list_events_detections_request", return_value=events_data)

    params = {
        "first_fetch": "1 hour",
        "max_fetch": 50,
        "entity_types": "Host,Account",
        "unresolved_priority": "Yes",
        "detection_status": "Open,Escalated",
    }

    incidents, next_run = fetch_incidents(client, params, {})

    assert len(incidents) == 2
    assert incidents[0]["name"] == "Vectra RUX: Dummy_Category_1 - Dummy Detection One - dummy-entity-one"
    assert incidents[0]["occurred"] == events_data.get("events")[0].get("event_timestamp", "")
    assert incidents[0]["severity"] == 2  # severity 4 maps to 2 (Medium)
    assert "rawJSON" in incidents[0]
    assert next_run["event_timestamp"] == events_data.get("events")[-1].get("event_timestamp", "")
    assert next_run["from"] == 200002 or next_run["from"] == "200002"  # next_checkpoint can be int or str


def test_fetch_incidents_with_last_run(mocker, client):
    """
    Given:
    - A client object.
    - A mocked 'getLastRun' method that returns last run data.
    - A mocked 'list_events_detections_request' method that returns sample events data.

    When:
    - Fetching incidents using the 'fetch_incidents' function with previous run data.

    Then:
    - Assert that incidents are fetched using the last run checkpoint.
    """
    events_data = util_load_json(f"{TEST_DATA_DIR}/events_detections_response.json")
    last_run = {
        "event_timestamp": "2025-12-21T00:00:00Z",
        "from": "checkpoint123",
        "was_fetched": [],
        "selected_statuses": "acknowledged,escalated,open,paused",
        "selected_types": "account,host",
        "unresolved_priority": "",
    }
    mocker.patch.object(demisto, "getLastRun", return_value=last_run)
    mocker.patch.object(demisto, "setLastRun")
    mocker.patch.object(client, "list_events_detections_request", return_value=events_data)

    params = {
        "first_fetch": "1 hour",
        "max_fetch": 50,
    }

    incidents, _ = fetch_incidents(client, params, last_run)

    assert len(incidents) == 2
    # Verify that the API was called with the from checkpoint
    call_args = client.list_events_detections_request.call_args
    assert call_args[1]["params"]["from"] == "checkpoint123"


def test_fetch_incidents_duplicate_detection_id(mocker, client):
    """
    Given:
    - A client object.
    - Events with duplicate detection IDs (already fetched).

    When:
    - Fetching incidents where some detections were already fetched.

    Then:
    - Assert that duplicate detections are not included in incidents.
    """
    events_data = util_load_json(f"{TEST_DATA_DIR}/events_detections_response.json")
    last_run = {
        "event_timestamp": "2025-12-21T00:00:00Z",
        "from": "",
        "was_fetched": [11111],  # First detection ID already fetched
    }
    mocker.patch.object(demisto, "getLastRun", return_value=last_run)
    mocker.patch.object(demisto, "setLastRun")
    mocker.patch.object(client, "list_events_detections_request", return_value=events_data)

    params = {
        "first_fetch": "1 hour",
        "max_fetch": 50,
    }

    incidents, next_run = fetch_incidents(client, params, last_run)

    assert len(incidents) == 1  # Only second detection should be fetched
    assert incidents[0]["rawJSON"]  # Verify rawJSON contains the event data
    assert next_run["was_fetched"] == [11111, 22222]


def test_fetch_incidents_empty_response(mocker, client):
    """
    Given:
    - A client object.
    - An empty events response.

    When:
    - Fetching incidents when no events are returned.

    Then:
    - Assert that no incidents are created.
    - Assert that last_run is updated with current time.
    """
    empty_response: dict = {"events": [], "next_checkpoint": None, "remaining_count": 0}
    last_run = {
        "event_timestamp": "2025-12-21T00:00:00Z",
        "from": "",
        "was_fetched": [],
    }
    mocker.patch.object(demisto, "getLastRun", return_value=last_run)
    mocker.patch.object(demisto, "setLastRun")
    mocker.patch.object(client, "list_events_detections_request", return_value=empty_response)

    params = {
        "first_fetch": "1 hour",
        "max_fetch": 50,
    }

    incidents, next_run = fetch_incidents(client, params, last_run)

    assert len(incidents) == 0
    assert "event_timestamp" in next_run
    assert next_run["from"] == ""


def test_fetch_incidents_with_test_flag(mocker, client):
    """
    Given:
    - A client object.
    - is_test flag set to True.

    When:
    - Fetching incidents with is_test=True.

    Then:
    - Assert that empty incidents and empty last_run are returned.
    """
    events_data = util_load_json(f"{TEST_DATA_DIR}/events_detections_response.json")
    mocker.patch.object(client, "list_events_detections_request", return_value=events_data)

    params = {
        "first_fetch": "1 hour",
        "max_fetch": 50,
    }

    incidents, next_run = fetch_incidents(client, params, {}, is_test=True)

    assert len(incidents) == 0
    assert next_run == {}


def test_fetch_incidents_severity_mapping(mocker, client):
    """
    Given:
    - A client object.
    - Events with different severity values.

    When:
    - Fetching incidents.

    Then:
    - Assert that severity is correctly mapped using map_severity function.
    """
    events_data = util_load_json(f"{TEST_DATA_DIR}/events_detections_response.json")
    mocker.patch.object(demisto, "getLastRun", return_value={})
    mocker.patch.object(demisto, "setLastRun")
    mocker.patch.object(client, "list_events_detections_request", return_value=events_data)

    params = {
        "first_fetch": "1 hour",
        "max_fetch": 50,
    }

    incidents, _ = fetch_incidents(client, params, {})

    # First event has severity 4 -> mapped to 2 (Medium)
    assert incidents[0]["severity"] == 2
    # Second event has severity 8 -> mapped to 3 (High)
    assert incidents[1]["severity"] == 3


def test_fetch_incidents_api_error(mocker, client):
    """
    Given:
    - A client object.
    - An API error when fetching events.

    When:
    - Fetching incidents when API returns an error.

    Then:
    - Assert that DemistoException is raised.
    """
    from CommonServerPython import DemistoException

    mocker.patch.object(demisto, "getLastRun", return_value={})
    mocker.patch.object(client, "list_events_detections_request", side_effect=DemistoException("API Error"))

    params = {
        "first_fetch": "1 hour",
        "max_fetch": 50,
    }

    with pytest.raises(DemistoException):
        fetch_incidents(client, params, {})


def test_fetch_incidents_with_next_checkpoint(mocker, client):
    """
    Given:
    - A client object.
    - Events response with next_checkpoint.

    When:
    - Fetching incidents with pagination checkpoint.

    Then:
    - Assert that next_run contains the next_checkpoint.
    """
    events_data = util_load_json(f"{TEST_DATA_DIR}/events_detections_response.json")
    mocker.patch.object(demisto, "getLastRun", return_value={})
    mocker.patch.object(demisto, "setLastRun")
    mocker.patch.object(client, "list_events_detections_request", return_value=events_data)

    params = {
        "first_fetch": "1 hour",
        "max_fetch": 50,
    }

    _, next_run = fetch_incidents(client, params, {})

    assert next_run["from"] == 200002 or next_run["from"] == "200002"  # next_checkpoint from response (can be int or str)


def test_validate_fetch_params_defaults():
    """
    Given:
    - Parameters with default values.

    When:
    - Calling validate_fetch_params with minimal parameters.

    Then:
    - Assert that default values are used correctly.
    """
    params = {
        "first_fetch": "1 hour",
        "max_fetch": 50,
    }
    last_run: dict = {}

    result = validate_fetch_params(params, last_run)

    assert result["investigation_status"] == "acknowledged,escalated,open,paused"  # Default detection status
    assert "unresolved_priority" not in result  # Default unresolved priority


def test_validate_fetch_params_comma_separated_values():
    """
    Given:
    - Parameters with comma-separated entity types.

    When:
    - Calling validate_fetch_params with comma-separated values.

    Then:
    - Assert that values are correctly parsed and joined.
    """
    params = {
        "first_fetch": "1 hour",
        "max_fetch": 50,
        "entity_types": "Host, Account",
        "only_prioritized_detections": False,
        "only_escalated_detections": False,
    }
    last_run: dict = {}

    result = validate_fetch_params(params, last_run)

    assert result["type"] == "account,host"
    assert result["investigation_status"] == "acknowledged,escalated,open,paused"


def test_validate_fetch_params_empty_detection_status_uses_default():
    """
    Given:
    - Parameters with default flags.

    When:
    - Calling validate_fetch_params.

    Then:
    - Assert that default detection status is used.
    """
    params = {
        "first_fetch": "1 hour",
        "max_fetch": 50,
    }
    last_run: dict = {}

    result = validate_fetch_params(params, last_run)

    assert result["investigation_status"] == "acknowledged,escalated,open,paused"  # Default


def test_validate_fetch_params_unresolved_priority_false():
    """
    Given:
    - Parameters with unresolved_priority set to "No".

    When:
    - Calling validate_fetch_params with unresolved_priority="No".

    Then:
    - Assert that unresolved_priority is False.
    """
    params = {
        "first_fetch": "1 hour",
        "max_fetch": 50,
        "only_prioritized_detections": False,
    }
    last_run: dict = {}

    result = validate_fetch_params(params, last_run)

    assert result.get("unresolved_priority") is None


def test_validate_fetch_params_uses_last_run_timestamp():
    """
    Given:
    - Parameters and last_run with event_timestamp.

    When:
    - Calling validate_fetch_params.

    Then:
    - Assert that last_run event_timestamp is used instead of first_fetch.
    """
    params = {
        "first_fetch": "2025-12-25T10:00:00Z",
        "max_fetch": 50,
    }
    last_run = {
        "event_timestamp": "2025-12-21T10:00:00Z",
        "from": "",
        "was_fetched": [],
    }

    result = validate_fetch_params(params, last_run)

    assert result["event_timestamp_gte"] == last_run.get("event_timestamp")


def test_main_function_test_module(mocker, client):
    """
    Given:
    - Mocked demisto.command() returning "test-module".

    When:
    - Calling main function with test-module command.

    Then:
    - Assert that test_module is called and returns "ok".
    """
    mocker.patch.object(demisto, "params", return_value={"isFetch": False})
    mocker.patch.object(demisto, "command", return_value="test-module")
    mocker.patch.object(demisto, "args", return_value={})
    mocker.patch.object(client, "list_events_detections_request", return_value={"events": []})
    mocker.patch("VectraRUXEventsDetections.VectraEventsDetectionsClient", return_value=client)
    mocker.patch("VectraRUXEventsDetections.return_results")

    VectraRUXEventsDetections.main()

    VectraRUXEventsDetections.return_results.assert_called_once_with("ok")  # type: ignore[attr-defined]


def test_main_function_fetch_incidents(mocker, client):
    """
    Given:
    - Mocked demisto.command() returning "fetch-incidents".

    When:
    - Calling main function with fetch-incidents command.

    Then:
    - Assert that fetch_incidents is called and incidents are set.
    """
    events_data = util_load_json(f"{TEST_DATA_DIR}/events_detections_response.json")
    mocker.patch.object(demisto, "params", return_value={"first_fetch": "1 hour", "max_fetch": 50})
    mocker.patch.object(demisto, "command", return_value="fetch-incidents")
    mocker.patch.object(demisto, "getLastRun", return_value={})
    mocker.patch.object(demisto, "setLastRun")
    mocker.patch.object(demisto, "incidents")
    mocker.patch.object(client, "list_events_detections_request", return_value=events_data)
    mocker.patch("VectraRUXEventsDetections.VectraEventsDetectionsClient", return_value=client)

    VectraRUXEventsDetections.main()

    demisto.incidents.assert_called_once()  # type: ignore[attr-defined]
    call_args = demisto.incidents.call_args[0][0]  # type: ignore[attr-defined]
    assert len(call_args) == 2


def test_main_function_invalid_command(mocker, client):
    """
    Given:
    - Mocked demisto.command() returning invalid command.

    When:
    - Calling main function with invalid command.

    Then:
    - Assert that NotImplementedError is raised.
    """
    mocker.patch.object(demisto, "params", return_value={})
    mocker.patch.object(demisto, "command", return_value="invalid-command")
    mocker.patch.object(demisto, "args", return_value={})
    mocker.patch("VectraRUXEventsDetections.VectraEventsDetectionsClient", return_value=client)
    mocker.patch("VectraRUXEventsDetections.return_error")

    VectraRUXEventsDetections.main()

    VectraRUXEventsDetections.return_error.assert_called_once()  # type: ignore[attr-defined]


def test_main_function_exception_handling(mocker, client):
    """
    Given:
    - Mocked demisto.command() that raises an exception.

    When:
    - Calling main function when an exception occurs.

    Then:
    - Assert that return_error is called with error message.
    """
    mocker.patch.object(demisto, "params", return_value={})
    mocker.patch.object(demisto, "command", return_value="test-module")
    mocker.patch.object(demisto, "args", return_value={})
    mocker.patch("VectraRUXEventsDetections.VectraEventsDetectionsClient", side_effect=Exception("Test error"))
    mocker.patch("VectraRUXEventsDetections.return_error")

    VectraRUXEventsDetections.main()

    VectraRUXEventsDetections.return_error.assert_called_once()  # type: ignore[attr-defined]
    assert "Test error" in str(VectraRUXEventsDetections.return_error.call_args[0][0])  # type: ignore[attr-defined]


@pytest.mark.parametrize("close_reason", ["benign", "remediated"])
def test_mark_detections_asclosed_command_valid_close(requests_mock, client, close_reason):
    """
    Tests mark_detection_as_closed_command with valid close reason.
    """

    response = {"_meta": {"level": "success", "message": f"Successfully closed detection as {close_reason}"}}
    status_response = {"message": {"success": ["Successfully updated detection statuses"]}, "_meta": {"level": "success"}}
    requests_mock.patch(os.path.join(BASE_URL, ENDPOINTS["CLOSE_DETECTIONS_ENDPOINT"]), json=response)
    requests_mock.patch(os.path.join(BASE_URL, ENDPOINTS["DETECTION_ENDPOINT"]), json=status_response)

    args = {"detection_ids": "123,234", "close_reason": close_reason}
    result = vectra_detections_mark_asclosed_command(client=client, args=args)

    assert result.outputs is None
    assert result.readable_output == f"##### The provided detection IDs have been successfully closed as {close_reason}."
    assert result.raw_response == response


@pytest.mark.parametrize(
    "args,error_msg",
    [
        ({}, ERRORS["REQUIRED_ARGUMENT"].format("detection_ids")),
        ({"detection_ids": "abc"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_ids", "abc")),
        ({"detection_ids": "0"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_ids", "0")),
        ({"detection_ids": "-5"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_ids", "-5")),
        ({"detection_ids": "123,abc,456"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_ids", "abc")),
        ({"detection_ids": "123"}, ERRORS["REQUIRED_ARGUMENT"].format("close_reason")),
        (
            {"detection_ids": "123", "close_reason": "invalid"},
            ERRORS["INVALID_ARG_VALUE"].format("close_reason", ", ".join(VALID_CLOSE_REASON)),
        ),
    ],
)
def test_mark_detections_asclosed_command_invalid_args(args, client, error_msg):
    """
    Tests mark_detections_asclosed_command with invalid arguments.
    """

    with pytest.raises(ValueError) as err:
        vectra_detections_mark_asclosed_command(client=client, args=args)

    assert str(err.value) == error_msg


def test_get_modified_remote_data_command_successful_retrieval(mocker, client):
    """
    Given:
    - A client object.
    - A mocked list_events_detections_request that returns events.

    When:
    - Calling the 'get_modified_remote_data_command' function with valid arguments.

    Then:
    - Assert that the function returns a GetModifiedRemoteDataResponse with modified incident IDs.
    """
    events_data = util_load_json(f"{TEST_DATA_DIR}/events_detections_response.json")
    # Set remaining_count to 0 to avoid infinite loop
    events_data["remaining_count"] = 0
    mocker.patch.object(client, "list_events_detections_request", return_value=events_data)

    args = {"lastUpdate": "2025-01-01T00:00:00+00:00"}
    result = get_modified_remote_data_command(client, args)

    assert result.modified_incident_ids is not None
    assert "11111" in result.modified_incident_ids
    assert "22222" in result.modified_incident_ids


def test_get_modified_remote_data_command_empty_response(mocker, client):
    """
    Given:
    - A client object.
    - A mocked list_events_detections_request that returns empty events.

    When:
    - Calling the 'get_modified_remote_data_command' function with valid arguments.

    Then:
    - Assert that the function returns a GetModifiedRemoteDataResponse with empty modified incident IDs.
    """
    empty_response: dict = {"events": [], "next_checkpoint": None, "remaining_count": 0}
    mocker.patch.object(client, "list_events_detections_request", return_value=empty_response)

    args = {"lastUpdate": "2025-01-01T00:00:00+00:00"}
    result = get_modified_remote_data_command(client, args)

    assert result.modified_incident_ids == []


def test_get_modified_remote_data_command_with_pagination(mocker, client):
    """
    Given:
    - A client object.
    - A mocked list_events_detections_request that returns events with remaining_count > 0 initially.

    When:
    - Calling the 'get_modified_remote_data_command' function with valid arguments.

    Then:
    - Assert that the function handles pagination and collects all modified incident IDs.
    """
    first_response = {
        "events": [{"detection_id": 11111, "event_timestamp": "2025-01-01T01:00:00Z"}],
        "next_checkpoint": "checkpoint123",
        "remaining_count": 1,
    }
    second_response: dict = {
        "events": [{"detection_id": 22222, "event_timestamp": "2025-01-01T02:00:00Z"}],
        "next_checkpoint": "checkpoint456",
        "remaining_count": 0,
    }
    mocker.patch.object(client, "list_events_detections_request", side_effect=[first_response, second_response])

    args = {"lastUpdate": "2025-01-01T00:00:00+00:00"}
    result = get_modified_remote_data_command(client, args)

    assert "11111" in result.modified_incident_ids
    assert "22222" in result.modified_incident_ids


def test_get_modified_remote_data_command_api_error(mocker, client):
    """
    Given:
    - A client object.
    - A mocked list_events_detections_request that raises a DemistoException.

    When:
    - Calling the 'get_modified_remote_data_command' function with valid arguments.

    Then:
    - Assert that the DemistoException is raised.
    """
    mocker.patch.object(client, "list_events_detections_request", side_effect=DemistoException("API Error"))

    args = {"lastUpdate": "2025-01-01T00:00:00+00:00"}

    with pytest.raises(DemistoException):
        get_modified_remote_data_command(client, args)


def test_get_modified_remote_data_command_filters_duplicates(mocker, client):
    """
    Given:
    - A client object.
    - A mocked list_events_detections_request that returns events with duplicate detection IDs.

    When:
    - Calling the 'get_modified_remote_data_command' function with valid arguments.

    Then:
    - Assert that duplicate incident IDs are filtered out.
    """
    response_with_duplicates = {
        "events": [
            {"detection_id": 11111, "event_timestamp": "2025-01-01T01:00:00Z"},
            {"detection_id": 11111, "event_timestamp": "2025-01-01T02:00:00Z"},
            {"detection_id": 22222, "event_timestamp": "2025-01-01T03:00:00Z"},
        ],
        "next_checkpoint": None,
        "remaining_count": 0,
    }
    mocker.patch.object(client, "list_events_detections_request", return_value=response_with_duplicates)

    args = {"lastUpdate": "2025-01-01T00:00:00+00:00"}
    result = get_modified_remote_data_command(client, args)

    assert len(result.modified_incident_ids) == 2
    assert "11111" in result.modified_incident_ids
    assert "22222" in result.modified_incident_ids


def test_get_remote_data_command_detection_found(mocker, client):
    """
    Given:
    - A client object.
    - A mocked list_events_detections_request that returns event data.

    When:
    - Calling the 'get_remote_data_command' function with valid arguments.

    Then:
    - Assert that the function returns a GetRemoteDataResponse with the detection data.
    """
    events_data = util_load_json(f"{TEST_DATA_DIR}/events_detections_response.json")
    mocker.patch.object(client, "list_events_detections_request", return_value=events_data)

    args = {"id": "11111", "lastUpdate": "2024-01-01T00:00:00+00:00"}
    result = get_remote_data_command(client, args)

    assert result.mirrored_object is not None
    assert result.mirrored_object.get("detection_id") == 11111


def test_get_remote_data_command_detection_not_found(mocker, client):
    """
    Given:
    - A client object.
    - A mocked list_events_detections_request that returns empty events.

    When:
    - Calling the 'get_remote_data_command' function with a detection ID that doesn't exist.

    Then:
    - Assert that the function returns "Incident was not found." message.
    """
    empty_response: dict = {"events": [], "next_checkpoint": None, "remaining_count": 0}
    mocker.patch.object(client, "list_events_detections_request", return_value=empty_response)

    args = {"id": "99999", "lastUpdate": "2024-01-01T00:00:00+00:00"}
    result = get_remote_data_command(client, args)

    assert result == "Incident was not found."


def test_get_remote_data_command_detection_updated(mocker, client):
    """
    Given:
    - A client object.
    - A mocked list_events_detections_request that returns updated event data.

    When:
    - Calling the 'get_remote_data_command' function where the event_timestamp is newer than lastUpdate.

    Then:
    - Assert that the function returns a GetRemoteDataResponse with updated detection data.
    """
    response_new_append = {
        "events": [
            {
                "detection_id": 11111,
                "event_timestamp": "2025-01-15T10:00:00Z",
                "detection_href": "https://dummy.url/detections/11111",
                "url": "https://dummy.url/entities/11",
                "status": "escalated",
            }
        ],
        "next_checkpoint": None,
        "remaining_count": 0,
    }
    response_other = {
        "events": [
            {
                "detection_id": 11111,
                "event_timestamp": "2025-01-14T10:00:00Z",
                "priority": "high",
            }
        ],
        "next_checkpoint": None,
        "remaining_count": 0,
    }
    mocker.patch.object(client, "list_events_detections_request", side_effect=[response_new_append, response_other])

    args = {"id": "11111", "lastUpdate": "2025-01-01T00:00:00+00:00"}
    result = get_remote_data_command(client, args)

    assert result.mirrored_object is not None
    assert result.mirrored_object.get("detection_id") == 11111
    assert "pivot=Vectra-RUX-XSOAR" in result.mirrored_object.get("detection_href", "")


def test_get_remote_data_command_nothing_new(mocker, client):
    """
    Given:
    - A client object.
    - A mocked list_events_detections_request that returns event data with old event_timestamp.

    When:
    - Calling the 'get_remote_data_command' function where the event_timestamp is older than lastUpdate.

    Then:
    - Assert that the function returns a GetRemoteDataResponse (no new updates).
    """
    response = {
        "events": [
            {
                "detection_id": 11111,
                "event_timestamp": "2025-01-01T00:00:00Z",
                "detection_href": "https://dummy.url/detections/11111",
                "url": "https://dummy.url/entities/11",
            }
        ],
        "next_checkpoint": None,
        "remaining_count": 0,
    }
    mocker.patch.object(client, "list_events_detections_request", return_value=response)

    args = {"id": "11111", "lastUpdate": "2025-01-15T00:00:00+00:00"}
    result = get_remote_data_command(client, args)

    assert result.mirrored_object is not None
    assert result.entries == []


def test_update_remote_system_command_with_notes(mocker, client):
    """
    Given:
    - A client object.
    - Mocked arguments with notes to be mirrored.

    When:
    - Calling the 'update_remote_system_command' function with valid arguments containing notes.

    Then:
    - Assert that the remote incident ID is returned.
    - Assert that add_note_to_detection_request is called.
    """
    mock_args = util_load_json(f"{TEST_DATA_DIR}/update_remote_system_args.json")

    mocker.patch.object(client, "list_detection_tags_request", return_value={})
    mocker.patch.object(client, "update_detection_tags_request", return_value={})
    add_note_mock = mocker.patch.object(client, "add_note_to_detection_request", return_value={})

    remote_incident_id = update_remote_system_command(client, mock_args, {})

    assert remote_incident_id == "12345"
    add_note_mock.assert_called_once()


def test_update_remote_system_command_with_tags(mocker, client):
    """
    Given:
    - A client object.
    - Mocked arguments with tags to be mirrored.

    When:
    - Calling the 'update_remote_system_command' function with valid arguments containing tags.

    Then:
    - Assert that the remote incident ID is returned.
    - Assert that update_detection_tags_request is called with the tags.
    """
    mock_args = util_load_json(f"{TEST_DATA_DIR}/update_remote_system_args.json")

    mocker.patch.object(client, "list_detection_tags_request", return_value={})
    update_tags_mock = mocker.patch.object(client, "update_detection_tags_request", return_value={})
    mocker.patch.object(client, "add_note_to_detection_request", return_value={})

    remote_incident_id = update_remote_system_command(client, mock_args, {})

    assert remote_incident_id == "12345"
    update_tags_mock.assert_called_once()


def test_update_remote_system_command_remove_all_tags(mocker, client):
    """
    Given:
    - A client object.
    - Mocked arguments where all tags are removed from XSOAR.

    When:
    - Calling the 'update_remote_system_command' function with arguments indicating tag removal.

    Then:
    - Assert that the remote incident ID is returned.
    - Assert that update_detection_tags_request is called to remove tags.
    """
    mock_args = util_load_json(f"{TEST_DATA_DIR}/update_remote_system_args.json")
    mock_args["delta"]["tags"] = []

    mocker.patch.object(client, "list_detection_tags_request", return_value={"tags": ["tag1", "tag2"]})
    update_tags_mock = mocker.patch.object(client, "update_detection_tags_request", return_value={})
    mocker.patch.object(client, "add_note_to_detection_request", return_value={})

    remote_incident_id = update_remote_system_command(client, mock_args, {})

    assert remote_incident_id == "12345"
    update_tags_mock.assert_called_once_with(detection_id="12345", tags=[])


def test_update_remote_system_command_with_detection_status(mocker, client, requests_mock):
    """
    Given:
    - A client object.
    - Mocked arguments with detection status to be updated.

    When:
    - Calling the 'update_remote_system_command' function with detection status.

    Then:
    - Assert that update_detection_status_request is called with the correct status.
    """
    mock_args = util_load_json(f"{TEST_DATA_DIR}/update_remote_system_args.json")
    mock_args["data"]["vectraruxinvestigationstatus"] = "closed"

    mocker.patch.object(client, "list_detection_tags_request", return_value={})
    mocker.patch.object(client, "update_detection_tags_request", return_value={})
    mocker.patch.object(client, "add_note_to_detection_request", return_value={})
    requests_mock.patch(os.path.join(BASE_URL, ENDPOINTS["DETECTION_ENDPOINT"]), json={})

    remote_incident_id = update_remote_system_command(client, mock_args, {})

    assert remote_incident_id == "12345"


def test_update_remote_system_command_with_priority_status(mocker, client, requests_mock):
    """
    Given:
    - A client object.
    - Mocked arguments with priority status to be updated to Not Prioritized.

    When:
    - Calling the 'update_remote_system_command' function with priority status.

    Then:
    - Assert that update_entity_unresolved_priority_status_request is called.
    """
    mock_args = util_load_json(f"{TEST_DATA_DIR}/update_remote_system_args.json")
    mock_args["data"]["vectraruxentityprioritystatus"] = "Not Prioritized"
    mock_args["data"]["vectraruxentityunresolvedprioritystatus"] = ""
    entity_id = mock_args.get("data").get("vectraruxentityid")

    mocker.patch.object(client, "list_detection_tags_request", return_value={})
    mocker.patch.object(client, "update_detection_tags_request", return_value={})
    mocker.patch.object(client, "add_note_to_detection_request", return_value={})
    requests_mock.patch(os.path.join(BASE_URL, f"{ENDPOINTS['ENTITY_ENDPOINT']}/{entity_id}"), json={})

    remote_incident_id = update_remote_system_command(client, mock_args, {})

    assert remote_incident_id == "12345"


def test_update_remote_system_command_with_closing_notes(mocker, client):
    """
    Given:
    - A client object.
    - Mocked arguments with closing notes for incident closure.

    When:
    - Calling the 'update_remote_system_command' function with closing notes.

    Then:
    - Assert that add_note_to_detection_request is called with the closing note.
    """
    mock_args = util_load_json(f"{TEST_DATA_DIR}/update_remote_system_args.json")
    mock_args["data"]["closeNotes"] = "Closing this incident due to resolution"
    mock_args["data"]["closeReason"] = "Resolved"
    mock_args["data"]["closingUserId"] = "admin_user"
    mock_args["delta"]["closingUserId"] = "admin_user"
    mock_args["entries"] = []

    mocker.patch.object(client, "list_detection_tags_request", return_value={})
    mocker.patch.object(client, "update_detection_tags_request", return_value={})
    add_note_mock = mocker.patch.object(client, "add_note_to_detection_request", return_value={})

    remote_incident_id = update_remote_system_command(client, mock_args, {})

    assert remote_incident_id == "12345"
    add_note_mock.assert_called_once()
    call_args = add_note_mock.call_args
    assert "Mirrored From XSOAR" in call_args[1]["note"]
    assert "Close Reason: Resolved" in call_args[1]["note"]
    assert "Closed By: admin_user" in call_args[1]["note"]
    assert "Close Notes: Closing this incident due to resolution" in call_args[1]["note"]


def test_update_remote_system_command_no_changes(mocker, client):
    """
    Given:
    - A client object.
    - Mocked arguments with no changes (empty delta, no entries).

    When:
    - Calling the 'update_remote_system_command' function with no changes.

    Then:
    - Assert that the remote incident ID is returned without making any API calls.
    """
    mock_args = util_load_json(f"{TEST_DATA_DIR}/update_remote_system_args.json")
    mock_args["delta"] = {}
    mock_args["entries"] = []

    mocker.patch.object(client, "list_detection_tags_request", return_value={})
    update_tags_mock = mocker.patch.object(client, "update_detection_tags_request", return_value={})
    add_note_mock = mocker.patch.object(client, "add_note_to_detection_request", return_value={})

    remote_incident_id = update_remote_system_command(client, mock_args, {})

    assert remote_incident_id == "12345"
    update_tags_mock.assert_not_called()
    add_note_mock.assert_not_called()


def test_update_remote_system_command_note_exceeds_limit(mocker, client):
    """
    Given:
    - A client object.
    - Mocked arguments with a note that exceeds the maximum character limit.

    When:
    - Calling the 'update_remote_system_command' function with a long note.

    Then:
    - Assert that the note is skipped and info is logged.
    """
    mock_args = util_load_json(f"{TEST_DATA_DIR}/update_remote_system_args.json")
    mock_args["entries"] = [
        {
            "id": "note_id",
            "type": "note",
            "contents": "A" * 9000,  # Exceeds MAX_OUTGOING_NOTE_LIMIT (8000)
            "user": "user1",
        }
    ]

    mocker.patch.object(client, "list_detection_tags_request", return_value={})
    mocker.patch.object(client, "update_detection_tags_request", return_value={})
    add_note_mock = mocker.patch.object(client, "add_note_to_detection_request", return_value={})
    info_mock = mocker.patch.object(demisto, "info")

    remote_incident_id = update_remote_system_command(client, mock_args, {})

    assert remote_incident_id == "12345"
    add_note_mock.assert_called_once()
    info_mock.assert_called()


def test_main_function_get_modified_remote_data(mocker, client):
    """
    Given:
    - Mocked demisto.command() returning "get-modified-remote-data".

    When:
    - Calling main function with get-modified-remote-data command.

    Then:
    - Assert that get_modified_remote_data_command is called.
    """
    events_data: dict = {"events": [], "next_checkpoint": None, "remaining_count": 0}
    mocker.patch.object(demisto, "params", return_value={})
    mocker.patch.object(demisto, "command", return_value="get-modified-remote-data")
    mocker.patch.object(demisto, "args", return_value={"lastUpdate": "2025-01-01T00:00:00+00:00"})
    mocker.patch.object(client, "list_events_detections_request", return_value=events_data)
    mocker.patch("VectraRUXEventsDetections.VectraEventsDetectionsClient", return_value=client)
    mocker.patch("VectraRUXEventsDetections.return_results")

    VectraRUXEventsDetections.main()

    VectraRUXEventsDetections.return_results.assert_called_once()  # type: ignore[attr-defined]


def test_main_function_get_remote_data(mocker, client):
    """
    Given:
    - Mocked demisto.command() returning "get-remote-data".

    When:
    - Calling main function with get-remote-data command.

    Then:
    - Assert that get_remote_data_command is called.
    """
    events_data = util_load_json(f"{TEST_DATA_DIR}/events_detections_response.json")
    mocker.patch.object(demisto, "params", return_value={})
    mocker.patch.object(demisto, "command", return_value="get-remote-data")
    mocker.patch.object(demisto, "args", return_value={"id": "11111", "lastUpdate": "2025-01-01T00:00:00+00:00"})
    mocker.patch.object(client, "list_events_detections_request", return_value=events_data)
    mocker.patch("VectraRUXEventsDetections.VectraEventsDetectionsClient", return_value=client)
    mocker.patch("VectraRUXEventsDetections.return_results")

    VectraRUXEventsDetections.main()

    VectraRUXEventsDetections.return_results.assert_called_once()  # type: ignore[attr-defined]


def test_main_function_update_remote_system(mocker, client, requests_mock):
    """
    Given:
    - Mocked demisto.command() returning "update-remote-system".

    When:
    - Calling main function with update-remote-system command.

    Then:
    - Assert that update_remote_system_command is called.
    """
    mock_args = util_load_json(f"{TEST_DATA_DIR}/update_remote_system_args.json")
    mocker.patch.object(demisto, "params", return_value={})
    mocker.patch.object(demisto, "command", return_value="update-remote-system")
    mocker.patch.object(demisto, "args", return_value=mock_args)
    requests_mock.get(os.path.join(BASE_URL, ENDPOINTS["LIST_TAGS_ENDPOINT"].format(mock_args.get("remoteId"))), json={})
    requests_mock.patch(os.path.join(BASE_URL, ENDPOINTS["LIST_TAGS_ENDPOINT"].format(mock_args.get("remoteId"))), json={})
    requests_mock.post(os.path.join(BASE_URL, ENDPOINTS["ADD_NOTE_ENDPOINT"].format(mock_args.get("remoteId"))), json={})
    mocker.patch("VectraRUXEventsDetections.VectraEventsDetectionsClient", return_value=client)
    mocker.patch("VectraRUXEventsDetections.return_results")

    VectraRUXEventsDetections.main()

    VectraRUXEventsDetections.return_results.assert_called_once()  # type: ignore[attr-defined]


def test_vectra_user_list_valid_arguments(requests_mock, client):
    """
    Given:
    - A mocked client for requests.
    - A mock assignment response.
    - Expected context data and human-readable output.

    When:
    - Calling the 'vectra_user_list_command' function with the provided client and arguments.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert that the human-readable output indicates that no results were found.
    - Assert that the 'EntryContext' property in the context is an empty dictionary.
    """
    user_res = util_load_json(f"{TEST_DATA_DIR}/user_list_response.json")
    context_data = util_load_json(f"{TEST_DATA_DIR}/user_list_context.json")
    with open(f"{TEST_DATA_DIR}/user_list_hr.md") as f:
        result_hr = f.read()
    requests_mock.get(BASE_URL + ENDPOINTS["USER_ENDPOINT"], json=user_res)
    # Call the function
    result = vectra_user_list_command(client, {"last_login_timestamp": "1 year"})
    result_context = result.to_context()
    # Assert the CommandResults
    assert result.outputs_prefix == "Vectra.User"
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("EntryContext") == context_data
    assert result.outputs_key_field == ["user_id"]


def test_vectra_user_list_when_response_is_empty(requests_mock, client):
    """
    Given:
    - A client object.
    - Mocked 'list_users_request' method returning an empty response.
    - Arguments specifying invalid tags.

    When:
    - Calling the 'vectra_user_list_command' function with the provided client and arguments.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert that the human-readable output indicates that no results were found.
    - Assert that the 'EntryContext' property in the context is an empty dictionary.
    """
    empty_response: dict = {"count": 0, "next": None, "previous": None, "results": []}
    requests_mock.get(BASE_URL + ENDPOINTS["USER_ENDPOINT"], json=empty_response)

    # Call the function
    result = vectra_user_list_command(client, {})
    result_context = result.to_context()
    # Assert the CommandResults
    assert result_context.get("HumanReadable") == "##### Got the empty list of users."
    assert result_context.get("EntryContext") == {}


def test_vectra_user_list_with_email_filter(mocker, client):
    """
    Given:
    - A mocked client.
    - Arguments with an email filter.

    When:
    - Calling the 'vectra_user_list_command' function with email argument.

    Then:
    - Assert that 'list_users_request' is called with 'email' (not 'username').
    - Assert that the command returns expected user data.
    """
    user_res = {
        "count": 1,
        "next": None,
        "previous": None,
        "results": [
            {
                "id": 10,
                "name": "brandon.bishop",
                "email": "test_user@example.com",
                "role": "Security Analyst",
                "last_login_timestamp": "2023-08-22T09:24:44Z",
            }
        ],
    }
    mock_request = mocker.patch.object(client, "list_users_request", return_value=user_res)

    result = vectra_user_list_command(client, {"email": "test_user@example.com"})

    mock_request.assert_called_once_with(email="test_user@example.com", role="", last_login_timestamp=None)
    assert result.outputs_prefix == "Vectra.User"
    assert len(result.outputs) == 1


def test_vectra_entity_list_valid_arguments(requests_mock, client):
    """
    Given:
    - A mocked client for requests.
    - A mock entity response.
    - Arguments specifying valid parameters for entity listing.

    When:
    - Calling the 'vectra_entity_list_command' function with the provided client and arguments.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert the correctness of the 'outputs_prefix' property.
    - Assert that the human-readable output contains the expected content.
    - Assert that the 'Contents' property in the context matches the entity data.
    - Assert that the 'EntryContext' property in the context matches the expected context data.
    - Assert the correctness of the 'outputs_key_field' property.
    """
    entity_data = util_load_json(f"{TEST_DATA_DIR}/list_entity_response.json")
    context_data = util_load_json(f"{TEST_DATA_DIR}/list_entity_context.json")
    requests_mock.get(BASE_URL + ENDPOINTS["ENTITY_ENDPOINT_v34"], json=entity_data)
    with open(f"{TEST_DATA_DIR}/list_entity_hr.md") as f:
        result_hr = f.read()
    args = {
        "entity_type": "account",
        "name": "name",
        "state": "active",
        "ordering": "name",
        "page": "1",
        "page_size": "4",
        "prioritized": "true",
        "tags": "test,test1",
        "last_modified_timestamp": "2 days",
        "last_detection_timestamp": "2 days",
    }

    # Call the function
    result = vectra_entity_list_command(client, args)
    result_context = result.to_context()
    # Assert the CommandResults
    assert result.outputs_prefix == "Vectra.Entity"
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("Contents") == entity_data.get("results")
    assert result_context.get("EntryContext") == remove_empty_elements(context_data)
    assert result.outputs_key_field == ["id", "type"]


def test_vectra_entity_list_when_response_is_empty(mocker, client):
    """
    Given:
    - A client object.
    - Mocked 'list_entities_request' method returning an empty response.
    - Arguments specifying invalid tags.

    When:
    - Calling the 'vectra_entity_list_command' function with the provided client and arguments.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert that the human-readable output indicates that no results were found.
    - Assert that the 'EntryContext' property in the context is an empty dictionary.
    """
    empty_response: dict = {"count": 0, "next": None, "previous": None, "results": []}
    mocker.patch.object(client, "list_entities_request", return_value=empty_response)
    args = {
        "tags": "invalid_tag",
        "name": "invalid_name",
    }

    # Call the function
    result = vectra_entity_list_command(client, args)
    result_context = result.to_context()
    # Assert the CommandResults
    assert result_context.get("HumanReadable") == "##### Couldn't find any matching entities for provided filters."
    assert result_context.get("EntryContext") == {}


@pytest.mark.parametrize(
    "args,error_msg",
    [
        (
            {"entity_type": "invalid_type"},
            ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)),
        ),
        ({"state": "invalid_state"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("state", ", ".join(VALID_ENTITY_STATE))),
        ({"page_size": "5001"}, ERRORS["INVALID_PAGE_SIZE"]),
    ],
)
def test_vectra_entity_list_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Arguments specifying invalid values.

    When:
    - Calling the 'vectra_entity_list_command' function with the provided client and arguments.

    Then:
    - Assert that the function raises a ValueError.
    - Assert that the error message matches the expected value for the corresponding invalid argument.
    """
    # Call the function and assert that it raises ValueError
    with pytest.raises(ValueError) as exception:
        vectra_entity_list_command(client, args)

    assert str(exception.value) == error_msg


def test_vectra_entity_describe_valid_arguments(requests_mock, client):
    """
    Given:
    - A mocked client for requests.
    - A mock entity response.
    - The expected human-readable output file.
    - Arguments specifying valid parameters for getting an entity.

    When:
    - Calling the 'vectra_entity_describe_command' function with the provided client and arguments.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert the correctness of the 'outputs_prefix' property.
    - Assert that the human-readable output matches the content of the expected file.
    - Assert that the 'Contents' property in the context matches the entity data.
    - Assert that the 'EntryContext' property in the context matches the expected context data.
    - Assert the correctness of the 'outputs_key_field' property.
    """
    entity_data = util_load_json(f"{TEST_DATA_DIR}/get_entity_response.json")
    context_data = util_load_json(f"{TEST_DATA_DIR}/get_entity_context.json")
    requests_mock.get(BASE_URL + ENDPOINTS["ENTITY_ENDPOINT_v34"] + "/21", json=entity_data)
    with open(f"{TEST_DATA_DIR}/get_entity_hr.md") as f:
        result_hr = f.read()
    args = {"entity_id": "21", "entity_type": "account"}

    # Call the function
    result = vectra_entity_describe_command(client, args)
    result_context = result.to_context()
    # Assert the CommandResults
    assert result.outputs_prefix == "Vectra.Entity"
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("Contents") == entity_data
    assert result_context.get("EntryContext") == remove_empty_elements(context_data)
    assert result.outputs_key_field == ["id", "type"]


@pytest.mark.parametrize(
    "args,error_msg",
    [
        (
            {"entity_id": "1", "entity_type": "invalid_type"},
            ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)),
        ),
        ({"entity_type": "account"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")),
        ({"entity_id": "1", "entity_type": ""}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type")),
    ],
)
def test_vectra_entity_describe_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Arguments specifying an invalid entity_type value.

    When:
    - Calling the 'vectra_entity_describe_command' function with the provided client and arguments.

    Then:
    - Assert that the function raises a ValueError.
    - Assert that the error message matches the expected value for an invalid entity_type value.
    """
    # Call the function and assert that it raises ValueError
    with pytest.raises(ValueError) as exception:
        vectra_entity_describe_command(client, args)

    assert str(exception.value) == error_msg


def test_vectra_entity_detection_list_valid_arguments(requests_mock, client):
    """
    Given:
    - A mocked client for requests.

    When:
    - A mock entity and detections response.
    - Opening and reading a specific human-readable file.
    - Providing arguments with a valid entity_id, page, and page_size.

    Then:
    - Call the 'vectra_list_entity_detection_command' function with the provided client and arguments.
    - Assert that the CommandResults outputs_prefix is 'Vectra.Entity.Detections'.
    - Assert that the CommandResults HumanReadable matches the content of the read human-readable file.
    - Assert that the CommandResults Contents match the expected detections data.
    - Assert that the 'EntryContext' property in the context matches the expected context data.
    - Assert that the CommandResults outputs_key_field is 'id'.
    """
    detections_data = util_load_json(f"{TEST_DATA_DIR}/entity_detection_list_response.json")
    context_data = util_load_json(f"{TEST_DATA_DIR}/entity_detection_list_context.json")
    entity_data = util_load_json(f"{TEST_DATA_DIR}/get_entity_response.json")
    requests_mock.get(BASE_URL + ENDPOINTS["ENTITY_ENDPOINT_v34"] + "/21", json=entity_data)
    requests_mock.get(BASE_URL + ENDPOINTS["DETECTION_ENDPOINT"], json=detections_data)
    with open(f"{TEST_DATA_DIR}/entity_detection_list_hr.md") as f:
        result_hr = f.read()
    args = {
        "entity_id": "21",
        "entity_type": "account",
        "page": "1",
        "page_size": "50",
        "last_timestamp": "2 days",
        "detection_category": "Botnet",
    }

    # Call the function
    result = vectra_entity_detection_list_command(client, args)
    result_context = result.to_context()
    # Assert the CommandResults
    assert result.outputs_prefix == "Vectra.Entity.Detections"
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("Contents") == detections_data
    assert result_context.get("EntryContext") == remove_empty_elements(context_data)
    assert result.outputs_key_field == "id"


def test_vectra_entity_detection_list_when_detection_response_is_empty(mocker, client):
    """
    Given:
    - A client object.
    - Mocked 'list_detections_request' method returning an empty response.
    - Arguments specifying invalid tags.

    When:
    - Calling the 'vectra_entity_list_command' function with the provided client and arguments.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert that the human-readable output indicates that no results were found.
    - Assert that the 'EntryContext' property in the context is an empty dictionary.
    """
    empty_response: dict = {"count": 0, "next": None, "previous": None, "results": []}
    entity_data = util_load_json(f"{TEST_DATA_DIR}/get_entity_response.json")
    mocker.patch.object(client, "get_entity_request", return_value=entity_data)
    mocker.patch.object(client, "list_detections_request", return_value=empty_response)
    args = {
        "entity_id": "1",
        "entity_type": "account",
        "tags": "invalid_tag",
    }

    # Call the function
    result = vectra_entity_detection_list_command(client, args)
    result_context = result.to_context()
    # Assert the CommandResults
    assert result_context.get("HumanReadable") == "##### Couldn't find any matching entity detections for provided filters."
    assert result_context.get("EntryContext") == {}


def test_vectra_entity_detection_list_when_entity_response_is_empty(mocker, client):
    """
    Given:
    - A client object.
    - Mocked 'get_entity_request' method returning an empty response.
    - Arguments specifying invalid tags.

    When:
    - Calling the 'vectra_entity_list_command' function with the provided client and arguments.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert that the human-readable output indicates that no results were found.
    - Assert that the 'EntryContext' property in the context is an empty dictionary.
    """
    empty_response: dict = {"count": 0, "next": None, "previous": None, "results": []}

    mocker.patch.object(client, "get_entity_request", return_value={})
    mocker.patch.object(client, "list_detections_request", return_value=empty_response)
    args = {
        "entity_id": "1",
        "entity_type": "account",
        "tags": "invalid_tag",
    }

    # Call the function
    result = vectra_entity_detection_list_command(client, args)
    result_context = result.to_context()
    # Assert the CommandResults
    assert result_context.get("HumanReadable") == "##### Couldn't find any matching detections for provided entity ID and type."
    assert result_context.get("EntryContext") == {}


@pytest.mark.parametrize(
    "args,error_msg",
    [
        ({"entity_id": None}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")),
        ({"entity_id": "1", "entity_type": "account", "page": "0"}, ERRORS["INVALID_INTEGER_VALUE"].format("page", "0")),
        (
            {"entity_id": "1", "entity_type": "account", "page_size": "0"},
            ERRORS["INVALID_INTEGER_VALUE"].format("page_size", "0"),
        ),
        ({"entity_id": "1", "entity_type": "account", "page_size": "5001"}, ERRORS["INVALID_PAGE_SIZE"]),
        (
            {"entity_id": "1", "entity_type": "account", "detection_category": "command and control"},
            ERRORS["INVALID_COMMAND_ARG_VALUE"].format("detection_category", ", ".join(DETECTION_CATEGORY_TO_ARG.keys())),
        ),
        ({"entity_id": "1", "entity_type": ""}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type")),
        (
            {"entity_id": "1", "entity_type": "invalid"},
            ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)),
        ),
    ],
)
def test_vectra_entity_detection_list_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Arguments specifying different invalid values for entity_id, page, and page_size.

    When:
    - Calling the 'vectra_list_entity_detection_command' function with the provided client and arguments.

    Then:
    - Assert that the function raises a ValueError.
    - Assert that the error message matches the expected error message for each invalid argument.
    """
    # Call the function and assert that it raises ValueError
    with pytest.raises(ValueError) as exception:
        vectra_entity_detection_list_command(client, args)

    assert str(exception.value) == error_msg


def test_vectra_detection_describe_valid_arguments(mocker, client):
    """
    Given:
    - A client object.

    When:
    - Mocking the 'list_detections_request' method of the client to return a specific detection data.
    - Opening and reading a specific human-readable file.
    - Providing arguments with a valid detection_ids, page, and page_size.

    Then:
    - Call the 'vectra_detection_describe_command' function with the provided client and arguments.
    - Assert that the CommandResults outputs_prefix is 'Vectra.Entity.Detections'.
    - Assert that the CommandResults HumanReadable matches the content of the read human-readable file.
    - Assert that the CommandResults Contents match the expected detection data.
    - Assert that the 'EntryContext' property in the context matches the expected context data.
    - Assert that the CommandResults outputs_key_field is 'id'.
    """
    detections_data = util_load_json(f"{TEST_DATA_DIR}/entity_detection_list_response.json")
    context_data = util_load_json(f"{TEST_DATA_DIR}/entity_detection_list_context.json")
    mocker.patch.object(client, "list_detections_request", return_value=detections_data)
    with open(f"{TEST_DATA_DIR}/entity_detection_list_hr.md") as f:
        result_hr = f.read()
    args = {"detection_ids": "21", "page": "1", "page_size": "50"}

    # Call the function
    result = vectra_detection_describe_command(client, args)
    result_context = result.to_context()
    # Assert the CommandResults
    assert result.outputs_prefix == "Vectra.Entity.Detections"
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("Contents") == detections_data
    assert result_context.get("EntryContext") == remove_empty_elements(context_data)
    assert result.outputs_key_field == "id"


def test_vectra_detection_describe_when_detection_response_is_empty(mocker, client):
    """
    Given:
    - A client object.
    - Mocked 'list_detections_request' method returning an empty response.
    - Arguments specifying invalid tags.

    When:
    - Calling the 'vectra_detection_describe_command' function with the provided client and arguments.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert that the human-readable output indicates that no results were found.
    - Assert that the 'EntryContext' property in the context is an empty dictionary.
    """
    empty_response: dict = {"count": 0, "next": None, "previous": None, "results": []}
    mocker.patch.object(client, "list_detections_request", return_value=empty_response)
    args = {"detection_ids": "21"}

    # Call the function
    result = vectra_detection_describe_command(client, args)
    result_context = result.to_context()
    # Assert the CommandResults
    assert result_context.get("HumanReadable") == "##### Couldn't find any matching detections for provided detection ID(s)."
    assert result_context.get("EntryContext") == {}


@pytest.mark.parametrize(
    "args,error_msg",
    [
        ({"detection_ids": None}, ERRORS["REQUIRED_ARGUMENT"].format("detection_ids")),
        ({"detection_ids": " "}, ERRORS["REQUIRED_ARGUMENT"].format("detection_ids")),
        ({"detection_ids": ",   , ,"}, ERRORS["REQUIRED_ARGUMENT"].format("detection_ids")),
        ({"detection_ids": ",,abc,"}, ERRORS["INVALID_NUMBER"].format("abc")),
        ({"detection_ids": ",,abc,12"}, ERRORS["INVALID_NUMBER"].format("abc")),
        ({"detection_ids": ",-12,"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_ids", "-12")),
        ({"detection_ids": ",12,", "page": "0"}, ERRORS["INVALID_INTEGER_VALUE"].format("page", "0")),
        ({"detection_ids": ",12,", "page_size": "0"}, ERRORS["INVALID_INTEGER_VALUE"].format("page_size", "0")),
        ({"detection_ids": ",12,", "page_size": "5001"}, ERRORS["INVALID_PAGE_SIZE"]),
    ],
)
def test_vectra_detection_describe_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Arguments specifying different invalid values for detection_ids, page, and page_size.

    When:
    - Calling the 'vectra_detection_describe_command' function with the provided client and arguments.

    Then:
    - Assert that the function raises a ValueError.
    - Assert that the error message matches the expected error message for each invalid argument.
    """
    # Call the function and assert that it raises ValueError
    with pytest.raises(ValueError) as exception:
        vectra_detection_describe_command(client, args)

    assert str(exception.value) == error_msg


def test_vectra_entity_note_add_valid_arguments(requests_mock, client):
    """
    Given:
    - A mocked client for requests.
    - A mock notes response.
    - The expected human-readable output file.
    - Arguments specifying valid parameters for adding a note to an entity.

    When:
    - Calling the 'vectra_entity_note_add_command' function with the provided client and arguments.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert the correctness of the 'outputs_prefix' property.
    - Assert that the human-readable output matches the content of the expected file.
    - Assert that the 'Contents' property in the context matches the notes response.
    - Assert that the 'EntryContext' property in the context matches the context data.
    - Assert the correctness of the 'outputs_key_field' property.
    """
    notes_res = util_load_json(f"{TEST_DATA_DIR}/entity_note_add_response.json")
    context_data = util_load_json(f"{TEST_DATA_DIR}/entity_note_add_context.json")
    requests_mock.post(BASE_URL + ENDPOINTS["ADD_AND_LIST_ENTITY_NOTE_ENDPOINT"].format("1"), json=notes_res)
    with open(f"{TEST_DATA_DIR}/entity_note_add_hr.md") as f:
        result_hr = f.read()
    args = {
        "entity_id": "1",
        "entity_type": "account",
        "note": "test_note",
    }

    # Call the function
    result = vectra_entity_note_add_command(client, args)
    result_context = result.to_context()
    notes_res["note_id"] = notes_res["id"]
    notes_res["entity_id"] = 1
    notes_res["entity_type"] = "account"
    # Assert the CommandResults
    assert result.outputs_prefix == "Vectra.Entity.Notes"
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("Contents") == notes_res
    assert result_context.get("EntryContext") == context_data
    assert result.outputs_key_field == ["entity_id", "entity_type", "note_id"]


@pytest.mark.parametrize(
    "args,error_msg",
    [
        (
            {"entity_id": "1", "entity_type": "invalid_type"},
            ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)),
        ),
        ({"entity_id": "1", "entity_type": "account"}, ERRORS["REQUIRED_ARGUMENT"].format("note")),
        ({"entity_type": "account", "note": "test_note"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")),
        (
            {"entity_id": "0", "entity_type": "account", "note": "test_note"},
            ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "0"),
        ),
        (
            {"entity_id": "-1", "entity_type": "account", "note": "test_note"},
            ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "-1"),
        ),
        (
            {"entity_id": "1.5", "entity_type": "account", "note": "test_note"},
            ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "1.5"),
        ),
        ({"entity_id": "1", "entity_type": ""}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type")),
    ],
)
def test_vectra_entity_note_add_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Arguments specifying different invalid values for entity_id, entity_type, and note.

    When:
    - Calling the 'vectra_entity_note_add_command' function with the provided client and arguments.

    Then:
    - Assert that the function raises a ValueError.
    - Assert that the error message matches the expected error message for each invalid argument.
    """
    # Call the function and assert that it raises ValueError
    with pytest.raises(ValueError) as exception:
        vectra_entity_note_add_command(client, args)

    assert str(exception.value) == error_msg


def test_vectra_entity_note_update_valid_arguments(requests_mock, client):
    """
    Given:
    - A mocked client for requests.
    - A mock note response.
    - The expected human-readable output file.
    - Arguments specifying valid parameters for updating a note of an entity.

    When:
    - Calling the 'vectra_entity_note_update_command' function with the provided client and arguments.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert the correctness of the 'outputs_prefix' property.
    - Assert that the human-readable output matches the content of the expected file.
    - Assert that the 'Contents' property in the context matches the notes response.
    - Assert that the 'EntryContext' property in the context matches the context data.
    - Assert the correctness of the 'outputs_key_field' property.
    """
    notes_res = util_load_json(f"{TEST_DATA_DIR}/entity_note_update_response.json")
    context_data = util_load_json(f"{TEST_DATA_DIR}/entity_note_update_context.json")
    requests_mock.patch(BASE_URL + ENDPOINTS["UPDATE_AND_REMOVE_ENTITY_NOTE_ENDPOINT"].format(1, 1), json=notes_res)
    with open(f"{TEST_DATA_DIR}/entity_note_update_hr.md") as f:
        result_hr = f.read()
    args = {
        "entity_id": "1",
        "entity_type": "account",
        "note_id": "1",
        "note": "test_note",
    }

    # Call the function
    result = vectra_entity_note_update_command(client, args)
    result_context = result.to_context()
    notes_res["note_id"] = notes_res["id"]
    notes_res["entity_id"] = 1
    notes_res["entity_type"] = "account"
    # Assert the CommandResults
    assert result.outputs_prefix == "Vectra.Entity.Notes"
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("Contents") == notes_res
    assert result_context.get("EntryContext") == context_data
    assert result.outputs_key_field == ["entity_id", "entity_type", "note_id"]


@pytest.mark.parametrize(
    "args,error_msg",
    [
        (
            {"entity_id": "1", "entity_type": "invalid_type", "note_id": "1"},
            ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)),
        ),
        ({"entity_id": "1", "entity_type": "account", "note_id": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("note")),
        ({"entity_type": "account", "note": "test_note", "note_id": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")),
        ({"entity_id": "1", "entity_type": "account", "note": "test_note"}, ERRORS["REQUIRED_ARGUMENT"].format("note_id")),
        (
            {"entity_id": "0", "entity_type": "account", "note": "test_note", "note_id": "1"},
            ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "0"),
        ),
        (
            {"entity_id": "-1", "entity_type": "account", "note": "test_note", "note_id": "1"},
            ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "-1"),
        ),
        (
            {"entity_id": "1.5", "entity_type": "account", "note": "test_note", "note_id": "1"},
            ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "1.5"),
        ),
        (
            {"note_id": "0", "entity_type": "account", "note": "test_note", "entity_id": "1"},
            ERRORS["INVALID_INTEGER_VALUE"].format("note_id", "0"),
        ),
        (
            {"note_id": "-1", "entity_type": "account", "note": "test_note", "entity_id": "1"},
            ERRORS["INVALID_INTEGER_VALUE"].format("note_id", "-1"),
        ),
        (
            {"note_id": "1.5", "entity_type": "account", "note": "test_note", "entity_id": "1"},
            ERRORS["INVALID_INTEGER_VALUE"].format("note_id", "1.5"),
        ),
        ({"entity_id": "1", "entity_type": "", "note_id": "2"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type")),
    ],
)
def test_vectra_entity_note_update_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Invalid arguments for updating a note of an entity.

    When:
    - Calling the 'vectra_entity_note_update_command' function with the provided client and arguments.

    Then:
    - Assert that a ValueError is raised with the expected error message.
    """
    # Call the function and assert that it raises ValueError
    with pytest.raises(ValueError) as exception:
        vectra_entity_note_update_command(client, args)

    assert str(exception.value) == error_msg


def test_vectra_entity_note_remove_valid_arguments(requests_mock, client):
    """
    Tests the 'vectra_entity_note_remove_command' function with valid arguments.

    Ensures that the function removes an entity note and returns the expected CommandResults object.

    Args:
        requests_mock: The requests mocker object.
        client: The VectraClient instance.

    Returns:
        None. Raises an AssertionError if the test fails.
    """
    requests_mock.delete(BASE_URL + ENDPOINTS["UPDATE_AND_REMOVE_ENTITY_NOTE_ENDPOINT"].format(1, 1), status_code=204)
    with open(f"{TEST_DATA_DIR}/entity_note_remove_hr.md") as f:
        result_hr = f.read()
    args = {
        "entity_id": "1",
        "entity_type": "account",
        "note_id": "1",
    }

    # Call the function
    result = vectra_entity_note_remove_command(client, args)
    result_context = result.to_context()
    # Assert the CommandResults
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("EntryContext") == {}


def test_vectra_entity_note_remove_invalid_status_code(requests_mock, client):
    """
    Tests the 'vectra_entity_note_remove_command' function with valid arguments.

    Ensures that the function gives error in HR for status code.

    Args:
        requests_mock: The requests mocker object.
        client: The VectraClient instance.

    Returns:
        None. Raises an AssertionError if the test fails.
    """
    requests_mock.delete(BASE_URL + ENDPOINTS["UPDATE_AND_REMOVE_ENTITY_NOTE_ENDPOINT"].format(1, 1), status_code=200)
    args = {
        "entity_id": "1",
        "entity_type": "account",
        "note_id": "1",
    }

    # Call the function
    result = vectra_entity_note_remove_command(client, args)
    result_context = result.to_context()
    # Assert the CommandResults
    assert result_context.get("HumanReadable") == "Something went wrong."
    assert result_context.get("EntryContext") == {}


@pytest.mark.parametrize(
    "args,error_msg",
    [
        (
            {"entity_id": "1", "entity_type": "invalid_type", "note_id": "1"},
            ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)),
        ),
        ({"entity_type": "account", "note": "test_note", "note_id": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")),
        ({"entity_id": "1", "note_id": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type")),
        ({"entity_id": "1", "entity_type": "account"}, ERRORS["REQUIRED_ARGUMENT"].format("note_id")),
        (
            {"entity_id": "0", "entity_type": "account", "note": "test_note", "note_id": "1"},
            ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "0"),
        ),
        (
            {"entity_id": "-1", "entity_type": "account", "note": "test_note", "note_id": "1"},
            ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "-1"),
        ),
        (
            {"entity_id": "1.5", "entity_type": "account", "note": "test_note", "note_id": "1"},
            ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "1.5"),
        ),
        (
            {"note_id": "0", "entity_type": "account", "note": "test_note", "entity_id": "1"},
            ERRORS["INVALID_INTEGER_VALUE"].format("note_id", "0"),
        ),
        (
            {"note_id": "-1", "entity_type": "account", "note": "test_note", "entity_id": "1"},
            ERRORS["INVALID_INTEGER_VALUE"].format("note_id", "-1"),
        ),
        (
            {"note_id": "1.5", "entity_type": "account", "note": "test_note", "entity_id": "1"},
            ERRORS["INVALID_INTEGER_VALUE"].format("note_id", "1.5"),
        ),
    ],
)
def test_vectra_entity_note_remove_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Invalid arguments for updating a note of an entity.

    When:
    - Calling the 'vectra_entity_note_remove_command' function with the provided client and arguments.

    Then:
    - Assert that a ValueError is raised with the expected error message.
    """
    # Call the function and assert that it raises ValueError
    with pytest.raises(ValueError) as exception:
        vectra_entity_note_remove_command(client, args)

    assert str(exception.value) == error_msg


def test_vectra_entity_tag_add_valid_arguments(requests_mock, client):
    """
    Given:
    - A mocked client for requests.
    - A mock get and update tag response.
    - The expected human-readable output file.
    - Arguments specifying valid parameters for adding the tags to an entity.

    When:
    - Calling the 'vectra_entity_tag_add_command' function with the provided client and arguments.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert the correctness of the 'outputs_prefix' property.
    - Assert that the human-readable output matches the content of the expected file.
    - Assert that the 'Contents' property in the context matches the tags response.
    - Assert that the 'EntryContext' property in the context matches the context data.
    - Assert the correctness of the 'outputs_key_field' property.
    """
    add_tags_res = util_load_json(f"{TEST_DATA_DIR}/entity_tag_add_response.json")
    get_tags_res = util_load_json(f"{TEST_DATA_DIR}/entity_tag_get_response.json")
    context_data = util_load_json(f"{TEST_DATA_DIR}/entity_tag_add_context.json")
    requests_mock.get(BASE_URL + ENDPOINTS["ENTITY_TAG_ENDPOINT"].format(1), json=get_tags_res)
    requests_mock.patch(BASE_URL + ENDPOINTS["ENTITY_TAG_ENDPOINT"].format(1), json=add_tags_res)
    with open(f"{TEST_DATA_DIR}/entity_tag_add_hr.md") as f:
        result_hr = f.read()
    args = {
        "entity_id": "1",
        "entity_type": "host",
        "tags": "tag1, tag2",
    }

    # Call the function
    result = vectra_entity_tag_add_command(client, args)
    result_context = result.to_context()
    add_tags_res.update({"entity_id": 1, "entity_type": "host"})
    del add_tags_res["status"]
    # Assert the CommandResults
    assert result.outputs_prefix == "Vectra.Entity.Tags"
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("Contents") == add_tags_res
    assert result_context.get("EntryContext") == remove_empty_elements(context_data)
    assert result.outputs_key_field == ["tag_id", "entity_type", "entity_id"]


@pytest.mark.parametrize(
    "args,error_msg",
    [
        (
            {"entity_id": "1", "entity_type": "invalid_type"},
            ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)),
        ),
        ({"entity_id": "1", "entity_type": "account"}, ERRORS["REQUIRED_ARGUMENT"].format("tags")),
        ({"entity_id": "1", "entity_type": "account", "tags": " , "}, ERRORS["REQUIRED_ARGUMENT"].format("tags")),
        ({"entity_type": "account"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")),
        (
            {"entity_id": "0", "entity_type": "account", "tags": " , tag1"},
            ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "0"),
        ),
        (
            {"entity_id": "-1", "entity_type": "account", "tags": "tag1"},
            ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "-1"),
        ),
        (
            {"entity_id": "1.5", "entity_type": "account", "tags": "tag1"},
            ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "1.5"),
        ),
        ({"entity_id": "1", "entity_type": "", "tags": "tag1"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type")),
    ],
)
def test_vectra_entity_tag_add_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Arguments specifying different invalid values for entity_id, entity_type, and tags.

    When:
    - Calling the 'vectra_entity_tag_add_command' function with the provided client and arguments.

    Then:
    - Assert that the function raises a ValueError.
    - Assert that the error message matches the expected error message for each invalid argument.
    """
    # Call the function and assert that it raises ValueError
    with pytest.raises(ValueError) as exception:
        vectra_entity_tag_add_command(client, args)

    assert str(exception.value) == error_msg


def test_vectra_entity_tag_add_when_get_tag_response_is_invalid(mocker, client):
    """
    Given:
    - A client object.
    - Mocked 'list_entity_tags_request' method returning invalid response.
    - Arguments specifying invalid tags.

    When:
    - Calling the 'vectra_entity_tag_add_command' function with the provided client and arguments.

    Then:
    - Assert that the human-readable output indicates that invalid result was found.
    """
    get_tags_res = util_load_json(f"{TEST_DATA_DIR}/entity_tag_get_invalid_response.json")
    mocker.patch.object(client, "list_entity_tags_request", return_value=get_tags_res)
    args = {
        "entity_id": "1",
        "entity_type": "host",
        "tags": "tag1, tag2",
    }
    # Call the function
    with pytest.raises(DemistoException) as exception:
        vectra_entity_tag_add_command(client, args)

    assert str(exception.value) == f"Something went wrong. Message: {get_tags_res.get('message')}."


def test_vectra_entity_tag_add_when_add_tag_response_is_invalid(mocker, client):
    """
    Given:
    - A client object.
    - Mocked 'update_entity_tags_request' method returning invalid response.
    - Arguments specifying invalid tags.

    When:
    - Calling the 'vectra_entity_tag_add_command' function with the provided client and arguments.

    Then:
    - Assert that the human-readable output indicates that invalid result was found.
    """
    get_tags_res = util_load_json(f"{TEST_DATA_DIR}/entity_tag_get_response.json")
    add_tags_res = util_load_json(f"{TEST_DATA_DIR}/entity_tag_add_invalid_response.json")
    mocker.patch.object(client, "update_entity_tags_request", return_value=add_tags_res)
    mocker.patch.object(client, "list_entity_tags_request", return_value=get_tags_res)
    args = {
        "entity_id": "1",
        "entity_type": "host",
        "tags": "tag1, tag2",
    }
    # Call the function
    with pytest.raises(DemistoException) as exception:
        vectra_entity_tag_add_command(client, args)

    assert str(exception.value) == f"Something went wrong. Message: {add_tags_res.get('message')}."


def test_vectra_entity_tag_remove_valid_arguments(mocker, client):
    """
    Given:
    - A client object.
    - Mocked 'update_entity_tags_request' method returning tags response.
    - The expected human-readable output file.
    - Arguments specifying valid parameters for adding the tags to an entity.

    When:
    - Calling the 'vectra_entity_tag_remove_command' function with the provided client and arguments.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert the correctness of the 'outputs_prefix' property.
    - Assert that the human-readable output matches the content of the expected file.
    - Assert that the 'Contents' property in the context matches the tags response.
    - Assert that the 'EntryContext' property in the context matches the context data.
    - Assert the correctness of the 'outputs_key_field' property.
    """
    add_tags_res = util_load_json(f"{TEST_DATA_DIR}/entity_tag_remove_response.json")
    get_tags_res = util_load_json(f"{TEST_DATA_DIR}/entity_tag_get_response_2.json")
    context_data = util_load_json(f"{TEST_DATA_DIR}/entity_tag_remove_context.json")
    mocker.patch.object(client, "update_entity_tags_request", return_value=add_tags_res)
    mocker.patch.object(client, "list_entity_tags_request", return_value=get_tags_res)
    with open(f"{TEST_DATA_DIR}/entity_tag_remove_hr.md") as f:
        result_hr = f.read()
    args = {"entity_id": "1", "entity_type": "host", "tags": "tag2"}

    # Call the function
    result = vectra_entity_tag_remove_command(client, args)
    result_context = result.to_context()
    add_tags_res.update({"entity_id": 1, "entity_type": "host"})
    # Assert the CommandResults
    assert result.outputs_prefix == "Vectra.Entity.Tags"
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("Contents") == add_tags_res
    assert result_context.get("EntryContext") == remove_empty_elements(context_data)
    assert result.outputs_key_field == ["tag_id", "entity_type", "entity_id"]


@pytest.mark.parametrize(
    "args,error_msg",
    [
        (
            {"entity_id": "1", "entity_type": "invalid_type"},
            ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)),
        ),
        ({"entity_id": "1", "entity_type": "account"}, ERRORS["REQUIRED_ARGUMENT"].format("tags")),
        ({"entity_id": "1", "entity_type": "account", "tags": " , "}, ERRORS["REQUIRED_ARGUMENT"].format("tags")),
        ({"entity_type": "account"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")),
        ({"entity_id": "0", "entity_type": "account"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "0")),
        ({"entity_id": "-1", "entity_type": "account"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "-1")),
        ({"entity_id": "1.5", "entity_type": "account"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "1.5")),
    ],
)
def test_vectra_entity_tag_remove_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Arguments specifying different invalid values for entity_id and entity_type.

    When:
    - Calling the 'vectra_entity_tag_remove_command' function with the provided client and arguments.

    Then:
    - Assert that the function raises a ValueError.
    - Assert that the error message matches the expected error message for each invalid argument.
    """
    # Call the function and assert that it raises ValueError
    with pytest.raises(ValueError) as exception:
        vectra_entity_tag_remove_command(client, args)

    assert str(exception.value) == error_msg


def test_vectra_entity_tag_remove_when_get_tag_response_is_invalid(mocker, client):
    """
    Given:
    - A client object.
    - Mocked 'list_entity_tags_request' method returning invalid response.
    - Arguments specifying invalid tags.

    When:
    - Calling the 'vectra_entity_tag_remove_command' function with the provided client and arguments.

    Then:
    - Assert that the human-readable output indicates that invalid result was found.
    """
    get_tags_res = util_load_json(f"{TEST_DATA_DIR}/entity_tag_get_invalid_response.json")
    mocker.patch.object(client, "list_entity_tags_request", return_value=get_tags_res)
    args = {
        "entity_id": "1",
        "entity_type": "host",
        "tags": "tag2",
    }
    # Call the function
    with pytest.raises(DemistoException) as exception:
        vectra_entity_tag_remove_command(client, args)

    assert str(exception.value) == f"Something went wrong. Message: {get_tags_res.get('message')}."


def test_vectra_entity_tag_remove_when_add_tag_response_is_invalid(mocker, client):
    """
    Given:
    - A client object.
    - Mocked 'update_entity_tags_request' method returning invalid response.
    - Arguments specifying invalid tags.

    When:
    - Calling the 'vectra_entity_tag_remove_command' function with the provided client and arguments.

    Then:
    - Assert that the human-readable output indicates that invalid result was found.
    """
    get_tags_res = util_load_json(f"{TEST_DATA_DIR}/entity_tag_get_response_2.json")
    add_tags_res = util_load_json(f"{TEST_DATA_DIR}/entity_tag_add_invalid_response.json")
    mocker.patch.object(client, "update_entity_tags_request", return_value=add_tags_res)
    mocker.patch.object(client, "list_entity_tags_request", return_value=get_tags_res)
    args = {
        "entity_id": "1",
        "entity_type": "host",
        "tags": "tag2",
    }
    # Call the function
    with pytest.raises(DemistoException) as exception:
        vectra_entity_tag_remove_command(client, args)

    assert str(exception.value) == f"Something went wrong. Message: {add_tags_res.get('message')}."


def test_vectra_entity_tag_list_valid_arguments(requests_mock, client):
    """
    Given:
    - A client object.
    - Mocked 'list_entity_tags_request' method returning tags response.
    - The expected human-readable output file.
    - Arguments specifying valid parameters for adding the tags to an entity.

    When:
    - Calling the 'vectra_entity_tag_list_command' function with the provided client and arguments.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert the correctness of the 'outputs_prefix' property.
    - Assert that the human-readable output matches the content of the expected file.
    - Assert that the 'Contents' property in the context matches the tags response.
    - Assert that the 'EntryContext' property in the context matches the context data.
    - Assert the correctness of the 'outputs_key_field' property.
    """
    get_tags_res = util_load_json(f"{TEST_DATA_DIR}/entity_tag_get_response_2.json")
    context_data = util_load_json(f"{TEST_DATA_DIR}/entity_tag_list_context.json")
    requests_mock.get(BASE_URL + ENDPOINTS["ENTITY_TAG_ENDPOINT"].format(1), json=get_tags_res)
    with open(f"{TEST_DATA_DIR}/entity_tag_list_hr.md") as f:
        result_hr = f.read()
    args = {"entity_id": "1", "entity_type": "host"}

    # Call the function
    result = vectra_entity_tag_list_command(client, args)
    result_context = result.to_context()
    get_tags_res.update({"entity_id": 1, "entity_type": "host"})
    del get_tags_res["status"]
    # Assert the CommandResults
    assert result.outputs_prefix == "Vectra.Entity.Tags"
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("Contents") == get_tags_res
    assert result_context.get("EntryContext") == remove_empty_elements(context_data)
    assert result.outputs_key_field == ["tag_id", "entity_type", "entity_id"]


def test_vectra_entity_tag_list_with_empty_tag_response(mocker, client):
    """
    Given:
    - A client object.
    - Mocked 'update_entity_tags_request' method returning tags response.
    - The expected human-readable output file.
    - Arguments specifying valid parameters for adding the tags to an entity.

    When:
    - Calling the 'list_entity_tags_request' function with the provided client and arguments.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert the correctness of the 'outputs_prefix' property.
    - Assert that the human-readable output matches the content of the expected file.
    - Assert that the 'Contents' property in the context matches the tags response.
    - Assert that the 'EntryContext' property in the context matches the context data.
    - Assert the correctness of the 'outputs_key_field' property.
    """
    get_tags_res = util_load_json(f"{TEST_DATA_DIR}/entity_tag_get_response_2.json")
    get_tags_res["tags"] = []
    context_data = util_load_json(f"{TEST_DATA_DIR}/entity_tag_empty_list_context.json")
    mocker.patch.object(client, "list_entity_tags_request", return_value=get_tags_res)
    with open(f"{TEST_DATA_DIR}/entity_tag_empty_list_hr.md") as f:
        result_hr = f.read()
    args = {"entity_id": "1", "entity_type": "host"}

    # Call the function
    result = vectra_entity_tag_list_command(client, args)
    result_context = result.to_context()
    get_tags_res.update({"entity_id": 1, "entity_type": "host"})
    # Assert the CommandResults
    assert result.outputs_prefix == "Vectra.Entity.Tags"
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("Contents") == get_tags_res
    assert result_context.get("EntryContext") == remove_empty_elements(context_data)
    assert result.outputs_key_field == ["tag_id", "entity_type", "entity_id"]


@pytest.mark.parametrize(
    "args,error_msg",
    [
        (
            {"entity_id": "1", "entity_type": "invalid_type"},
            ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)),
        ),
        ({"entity_type": "account"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")),
        ({"entity_id": "0", "entity_type": "account"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "0")),
        ({"entity_id": "-1", "entity_type": "account"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "-1")),
        ({"entity_id": "1.5", "entity_type": "account"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "1.5")),
        ({"entity_id": "1", "entity_type": ""}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type")),
    ],
)
def test_vectra_entity_tag_list_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Arguments specifying different invalid values for entity_id, entity_type, and tags.

    When:
    - Calling the 'vectra_entity_tag_list_command' function with the provided client and arguments.

    Then:
    - Assert that the function raises a ValueError.
    - Assert that the error message matches the expected error message for each invalid argument.
    """
    # Call the function and assert that it raises ValueError
    with pytest.raises(ValueError) as exception:
        vectra_entity_tag_list_command(client, args)

    assert str(exception.value) == error_msg


def test_vectra_entity_tag_list_when_response_is_invalid(mocker, client):
    """
    Given:
    - A client object.
    - Mocked 'list_entity_tags_request' method returning invalid response.
    - Arguments specifying invalid tags.

    When:
    - Calling the 'vectra_entity_tag_list_command' function with the provided client and arguments.

    Then:
    - Assert that the human-readable output indicates that invalid result was found.
    """
    get_tags_res = util_load_json(f"{TEST_DATA_DIR}/entity_tag_get_invalid_response.json")
    mocker.patch.object(client, "list_entity_tags_request", return_value=get_tags_res)
    args = {
        "entity_id": "1",
        "entity_type": "host",
        "tags": "tag1, tag2",
    }
    # Call the function
    with pytest.raises(DemistoException) as exception:
        vectra_entity_tag_list_command(client, args)

    assert str(exception.value) == f"Something went wrong. Message: {get_tags_res.get('message')}."


def test_vectra_entity_assignment_add_valid_arguments(requests_mock, client):
    """
    Given:
    - A client object.
    - Mocked 'add_entity_assignment_request' method returning assignment data.
    - The expected human-readable output file.
    - Arguments specifying valid parameters for adding an assignment.

    When:
    - Calling the 'vectra_entity_assignment_add_command' function with the provided client and arguments.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert the correctness of the 'outputs_prefix' property.
    - Assert that the human-readable output matches the content of the expected file.
    - Assert that the 'Contents' property in the context matches the assignment data.
    - Assert that the 'EntryContext' property in the context matches the expected context data.
    - Assert the correctness of the 'outputs_key_field' property.
    """
    assignment_res = util_load_json(f"{TEST_DATA_DIR}/entity_assignment_add_response.json")
    context_data = util_load_json(f"{TEST_DATA_DIR}/entity_assignment_add_context.json")
    with open(f"{TEST_DATA_DIR}/entity_assignment_add_account_hr.md") as f:
        result_hr = f.read()
    args = {"entity_id": "1", "entity_type": "account", "user_id": "1"}
    requests_mock.post(BASE_URL + ENDPOINTS["ASSIGNMENT_ENDPOINT"], json=assignment_res[0])
    # Call the function
    result = vectra_entity_assignment_add_command(client, args)
    result_context = result.to_context()
    # Assert the CommandResults
    assert result.outputs_prefix == "Vectra.Entity.Assignments"
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("EntryContext") == context_data[0]
    assert result.outputs_key_field == ["assignment_id"]
    # For Host
    with open(f"{TEST_DATA_DIR}/entity_assignment_add_host_hr.md") as f:
        result_hr = f.read()
    args = {"entity_id": "1", "entity_type": "host", "user_id": "3"}

    requests_mock.post(BASE_URL + ENDPOINTS["ASSIGNMENT_ENDPOINT"], json=assignment_res[1])
    # Call the function
    result = vectra_entity_assignment_add_command(client, args)
    result_context = result.to_context()
    # Assert the CommandResults
    assert result.outputs_prefix == "Vectra.Entity.Assignments"
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("EntryContext") == context_data[1]
    assert result.outputs_key_field == ["assignment_id"]


@pytest.mark.parametrize(
    "args,error_msg",
    [
        (
            {"entity_id": "1", "entity_type": "invalid_type", "user_id": "1"},
            ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)),
        ),
        ({"entity_id": "1", "entity_type": "account"}, ERRORS["REQUIRED_ARGUMENT"].format("user_id")),
        ({"entity_type": "account", "note": "test_note"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")),
        (
            {"entity_id": "0", "entity_type": "account", "note": "test_note"},
            ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "0"),
        ),
        (
            {"entity_id": "-1", "entity_type": "account", "note": "test_note"},
            ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "-1"),
        ),
        (
            {"entity_id": "1.5", "entity_type": "account", "note": "test_note"},
            ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "1.5"),
        ),
        ({"entity_id": "1", "entity_type": "account", "user_id": "0"}, ERRORS["INVALID_INTEGER_VALUE"].format("user_id", "0")),
        ({"entity_id": "1", "entity_type": "account", "user_id": "-1"}, ERRORS["INVALID_INTEGER_VALUE"].format("user_id", "-1")),
        (
            {"entity_id": "1", "entity_type": "account", "user_id": "1.5"},
            ERRORS["INVALID_INTEGER_VALUE"].format("user_id", "1.5"),
        ),
        (
            {"entity_id": "1", "entity_type": "", "user_id": "1"},
            ERRORS["REQUIRED_ARGUMENT"].format("entity_type"),
        ),
    ],
)
def test_vectra_entity_assignment_add_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Arguments specifying different invalid values for entity_id, entity_type, and user_id.

    When:
    - Calling the 'vectra_entity_assignment_add_command' function with the provided client and arguments.

    Then:
    - Assert that the function raises a ValueError.
    - Assert that the error message matches the expected error message for each invalid argument.
    """
    # Call the function and assert that it raises ValueError
    with pytest.raises(ValueError) as exception:
        vectra_entity_assignment_add_command(client, args)

    assert str(exception.value) == error_msg


def test_vectra_entity_assignment_update_valid_arguments(requests_mock, client):
    """
    Given:
    - A mocked client for requests.
    - A mock entity assignment update response.
    - The expected human-readable output file.
    - Arguments specifying valid parameters for updating an entity assignment.

    When:
    - Calling the 'vectra_entity_assignment_update_command' function with the provided client and arguments.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert the correctness of the 'outputs_prefix' property.
    - Assert that the human-readable output matches the content of the expected file.
    - Assert that the 'Contents' property in the context matches the assignment data.
    - Assert that the 'EntryContext' property in the context matches the expected context data.
    - Assert the correctness of the 'outputs_key_field' property.
    """
    assignment_res = util_load_json(f"{TEST_DATA_DIR}/entity_assignment_update_response.json")
    context_data = util_load_json(f"{TEST_DATA_DIR}/entity_assignment_update_context.json")
    requests_mock.put(BASE_URL + ENDPOINTS["UPDATE_ASSIGNMENT_ENDPOINT"].format(1), json=assignment_res)
    with open(f"{TEST_DATA_DIR}/entity_assignment_update_hr.md") as f:
        result_hr = f.read()
    args = {"assignment_id": "1", "user_id": "2"}

    # Call the function
    result = vectra_entity_assignment_update_command(client, args)
    result_context = result.to_context()
    assignment_res.get("assignment")["assignment_id"] = 1
    # Assert the CommandResults
    assert result.outputs_prefix == "Vectra.Entity.Assignments"
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("Contents") == assignment_res.get("assignment")
    assert result_context.get("EntryContext") == context_data
    assert result.outputs_key_field == ["assignment_id"]


@pytest.mark.parametrize(
    "args,error_msg",
    [
        ({"assignment_id": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("user_id")),
        ({"user_id": "2"}, ERRORS["REQUIRED_ARGUMENT"].format("assignment_id")),
        ({"assignment_id": "0", "user_id": "2"}, ERRORS["INVALID_INTEGER_VALUE"].format("assignment_id", "0")),
        ({"assignment_id": "-1", "user_id": "2"}, ERRORS["INVALID_INTEGER_VALUE"].format("assignment_id", "-1")),
        ({"assignment_id": "1.5", "user_id": "2"}, ERRORS["INVALID_INTEGER_VALUE"].format("assignment_id", "1.5")),
        ({"user_id": "0", "assignment_id": "2"}, ERRORS["INVALID_INTEGER_VALUE"].format("user_id", "0")),
        ({"user_id": "-1", "assignment_id": "2"}, ERRORS["INVALID_INTEGER_VALUE"].format("user_id", "-1")),
        ({"user_id": "1.5", "assignment_id": "2"}, ERRORS["INVALID_INTEGER_VALUE"].format("user_id", "1.5")),
    ],
)
def test_vectra_entity_assignment_update_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Arguments specifying different invalid values for assignment_id and user_id.

    When:
    - Calling the 'vectra_entity_assignment_update_command' function with the provided client and arguments.

    Then:
    - Assert that the function raises ValueError.
    - Assert that the error message matches the expected error message for each invalid argument.
    """
    # Call the function and assert that it raises ValueError
    with pytest.raises(ValueError) as exception:
        vectra_entity_assignment_update_command(client, args)

    assert str(exception.value) == error_msg


def test_vectra_detection_pcap_download_valid_arguments(requests_mock, client):
    """
    Given:
    - A client object.
    - Mocked GET request for downloading PCAP data of a detection.
    - The expected binary file content of the PCAP.
    - Arguments specifying a valid detection ID for downloading PCAP.

    When:
    - Calling the 'vectra_detection_pcap_download_command' function with the provided client and arguments.

    Then:
    - Assert that the result contains the expected binary file content.
    """
    mock_file_content = b"PCAP data of detection id 1431"
    args = {"detection_id": "1431"}
    requests_mock.get(
        BASE_URL + ENDPOINTS["DOWNLOAD_DETECTION_PCAP"].format("1431"),
        content=mock_file_content,
        headers={"Content-Disposition": 'attachement;filename="IP-1.1.1.1_hidden_dns_tunnel_1431.pcap"'},
    )
    # Call the function
    result = vectra_detection_pcap_download_command(client, args)

    # Assert the CommandResults
    assert result.get("File") == "IP-1.1.1.1_hidden_dns_tunnel_1431.pcap"


@pytest.mark.parametrize(
    "args,error_msg",
    [
        ({}, ERRORS["REQUIRED_ARGUMENT"].format("detection_id")),
        ({"detection_id": "as,2"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "as,2")),
        ({"detection_id": "1.5"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "1.5")),
        ({"detection_id": "-1"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "-1")),
    ],
)
def test_vectra_detection_pcap_download_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Arguments specifying different invalid values for detection_id.

    When:
    - Calling the 'vectra_detection_pcap_download_command' function with the provided client and arguments.

    Then:
    - Assert that the function raises a ValueError.
    - Assert that the error message matches the expected error message for each invalid argument.
    """
    # Call the function and assert that it raises ValueError
    with pytest.raises(ValueError) as exception:
        vectra_detection_pcap_download_command(client, args)

    assert str(exception.value) == error_msg


def test_vectra_assignment_list_valid_arguments(requests_mock, client):
    """
    Given:
    - A mocked client for requests.
    - A mock assignment response.
    - Expected context data and human-readable output.

    When:
    - Calling the 'vectra_assignment_list_command' function with the provided client and no additional arguments.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert the correctness of the 'outputs_prefix' property.
    - Assert that the human-readable output matches the content of the expected file.
    - Assert that the 'Contents' property in the context matches the assignment data.
    - Assert that the 'EntryContext' property in the context matches the expected context data.
    - Assert the correctness of the 'outputs_key_field' property.
    """
    assignment_res = util_load_json(f"{TEST_DATA_DIR}/assignment_list_response.json")
    context_data = util_load_json(f"{TEST_DATA_DIR}/assignment_list_context.json")
    with open(f"{TEST_DATA_DIR}/assignment_list_hr.md") as f:
        result_hr = f.read()
    requests_mock.get(BASE_URL + ENDPOINTS["ASSIGNMENT_ENDPOINT"], json=assignment_res)
    # Call the function
    result = vectra_assignment_list_command(client, {"entity_type": "host", "entity_ids": "1"})
    result_context = result.to_context()
    # Assert the CommandResults
    assert result.outputs_prefix == "Vectra.Entity.Assignments"
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("EntryContext") == context_data
    assert result.outputs_key_field == ["assignment_id"]


def test_vectra_assignment_list_when_assignment_response_is_empty(requests_mock, client):
    """
    Given:
    - A mocked client for requests.
    - An empty assignment response.

    When:
    - Calling the 'vectra_assignment_list_command' function with the provided empty assignment response.

    Then:
    - Assert that the CommandResults object contains the appropriate human-readable output for empty results.
    - Assert that the EntryContext is empty.
    """
    empty_response: dict = {"count": 0, "next": None, "previous": None, "results": []}
    args = {"resolved": "False", "created_after": "1 day", "entity_type": "account", "entity_ids": "1"}
    requests_mock.get(BASE_URL + ENDPOINTS["ASSIGNMENT_ENDPOINT"], json=empty_response)
    # Call the function
    result = vectra_assignment_list_command(client, args)
    result_context = result.to_context()
    # Assert the CommandResults
    assert result_context.get("HumanReadable") == "##### Couldn't find any matching assignments for provided filters."
    assert result_context.get("EntryContext") == {}


@pytest.mark.parametrize(
    "args,error_msg",
    [
        ({"entity_type": "account"}, ERRORS["ENTITY_IDS_WITHOUT_TYPE"]),
        ({"entity_ids": "1,2"}, ERRORS["ENTITY_IDS_WITHOUT_TYPE"]),
        (
            {"entity_ids": "1", "entity_type": "invalid_type"},
            ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)),
        ),
    ],
)
def test_vectra_assignment_list_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Arguments specifying invalid parameters for listing assignments.

    When:
    - Calling the 'vectra_assignment_list_command' function with the provided client and arguments.

    Then:
    - Assert that the function raises a ValueError.
    - Assert that the raised error message matches the expected error message.
    """
    # Call the function and assert that it raises ValueError
    with pytest.raises(ValueError) as exception:
        vectra_assignment_list_command(client, args)

    assert str(exception.value) == error_msg


def test_vectra_entity_note_list_valid_arguments(requests_mock, client):
    """
    Given:
    - A mocked 'requests_mock' to simulate API responses.
    - A client object.
    - Mocked entity note list response data.
    - Mocked context data.

    When:
    - Calling the 'vectra_entity_note_list_command' function with valid arguments.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert the correctness of the 'outputs_prefix' property.
    - Assert that the human-readable output matches the content of the expected file.
    - Assert that the 'Contents' property in the context matches the entity note list data.
    - Assert that the 'EntryContext' property in the context matches the expected context data.
    - Assert the correctness of the 'outputs_key_field' property.
    """
    notes_res = util_load_json(f"{TEST_DATA_DIR}/entity_note_list_response.json")
    context_data = util_load_json(f"{TEST_DATA_DIR}/entity_note_list_context.json")
    with open(f"{TEST_DATA_DIR}/entity_note_list_hr.md") as f:
        result_hr = f.read()
    args = {
        "entity_id": "1",
        "entity_type": "account",
    }
    url = BASE_URL + ENDPOINTS["ADD_AND_LIST_ENTITY_NOTE_ENDPOINT"].format(args.get("entity_id"))
    params = {"type": args.get("entity_type")}
    final_url = add_params_in_url(url, params)
    requests_mock.get(final_url, json=notes_res)
    # Call the function
    result = vectra_entity_note_list_command(client, args)
    result_context = result.to_context()
    # Assert the CommandResults
    assert result.outputs_prefix == "Vectra.Entity.Notes"
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("Contents") == notes_res
    assert result_context.get("EntryContext") == remove_empty_elements(context_data)
    assert result.outputs_key_field == ["entity_id", "entity_type", "note_id"]


@pytest.mark.parametrize(
    "args,error_msg",
    [
        (
            {"entity_id": "1", "entity_type": "invalid_type"},
            ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)),
        ),
        ({"entity_type": "account"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")),
        ({"entity_id": "0", "entity_type": "account"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "0")),
        ({"entity_id": "-1", "entity_type": "account"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "-1")),
        ({"entity_id": "1.5", "entity_type": "account"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "1.5")),
        ({"entity_id": "1", "entity_type": ""}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type")),
    ],
)
def test_vectra_entity_note_list_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Arguments specifying different invalid values for entity_id and entity_type.

    When:
    - Calling the 'vectra_entity_note_list_command' function with the provided client and arguments.

    Then:
    - Assert that the function raises a ValueError.
    - Assert that the error message matches the expected error message for each invalid argument.
    """
    # Call the function and assert that it raises ValueError
    with pytest.raises(ValueError) as exception:
        vectra_entity_note_list_command(client, args)

    assert str(exception.value) == error_msg


def test_vectra_entity_note_list_when_note_response_is_empty(requests_mock, client):
    """
    Given:
    - A mocked client for requests.
    - An empty assignment response.

    When:
    - Calling the 'vectra_assignment_list_command' function with the provided empty assignment response.

    Then:
    - Assert that the CommandResults object contains the appropriate human-readable output for empty results.
    - Assert that the EntryContext is empty.
    """
    empty_response: list = []
    args = {
        "entity_id": "1",
        "entity_type": "account",
    }
    url = BASE_URL + ENDPOINTS["ADD_AND_LIST_ENTITY_NOTE_ENDPOINT"].format(args.get("entity_id"))
    params = {"type": args.get("entity_type")}
    final_url = add_params_in_url(url, params)
    requests_mock.get(final_url, json=empty_response)
    # Call the function
    result = vectra_entity_note_list_command(client, args)
    result_context = result.to_context()
    # Assert the CommandResults
    assert result_context.get("HumanReadable") == "##### Couldn't find any notes for provided entity."
    assert result_context.get("EntryContext") == {}


def test_vectra_group_list_valid_arguments(requests_mock, client):
    """
    Given:
    - A mocked client for requests.
    - A mock assignment response.
    - Expected context data and human-readable output.

    When:
    - Calling the 'vectra_group_list_command' function with the provided client and arguments.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert that the human-readable output indicates that no results were found.
    - Assert that the 'EntryContext' property in the context is an empty dictionary.
    """
    group_res = util_load_json(f"{TEST_DATA_DIR}/group_list_response.json")
    context_data = util_load_json(f"{TEST_DATA_DIR}/group_list_context.json")
    with open(f"{TEST_DATA_DIR}/group_list_hr.md") as f:
        result_hr = f.read()
    requests_mock.get(BASE_URL + ENDPOINTS["GROUP_ENDPOINT"], json=group_res)
    args = {"group_type": "account", "importance": "high"}
    # Call the function
    result = vectra_group_list_command(client, args)
    result_context = result.to_context()
    # Assert the CommandResults
    assert result.outputs_prefix == "Vectra.Group"
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("EntryContext") == context_data
    assert result.outputs_key_field == ["group_id"]


def test_vectra_group_list_when_response_is_empty(mocker, client):
    """
    Given:
    - A client object.
    - Mocked 'list_group_request' method returning an empty response.
    - Arguments specifying invalid tags.

    When:
    - Calling the 'vectra_group_list_command' function with the provided client and arguments.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert that the human-readable output indicates that no results were found.
    - Assert that the 'EntryContext' property in the context is an empty dictionary.
    """
    empty_response: dict = {"count": 0, "next": None, "previous": None, "results": []}
    mocker.patch.object(client, "list_group_request", return_value=empty_response)

    # Call the function
    result = vectra_group_list_command(client, {})
    result_context = result.to_context()
    # Assert the CommandResults
    assert result_context.get("HumanReadable") == "##### Couldn't find any matching groups for provided filters."
    assert result_context.get("EntryContext") == {}


@pytest.mark.parametrize(
    "args,error_msg",
    [
        ({"group_type": "invalid"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("group_type", ", ".join(VALID_GROUP_TYPE))),
        (
            {"group_type": "host", "account_names": "account_name"},
            ERRORS["INVALID_SUPPORT_FOR_ARG"].format("group_type", "account", "account_names"),
        ),
        (
            {"group_type": "host", "domains": "domain"},
            ERRORS["INVALID_SUPPORT_FOR_ARG"].format("group_type", "domain", "domains"),
        ),
        ({"group_type": "account", "host_ids": "1"}, ERRORS["INVALID_SUPPORT_FOR_ARG"].format("group_type", "host", "host_ids")),
        ({"group_type": "host", "host_ids": "abc"}, 'Invalid number: "{}"="{}"'.format("host_ids", "abc")),
        ({"group_type": "host", "host_ids": "-1"}, ERRORS["INVALID_INTEGER_VALUE"].format("host_ids", "-1")),
        (
            {"group_type": "account", "host_names": "host_name"},
            ERRORS["INVALID_SUPPORT_FOR_ARG"].format("group_type", "host", "host_names"),
        ),
        ({"group_type": "host", "ips": "0.0.0.0"}, ERRORS["INVALID_SUPPORT_FOR_ARG"].format("group_type", "ip", "ips")),
        ({"importance": "invalid"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("importance", ", ".join(VALID_IMPORTANCE_VALUE))),
    ],
)
def test_vectra_group_list_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Arguments specifying invalid values.

    When:
    - Calling the 'vectra_group_list_command' function with the provided client and arguments.

    Then:
    - Assert that the function raises a ValueError.
    - Assert that the error message matches the expected value for the corresponding invalid argument.
    """
    # Call the function and assert that it raises ValueError
    with pytest.raises(ValueError) as exception:
        vectra_group_list_command(client, args)

    assert str(exception.value) == error_msg


def test_vectra_assign_domain_group_valid_arguments(requests_mock, client):
    """
    Given:
    - A client object.
    - Mocked response data for an existing group.
    - Arguments specifying a group ID and members to assign.

    When:
    - Calling the 'vectra_group_assign_command' function with the provided client and arguments.

    Then: - Assert that the result's human-readable output, context data, and key field match the expected values for
    domain type.
    """
    assign_group_res = util_load_json(f"{TEST_DATA_DIR}/assign_group_response.json")
    groups = util_load_json(f"{TEST_DATA_DIR}/get_groups_response.json")
    context_data = util_load_json(f"{TEST_DATA_DIR}/assign_group_context.json")
    # For Domain group
    with open(f"{TEST_DATA_DIR}/assign_domain_group_hr.md") as f:
        result_hr = f.read()
    args = {"group_id": "1", "members": "*.domain3.com,*.domain2.com"}
    requests_mock.get(BASE_URL + "{}/{}".format(ENDPOINTS["GROUP_ENDPOINT"], args.get("group_id")), json=groups[0])
    requests_mock.patch(BASE_URL + "{}/{}".format(ENDPOINTS["GROUP_ENDPOINT"], args.get("group_id")), json=assign_group_res[0])
    # Call the function
    result = vectra_group_assign_command(client, args)
    result_context = result.to_context()
    # Assert the CommandResults
    assert result.outputs_prefix == "Vectra.Group"
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("EntryContext") == context_data[0]
    assert result.outputs_key_field == ["group_id"]


def test_vectra_assign_account_group_valid_arguments(requests_mock, client):
    """
    Given:
    - A client object.
    - Mocked response data for an existing group.
    - Arguments specifying a group ID and members to assign.

    When:
    - Calling the 'vectra_group_assign_command' function with the provided client and arguments.

    Then: - Assert that the result's human-readable output, context data, and key field match the expected values for
    account type.
    """
    assign_group_res = util_load_json(f"{TEST_DATA_DIR}/assign_group_response.json")
    groups = util_load_json(f"{TEST_DATA_DIR}/get_groups_response.json")
    context_data = util_load_json(f"{TEST_DATA_DIR}/assign_group_context.json")
    # For Account group
    with open(f"{TEST_DATA_DIR}/assign_account_group_hr.md") as f:
        result_hr = f.read()
    args = {"group_id": "3", "members": "account_3,account_4"}
    requests_mock.get(BASE_URL + "{}/{}".format(ENDPOINTS["GROUP_ENDPOINT"], args.get("group_id")), json=groups[2])
    requests_mock.patch(BASE_URL + "{}/{}".format(ENDPOINTS["GROUP_ENDPOINT"], args.get("group_id")), json=assign_group_res[2])
    # Call the function
    result = vectra_group_assign_command(client, args)
    result_context = result.to_context()
    # Assert the CommandResults
    assert result.outputs_prefix == "Vectra.Group"
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("EntryContext") == context_data[2]
    assert result.outputs_key_field == ["group_id"]


def test_vectra_assign_host_group_valid_arguments(requests_mock, client):
    """
    Given:
    - A client object.
    - Mocked response data for an existing group.
    - Arguments specifying a group ID and members to assign.

    When:
    - Calling the 'vectra_group_assign_command' function with the provided client and arguments.

    Then: - Assert that the result's human-readable output, context data, and key field match the expected values for
    host type.
    """
    assign_group_res = util_load_json(f"{TEST_DATA_DIR}/assign_group_response.json")
    groups = util_load_json(f"{TEST_DATA_DIR}/get_groups_response.json")
    context_data = util_load_json(f"{TEST_DATA_DIR}/assign_group_context.json")
    # For Host group
    with open(f"{TEST_DATA_DIR}/assign_host_group_hr.md") as f:
        result_hr = f.read()
    args = {"group_id": "2", "members": "1,2"}
    requests_mock.get(BASE_URL + "{}/{}".format(ENDPOINTS["GROUP_ENDPOINT"], args.get("group_id")), json=groups[1])
    requests_mock.patch(BASE_URL + "{}/{}".format(ENDPOINTS["GROUP_ENDPOINT"], args.get("group_id")), json=assign_group_res[1])
    # Call the function
    result = vectra_group_assign_command(client, args)
    result_context = result.to_context()
    # Assert the CommandResults
    assert result.outputs_prefix == "Vectra.Group"
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("EntryContext") == context_data[1]
    assert result.outputs_key_field == ["group_id"]


def test_vectra_assign_member_already_exist(requests_mock, client):
    """
    Given:
    - A client object.
    - Mocked response data for an existing group.
    - Arguments specifying a group ID and members to assign.

    When:
    - Calling the 'vectra_group_assign_command' function with the provided client and arguments.

    Then:
    - Assert that the result's human-readable output indicates that the members are already in the group.
    """
    groups = util_load_json(f"{TEST_DATA_DIR}/assign_group_response.json")

    args = {"group_id": "2", "members": "1,2"}
    requests_mock.get(BASE_URL + "{}/{}".format(ENDPOINTS["GROUP_ENDPOINT"], args.get("group_id")), json=groups[1])

    # Call the function
    result = vectra_group_assign_command(client, args)
    result_context = result.to_context()
    # Assert the CommandResults
    assert result_context.get("HumanReadable") == "##### Member(s) 1, 2 are already in the group."


@pytest.mark.parametrize(
    "args,error_msg",
    [
        ({"members": "account1"}, ERRORS["REQUIRED_ARGUMENT"].format("group_id")),
        ({"group_id": "0", "members": "account1"}, ERRORS["INVALID_INTEGER_VALUE"].format("group_id", "0")),
        ({"group_id": "-1", "members": "account1"}, ERRORS["INVALID_INTEGER_VALUE"].format("group_id", "-1")),
        ({"group_id": "1.5", "members": "account1"}, ERRORS["INVALID_INTEGER_VALUE"].format("group_id", "1.5")),
        ({"group_id": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("members")),
    ],
)
def test_vectra_group_assign_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Arguments specifying invalid parameters for assigning members to a group.

    When:
    - Calling the 'vectra_group_assign_command' function with the provided client and arguments.

    Then:
    - Assert that the function raises a ValueError.
    - Assert that the raised error message matches the expected error message.
    """
    # Call the function and assert that it raises ValueError
    with pytest.raises(ValueError) as exception:
        vectra_group_assign_command(client, args)

    assert str(exception.value) == error_msg


def test_vectra_unassign_domain_group_valid_arguments(requests_mock, client):
    """
    Given:
    - A client object.
    - Mocked response data for an existing group.
    - Arguments specifying a group ID and members to assign.

    When:
    - Calling the 'vectra_group_unassign_command' function with the provided client and arguments.

    Then: - Assert that the result's human-readable output, context data, and key field match the expected values for
    domain type.
    """
    unassign_group_res = util_load_json(f"{TEST_DATA_DIR}/unassign_group_response.json")
    groups = util_load_json(f"{TEST_DATA_DIR}/get_groups_response.json")
    context_data = util_load_json(f"{TEST_DATA_DIR}/unassign_group_context.json")
    # For Domain group
    with open(f"{TEST_DATA_DIR}/unassign_domain_group_hr.md") as f:
        result_hr = f.read()
    args = {"group_id": "1", "members": "*.domain1.net"}
    requests_mock.get(BASE_URL + "{}/{}".format(ENDPOINTS["GROUP_ENDPOINT"], args.get("group_id")), json=groups[0])
    requests_mock.patch(BASE_URL + "{}/{}".format(ENDPOINTS["GROUP_ENDPOINT"], args.get("group_id")), json=unassign_group_res[0])
    # Call the function
    result = vectra_group_unassign_command(client, args)
    result_context = result.to_context()
    # Assert the CommandResults
    assert result.outputs_prefix == "Vectra.Group"
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("EntryContext") == context_data[0]
    assert result.outputs_key_field == ["group_id"]


def test_vectra_unassign_host_group_valid_arguments(requests_mock, client):
    """
    Given:
    - A client object.
    - Mocked response data for an existing group.
    - Arguments specifying a group ID and members to assign.

    When:
    - Calling the 'vectra_group_unassign_command' function with the provided client and arguments.

    Then: - Assert that the result's human-readable output, context data, and key field match the expected values for
    host type.
    """
    unassign_group_res = util_load_json(f"{TEST_DATA_DIR}/unassign_group_response.json")
    groups = util_load_json(f"{TEST_DATA_DIR}/get_groups_response.json")
    context_data = util_load_json(f"{TEST_DATA_DIR}/unassign_group_context.json")

    with open(f"{TEST_DATA_DIR}/unassign_host_group_hr.md") as f:
        result_hr = f.read()
    args = {"group_id": "2", "members": "3"}
    requests_mock.get(BASE_URL + "{}/{}".format(ENDPOINTS["GROUP_ENDPOINT"], args.get("group_id")), json=groups[1])
    requests_mock.patch(BASE_URL + "{}/{}".format(ENDPOINTS["GROUP_ENDPOINT"], args.get("group_id")), json=unassign_group_res[1])
    # Call the function
    result = vectra_group_unassign_command(client, args)
    result_context = result.to_context()
    # Assert the CommandResults
    assert result.outputs_prefix == "Vectra.Group"
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("EntryContext") == context_data[1]
    assert result.outputs_key_field == ["group_id"]


def test_vectra_unassign_account_group_valid_arguments(requests_mock, client):
    """
    Given:
    - A client object.
    - Mocked response data for an existing group.
    - Arguments specifying a group ID and members to assign.

    When:
    - Calling the 'vectra_group_unassign_command' function with the provided client and arguments.

    Then: - Assert that the result's human-readable output, context data, and key field match the expected values for
    account type.
    """
    unassign_group_res = util_load_json(f"{TEST_DATA_DIR}/unassign_group_response.json")
    groups = util_load_json(f"{TEST_DATA_DIR}/get_groups_response.json")
    context_data = util_load_json(f"{TEST_DATA_DIR}/unassign_group_context.json")

    with open(f"{TEST_DATA_DIR}/unassign_account_group_hr.md") as f:
        result_hr = f.read()
    args = {"group_id": "3", "members": "account_1"}
    requests_mock.get(BASE_URL + "{}/{}".format(ENDPOINTS["GROUP_ENDPOINT"], args.get("group_id")), json=groups[2])
    requests_mock.patch(BASE_URL + "{}/{}".format(ENDPOINTS["GROUP_ENDPOINT"], args.get("group_id")), json=unassign_group_res[2])
    # Call the function
    result = vectra_group_unassign_command(client, args)
    result_context = result.to_context()
    # Assert the CommandResults
    assert result.outputs_prefix == "Vectra.Group"
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("EntryContext") == context_data[2]
    assert result.outputs_key_field == ["group_id"]


def test_vectra_unassign_member_already_exist(requests_mock, client):
    """
    Given:
    - A client object.
    - Mocked response data for an existing group.
    - Arguments specifying a group ID and members to assign.

    When:
    - Calling the 'vectra_group_unassign_command' function with the provided client and arguments.

    Then:
    - Assert that the result's human-readable output indicates that the members are already in the group.
    """
    groups = util_load_json(f"{TEST_DATA_DIR}/assign_group_response.json")

    args = {"group_id": "2", "members": "6,7"}
    requests_mock.get(BASE_URL + "{}/{}".format(ENDPOINTS["GROUP_ENDPOINT"], args.get("group_id")), json=groups[1])

    # Call the function
    result = vectra_group_unassign_command(client, args)
    result_context = result.to_context()
    # Assert the CommandResults
    assert result_context.get("HumanReadable") == "##### Member(s) 6, 7 do not exist in the group."


@pytest.mark.parametrize(
    "args,error_msg",
    [
        ({"members": "account1"}, ERRORS["REQUIRED_ARGUMENT"].format("group_id")),
        ({"group_id": "0", "members": "account1"}, ERRORS["INVALID_INTEGER_VALUE"].format("group_id", "0")),
        ({"group_id": "-1", "members": "account1"}, ERRORS["INVALID_INTEGER_VALUE"].format("group_id", "-1")),
        ({"group_id": "1.5", "members": "account1"}, ERRORS["INVALID_INTEGER_VALUE"].format("group_id", "1.5")),
        ({"group_id": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("members")),
    ],
)
def test_vectra_group_unassign_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Arguments specifying invalid parameters for assigning members to a group.

    When:
    - Calling the 'vectra_group_unassign_command' function with the provided client and arguments.

    Then:
    - Assert that the function raises a ValueError.
    - Assert that the raised error message matches the expected error message.
    """
    # Call the function and assert that it raises ValueError
    with pytest.raises(ValueError) as exception:
        vectra_group_unassign_command(client, args)

    assert str(exception.value) == error_msg


@pytest.mark.parametrize("close_reason", ["benign", "remediated"])
def test_vectra_entity_detections_mark_asclosed_valid_arguments(requests_mock, client, close_reason):
    """
    Given:
    - A client object.
    - Mocked responses for entity data and marking detections as closed.
    - Arguments specifying valid parameters for marking detections as closed for an entity.

    When:
    - Calling the 'vectra_entity_detections_mark_asclosed_command' function with the provided client and arguments.

    Then:
    - Assert that the human-readable output matches the expected output.
    """
    entity_response = util_load_json(f"{TEST_DATA_DIR}/get_entity_response.json")
    response = {"_meta": {"level": "Success", "message": f"Successfully closed detections as {close_reason}"}}
    status_response = {"message": {"success": ["Successfully updated detection statuses"]}, "_meta": {"level": "success"}}
    args = {"entity_id": "334", "entity_type": "account", "close_reason": close_reason}
    requests_mock.get(BASE_URL + "{}/{}".format(ENDPOINTS["ENTITY_ENDPOINT_v34"], args["entity_id"]), json=entity_response)
    requests_mock.patch(BASE_URL + ENDPOINTS["DETECTION_CLOSE_ENDPOINT"], json=response)
    requests_mock.patch(BASE_URL + ENDPOINTS["DETECTION_ENDPOINT"], json=status_response)
    # Call the function
    result = vectra_entity_detections_mark_asclosed_command(client, args)
    result_context = result.to_context()
    # Assert the CommandResults
    expected_hr = f"##### The detections (1933, 1934) of the provided entity ID have been successfully closed as {close_reason}."
    assert result_context.get("HumanReadable") == expected_hr


def test_vectra_entity_detections_mark_asclosed_with_no_detections(requests_mock, client):
    """
    Given:
    - A client object.
    - Mocked responses for entity data with no detections.
    - Arguments specifying valid parameters for marking detections as closed for an entity with no detections.

    When:
    - Calling the 'vectra_entity_detections_mark_asclosed_command' function with the provided client and arguments.

    Then:
    - Assert that the human-readable output matches the expected output indicating no detections to mark as closed.
    """
    args = {"entity_id": "1", "entity_type": "account", "close_reason": "benign"}
    requests_mock.get(
        BASE_URL + "{}/{}".format(ENDPOINTS["ENTITY_ENDPOINT_v34"], args["entity_id"]), json={"entity_id": "1", "type": "account"}
    )
    # Call the function
    result = vectra_entity_detections_mark_asclosed_command(client, args)
    result_context = result.to_context()
    # Assert the CommandResults
    assert result_context.get("HumanReadable") == "There are no active detections to mark as closed for this entity ID: 1."


def test_vectra_entity_detections_mark_asclosed_command_invalid_response(requests_mock, client):
    """
    Given:
    - A mocked client for requests.
    - A mock entity detection mark as closed invalid response.
    - Arguments specifying valid parameters for marking detections as closed for an entity.

    When:
    - Calling the 'vectra_entity_detections_mark_asclosed_command' function with the provided client and arguments.

    Then:
    - Assert that the function raises a DemistoException with the expected error message.
    """
    entity_response = util_load_json(f"{TEST_DATA_DIR}/get_entity_response.json")
    response = {"_meta": {"level": "Error", "message": "Failed to close detections"}}
    requests_mock.get(BASE_URL + "{}/{}".format(ENDPOINTS["ENTITY_ENDPOINT_v34"], 334), json=entity_response)
    requests_mock.patch(BASE_URL + ENDPOINTS["DETECTION_CLOSE_ENDPOINT"], json=response)
    args = {"entity_id": "334", "entity_type": "account", "close_reason": "benign"}

    # Capture exception from the function
    with pytest.raises(DemistoException) as exception:
        vectra_entity_detections_mark_asclosed_command(client, args)

    assert str(exception.value) == "Something went wrong. Message: Failed to close detections."


@pytest.mark.parametrize(
    "args,error_msg",
    [
        ({"entity_id": "", "entity_type": "account", "close_reason": "benign"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")),
        (
            {"entity_id": "1", "entity_type": "invalid_type", "close_reason": "benign"},
            ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)),
        ),
        ({"entity_id": "1", "entity_type": "", "close_reason": "benign"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type")),
        ({"entity_id": "1", "entity_type": "account", "close_reason": ""}, ERRORS["REQUIRED_ARGUMENT"].format("close_reason")),
        (
            {"entity_id": "1", "entity_type": "account", "close_reason": "invalid_reason"},
            ERRORS["INVALID_COMMAND_ARG_VALUE"].format("close_reason", "benign, remediated"),
        ),
    ],
)
def test_vectra_entity_detections_mark_asclosed_invalid_args(client, args, error_msg):
    """
    Given:
    - Invalid arguments for marking detections as closed.

    When:
    - Calling the 'vectra_entity_detections_mark_asclosed_command' function with the provided invalid arguments.

    Then:
    - Assert that the function raises a ValueError with the expected error message.
    """
    # Call the function and assert that it raises ValueError
    with pytest.raises(ValueError) as exception:
        vectra_entity_detections_mark_asclosed_command(client, args)

    assert str(exception.value) == error_msg


def test_vectra_detections_mark_asclosed_invalid_response(requests_mock, client):
    """
    Given:
    - A mocked client for requests.
    - A mock detection mark as closed invalid response.
    - Arguments specifying valid detection IDs and close reason to mark as closed.

    When:
    - Calling the 'vectra_detections_mark_asclosed_command' function with the provided client and arguments.

    Then:
    - Assert that the function raises a DemistoException with the expected error message.
    """
    response = {"_meta": {"level": "Error", "message": "Failed to close detections"}}
    requests_mock.patch(BASE_URL + ENDPOINTS["DETECTION_CLOSE_ENDPOINT"], json=response)
    args = {"detection_ids": "1,2,3", "close_reason": "benign"}

    # Capture exception from the function
    with pytest.raises(DemistoException) as exception:
        vectra_detections_mark_asclosed_command(client, args)

    assert str(exception.value) == "Something went wrong. Message: Failed to close detections."


@pytest.mark.parametrize(
    "args,error_msg",
    [
        ({}, ERRORS["REQUIRED_ARGUMENT"].format("detection_ids")),
        ({"detection_ids": "1,2,3"}, ERRORS["REQUIRED_ARGUMENT"].format("close_reason")),
        ({"detection_ids": "as,2", "close_reason": "benign"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_ids", "as")),
        (
            {"detection_ids": "1,2, , , ,,,3", "close_reason": "benign"},
            ERRORS["INVALID_INTEGER_VALUE"].format("detection_ids", ""),
        ),
        (
            {"detection_ids": "1,2,3", "close_reason": "invalid"},
            ERRORS["INVALID_COMMAND_ARG_VALUE"].format("close_reason", "benign, remediated"),
        ),
    ],
)
def test_vectra_detections_mark_asclosed_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Invalid arguments (missing detection_ids, missing close_reason, invalid detection_ids, invalid close_reason).

    When:
    - Calling the 'vectra_detections_mark_asclosed_command' function with the provided client and arguments.

    Then:
    - Assert that the function raises a ValueError with the expected error message.
    """
    # Call the function and assert that it raises ValueError
    with pytest.raises(ValueError) as exception:
        vectra_detections_mark_asclosed_command(client, args)

    assert str(exception.value) == error_msg


def test_vectra_detections_mark_asopen_valid_arguments(requests_mock, client):
    """
    Given:
    - A mocked client for requests.
    - A mock detection mark as open response.
    - Arguments specifying valid detection IDs to mark as open.

    When:
    - Calling the 'vectra_detections_mark_asopen_command' function with the provided client and arguments.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert that the human-readable output matches the expected content.
    """
    response = {"_meta": {"level": "success", "message": "Successfully re-opened detections"}}
    status_response = {"message": {"success": ["Successfully updated detection statuses"]}, "_meta": {"level": "success"}}
    requests_mock.patch(BASE_URL + ENDPOINTS["DETECTION_OPEN_ENDPOINT"], json=response)
    requests_mock.patch(os.path.join(BASE_URL, ENDPOINTS["DETECTION_ENDPOINT"]), json=status_response)

    args = {"detection_ids": "1,2,3"}

    # Call the function
    result = vectra_detections_mark_asopen_command(client, args)
    result_context = result.to_context()
    expected_hr = "##### The provided detection IDs have been successfully re-opened."
    # Assert the CommandResults
    assert result_context.get("HumanReadable") == expected_hr
    assert result_context.get("EntryContext") == {}


def test_vectra_detections_mark_asopen_invalid_response(requests_mock, client):
    """
    Given:
    - A mocked client for requests.
    - A mock detection mark as open invalid response.
    - Arguments specifying valid detection IDs to mark as open.

    When:
    - Calling the 'vectra_detections_mark_asopen_command' function with the provided client and arguments.

    Then:
    - Assert that the function raises a DemistoException with the expected error message.
    """
    response = {"_meta": {"level": "Error", "message": "Failed to open detections"}}
    requests_mock.patch(BASE_URL + ENDPOINTS["DETECTION_OPEN_ENDPOINT"], json=response)
    args = {"detection_ids": "1,2,3"}

    # Capture exception from the function
    with pytest.raises(DemistoException) as exception:
        vectra_detections_mark_asopen_command(client, args)

    assert str(exception.value) == "Something went wrong. Message: Failed to open detections."


@pytest.mark.parametrize(
    "args,error_msg",
    [
        ({}, ERRORS["REQUIRED_ARGUMENT"].format("detection_ids")),
        ({"detection_ids": ""}, ERRORS["REQUIRED_ARGUMENT"].format("detection_ids", "")),
        ({"detection_ids": "as,2"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_ids", "as")),
        ({"detection_ids": "1,2, ,  ,3"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_ids", "")),
    ],
)
def test_vectra_detections_mark_asopen_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Invalid arguments (missing detection_ids, empty detection_ids, invalid detection_ids).

    When:
    - Calling the 'vectra_detections_mark_asopen_command' function with the provided client and arguments.

    Then:
    - Assert that the function raises a ValueError with the expected error message.
    """
    # Call the function and assert that it raises ValueError
    with pytest.raises(ValueError) as exception:
        vectra_detections_mark_asopen_command(client, args)

    assert str(exception.value) == error_msg


def test_vectra_detection_tag_list_valid_arguments(requests_mock, client):
    """
    Given:
    - A client object.
    - Mocked 'list_detection_tags_request' method returning tags response.
    - The expected human-readable output file.
    - Arguments specifying valid parameters for listing the tags of a detection.

    When:
    - Calling the 'vectra_detection_tag_list_command' function with the provided client and arguments.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert the correctness of the 'outputs_prefix' property.
    - Assert that the human-readable output matches the expected format.
    - Assert that the 'Contents' property in the context matches the tags response.
    - Assert that the 'EntryContext' property in the context matches the context data.
    - Assert the correctness of the 'outputs_key_field' property.
    """
    get_tags_res = {"status": "success", "tag_id": "36", "tags": ["tag1", "tag2"]}
    requests_mock.get(BASE_URL + ENDPOINTS["LIST_TAGS_ENDPOINT"].format(123), json=get_tags_res)
    args = {"detection_id": "123"}

    # Call the function
    result = vectra_detection_tag_list_command(client, args)
    result_context = result.to_context()
    get_tags_res.update({"detection_id": 123})  # type: ignore
    del get_tags_res["status"]

    # Assert the CommandResults
    assert result.outputs_prefix == "Vectra.Detection.Tags"
    assert "##### List of tags: **tag1, tag2**" in result_context.get("HumanReadable")
    assert result_context.get("Contents") == get_tags_res
    assert result.outputs_key_field == ["tag_id", "detection_id"]


def test_vectra_detection_tag_list_with_empty_tag_response(mocker, client):
    """
    Given:
    - A client object.
    - Mocked 'list_detection_tags_request' method returning empty tags response.
    - Arguments specifying valid parameters for listing the tags of a detection.

    When:
    - Calling the 'vectra_detection_tag_list_command' function with the provided client and arguments.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert the correctness of the 'outputs_prefix' property.
    - Assert that the human-readable output indicates no tags were found.
    - Assert that the 'Contents' property in the context matches the tags response.
    - Assert the correctness of the 'outputs_key_field' property.
    """
    get_tags_res = {"status": "success", "tag_id": "36", "tags": []}
    mocker.patch.object(client, "list_detection_tags_request", return_value=get_tags_res)
    args = {"detection_id": "123"}

    # Call the function
    result = vectra_detection_tag_list_command(client, args)
    result_context = result.to_context()
    get_tags_res.update({"detection_id": 123})  # type: ignore
    get_tags_res.pop("status", None)

    # Assert the CommandResults
    assert result.outputs_prefix == "Vectra.Detection.Tags"
    assert result_context.get("HumanReadable") == "##### No tags were found for the given detection ID."
    assert result_context.get("Contents") == get_tags_res
    assert result.outputs_key_field == ["tag_id", "detection_id"]


@pytest.mark.parametrize(
    "args,error_msg",
    [
        ({}, ERRORS["REQUIRED_ARGUMENT"].format("detection_id")),
        ({"detection_id": "0"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "0")),
        ({"detection_id": "-1"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "-1")),
        ({"detection_id": "1.5"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "1.5")),
        ({"detection_id": "abc"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "abc")),
    ],
)
def test_vectra_detection_tag_list_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Arguments specifying different invalid values for detection_id.

    When:
    - Calling the 'vectra_detection_tag_list_command' function with the provided client and arguments.

    Then:
    - Assert that the function raises a ValueError.
    - Assert that the error message matches the expected error message for each invalid argument.
    """
    # Call the function and assert that it raises ValueError
    with pytest.raises(ValueError) as exception:
        vectra_detection_tag_list_command(client, args)

    assert str(exception.value) == error_msg


def test_vectra_detection_tag_list_when_response_is_invalid(mocker, client):
    """
    Given:
    - A client object.
    - Mocked 'list_detection_tags_request' method returning invalid response.
    - Arguments specifying valid detection_id.

    When:
    - Calling the 'vectra_detection_tag_list_command' function with the provided client and arguments.

    Then:
    - Assert that the function raises a DemistoException.
    - Assert that the error message indicates something went wrong.
    """
    get_tags_res = {"status": "error", "message": "Detection not found"}
    mocker.patch.object(client, "list_detection_tags_request", return_value=get_tags_res)
    args = {"detection_id": "123"}

    # Call the function
    with pytest.raises(DemistoException) as exception:
        vectra_detection_tag_list_command(client, args)

    assert str(exception.value) == f"Something went wrong. Message: {get_tags_res.get('message')}."


def test_vectra_detection_tag_list_when_response_has_no_status(mocker, client):
    """
    Given:
    - A client object.
    - Mocked 'list_detection_tags_request' method returning response without status.
    - Arguments specifying valid detection_id.

    When:
    - Calling the 'vectra_detection_tag_list_command' function with the provided client and arguments.

    Then:
    - Assert that the function raises a DemistoException.
    - Assert that the error message indicates something went wrong.
    """
    get_tags_res = {"tag_id": "36", "tags": ["tag1"]}
    mocker.patch.object(client, "list_detection_tags_request", return_value=get_tags_res)
    args = {"detection_id": "123"}

    # Call the function
    with pytest.raises(DemistoException) as exception:
        vectra_detection_tag_list_command(client, args)

    assert str(exception.value) == "Something went wrong."


def test_vectra_detection_tag_add_valid_arguments(requests_mock, client):
    """
    Given:
    - A mocked client for requests.
    - A mock get and update tag response.
    - The expected human-readable output file.
    - Arguments specifying valid parameters for adding the tags to an entity.

    When:
    - Calling the 'vectra_detection_tag_add_command' function with the provided client and arguments.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert the correctness of the 'outputs_prefix' property.
    - Assert that the human-readable output matches the content of the expected file.
    - Assert that the 'Contents' property in the context matches the tags response.
    - Assert that the 'EntryContext' property in the context matches the context data.
    - Assert the correctness of the 'outputs_key_field' property.
    """
    add_tags_res = util_load_json(f"{TEST_DATA_DIR}/detection_tag_add_response.json")
    get_tags_res = util_load_json(f"{TEST_DATA_DIR}/detection_tag_get_response.json")
    context_data = util_load_json(f"{TEST_DATA_DIR}/detection_tag_add_context.json")
    with open(f"{TEST_DATA_DIR}/detection_tag_add_hr.md") as f:
        result_hr = f.read()

    requests_mock.get(BASE_URL + ENDPOINTS["LIST_TAGS_ENDPOINT"].format(1), json=get_tags_res)
    requests_mock.patch(BASE_URL + ENDPOINTS["LIST_TAGS_ENDPOINT"].format(1), json=add_tags_res)

    args = {
        "detection_id": "1",
        "tags": "tag1, tag2",
    }

    # Call the function
    result = vectra_detection_tag_add_command(client, args)
    result_context = result.to_context()
    add_tags_res.update({"detection_id": 1})
    del add_tags_res["status"]
    # Assert the CommandResults
    assert result.outputs_prefix == "Vectra.Detection.Tags"
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("Contents") == add_tags_res
    assert result_context.get("EntryContext") == remove_empty_elements(context_data)
    assert result.outputs_key_field == ["tag_id", "detection_id"]


@pytest.mark.parametrize(
    "args,error_msg",
    [
        ({"tags": "tag1"}, ERRORS["REQUIRED_ARGUMENT"].format("detection_id")),
        ({"detection_id": "1", "tags": ""}, ERRORS["REQUIRED_ARGUMENT"].format("tags")),
        ({"detection_id": "1", "tags": " , "}, ERRORS["REQUIRED_ARGUMENT"].format("tags")),
        ({"detection_id": "0", "tags": "tag1"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "0")),
        ({"detection_id": "-1", "tags": "tag1"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "-1")),
        ({"detection_id": "1.5", "tags": "tag1"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "1.5")),
        ({"detection_id": "abc", "tags": "tag1"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "abc")),
    ],
)
def test_vectra_detection_tag_add_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Arguments specifying different invalid values for entity_id, entity_type, and tags.

    When:
    - Calling the 'vectra_detection_tag_add_command' function with the provided client and arguments.

    Then:
    - Assert that the function raises a ValueError.
    - Assert that the error message matches the expected error message for each invalid argument.
    """
    # Call the function and assert that it raises ValueError
    with pytest.raises(ValueError) as exception:
        vectra_detection_tag_add_command(client, args)

    assert str(exception.value) == error_msg


def test_vectra_detection_tag_add_when_get_tag_response_is_invalid(requests_mock, client):
    """
    Given:
    - A client object.
    - Mocked 'list_detection_tags_request' method returning invalid response.
    - Arguments specifying invalid tags.

    When:
    - Calling the 'vectra_detection_tag_add_command' function with the provided client and arguments.

    Then:
    - Assert that the human-readable output indicates that invalid result was found.
    """
    get_tags_res = util_load_json(f"{TEST_DATA_DIR}/detection_tag_get_invalid_response.json")
    requests_mock.get(BASE_URL + ENDPOINTS["LIST_TAGS_ENDPOINT"].format(1), json=get_tags_res)
    args = {
        "detection_id": "1",
        "tags": "tag1, tag2",
    }
    # Call the function
    with pytest.raises(DemistoException) as exception:
        vectra_detection_tag_add_command(client, args)

    assert str(exception.value) == f"Something went wrong. Message: {get_tags_res.get('message')}."


def test_vectra_detection_tag_add_when_add_tag_response_is_invalid(requests_mock, client):
    """
    Given:
    - A client object.
    - Mocked 'update_detection_tags_request' method returning invalid response.
    - Arguments specifying invalid tags.

    When:
    - Calling the 'vectra_detection_tag_add_command' function with the provided client and arguments.

    Then:
    - Assert that the human-readable output indicates that invalid result was found.
    """
    get_tags_res = util_load_json(f"{TEST_DATA_DIR}/detection_tag_get_response.json")
    add_tags_res = util_load_json(f"{TEST_DATA_DIR}/detection_tag_get_invalid_response.json")

    requests_mock.get(BASE_URL + ENDPOINTS["LIST_TAGS_ENDPOINT"].format(1), json=get_tags_res)
    requests_mock.patch(BASE_URL + ENDPOINTS["LIST_TAGS_ENDPOINT"].format(1), json=add_tags_res)
    args = {
        "detection_id": "1",
        "tags": "tag1, tag2",
    }
    # Call the function
    with pytest.raises(DemistoException) as exception:
        vectra_detection_tag_add_command(client, args)

    assert str(exception.value) == f"Something went wrong. Message: {add_tags_res.get('message')}."


def test_vectra_detection_tag_remove_valid_arguments(requests_mock, client):
    """
    Given:
    - A client object.
    - Mocked 'update_detection_tags_request' method returning tags response.
    - The expected human-readable output file.
    - Arguments specifying valid parameters for adding the tags to an entity.

    When:
    - Calling the 'vectra_detection_tag_remove_command' function with the provided client and arguments.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert the correctness of the 'outputs_prefix' property.
    - Assert that the human-readable output matches the content of the expected file.
    - Assert that the 'Contents' property in the context matches the tags response.
    - Assert that the 'EntryContext' property in the context matches the context data.
    - Assert the correctness of the 'outputs_key_field' property.
    """
    remove_tags_res = util_load_json(f"{TEST_DATA_DIR}/detection_tag_remove_response.json")
    get_tags_res = util_load_json(f"{TEST_DATA_DIR}/detection_tag_get_response.json")
    context_data = util_load_json(f"{TEST_DATA_DIR}/detection_tag_remove_context.json")
    with open(f"{TEST_DATA_DIR}/detection_tag_remove_hr.md") as f:
        result_hr = f.read()

    requests_mock.get(BASE_URL + ENDPOINTS["LIST_TAGS_ENDPOINT"].format(1), json=get_tags_res)
    requests_mock.patch(BASE_URL + ENDPOINTS["LIST_TAGS_ENDPOINT"].format(1), json=remove_tags_res)

    args = {"detection_id": "1", "tags": "tag,tag2"}

    # Call the function
    result = vectra_detection_tag_remove_command(client, args)
    result_context = result.to_context()
    remove_tags_res.update({"detection_id": 1})
    del remove_tags_res["status"]
    # Assert the CommandResults
    assert result.outputs_prefix == "Vectra.Detection.Tags"
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("Contents") == remove_tags_res
    assert result_context.get("EntryContext") == remove_empty_elements(context_data)
    assert result.outputs_key_field == ["tag_id", "detection_id"]


@pytest.mark.parametrize(
    "args,error_msg",
    [
        ({"tags": "tag1"}, ERRORS["REQUIRED_ARGUMENT"].format("detection_id")),
        ({"detection_id": "1", "tags": ""}, ERRORS["REQUIRED_ARGUMENT"].format("tags")),
        ({"detection_id": "1", "tags": " , "}, ERRORS["REQUIRED_ARGUMENT"].format("tags")),
        ({"detection_id": "0", "tags": "tag1"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "0")),
        ({"detection_id": "-1", "tags": "tag1"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "-1")),
        ({"detection_id": "1.5", "tags": "tag1"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "1.5")),
        ({"detection_id": "abc", "tags": "tag1"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "abc")),
    ],
)
def test_vectra_detection_tag_remove_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Arguments specifying different invalid values for entity_id and entity_type.

    When:
    - Calling the 'vectra_detection_tag_remove_command' function with the provided client and arguments.

    Then:
    - Assert that the function raises a ValueError.
    - Assert that the error message matches the expected error message for each invalid argument.
    """
    # Call the function and assert that it raises ValueError
    with pytest.raises(ValueError) as exception:
        vectra_detection_tag_remove_command(client, args)

    assert str(exception.value) == error_msg


def test_vectra_detection_tag_remove_when_get_tag_response_is_invalid(requests_mock, client):
    """
    Given:
    - A client object.
    - Mocked 'list_detection_tags_request' method returning invalid response.
    - Arguments specifying invalid tags.

    When:
    - Calling the 'vectra_detection_tag_remove_command' function with the provided client and arguments.

    Then:
    - Assert that the human-readable output indicates that invalid result was found.
    """
    get_tags_res = util_load_json(f"{TEST_DATA_DIR}/detection_tag_get_invalid_response.json")
    requests_mock.get(BASE_URL + ENDPOINTS["LIST_TAGS_ENDPOINT"].format(1), json=get_tags_res)
    args = {
        "detection_id": "1",
        "tags": "tag2",
    }
    # Call the function
    with pytest.raises(DemistoException) as exception:
        vectra_detection_tag_remove_command(client, args)

    assert str(exception.value) == f"Something went wrong. Message: {get_tags_res.get('message')}."


def test_vectra_detection_tag_remove_when_remove_tag_response_is_invalid(requests_mock, client):
    """
    Given:
    - A client object.
    - Mocked 'update_detection_tags_request' method returning invalid response.
    - Arguments specifying invalid tags.

    When:
    - Calling the 'vectra_detection_tag_remove_command' function with the provided client and arguments.

    Then:
    - Assert that the human-readable output indicates that invalid result was found.
    """
    get_tags_res = util_load_json(f"{TEST_DATA_DIR}/detection_tag_get_response.json")
    remove_tags_res = util_load_json(f"{TEST_DATA_DIR}/detection_tag_get_invalid_response.json")

    requests_mock.get(BASE_URL + ENDPOINTS["LIST_TAGS_ENDPOINT"].format(1), json=get_tags_res)
    requests_mock.patch(BASE_URL + ENDPOINTS["LIST_TAGS_ENDPOINT"].format(1), json=remove_tags_res)
    args = {
        "detection_id": "1",
        "tags": "tag, tag2",
    }
    # Call the function
    with pytest.raises(DemistoException) as exception:
        vectra_detection_tag_remove_command(client, args)

    assert str(exception.value) == f"Something went wrong. Message: {remove_tags_res.get('message')}."


def test_vectra_entity_detection_list_passes_entity_id_and_type(mocker, client):
    """
    Ensure entity_id and entity_type are passed correctly to list_detections_request.
    """
    entity_id = 42
    entity_type = "host"
    # Mock entity response with detection_set
    entity_data = {"detection_set": ["https://api/v3.3/detections/123"]}
    mocker.patch.object(client, "get_entity_request", return_value=entity_data)
    # Use realistic detection data with 'url' key
    detections_data = util_load_json(f"{TEST_DATA_DIR}/entity_detection_list_response.json")
    mock_list = mocker.patch.object(client, "list_detections_request", return_value=detections_data)
    args = {"entity_id": str(entity_id), "entity_type": entity_type}
    vectra_entity_detection_list_command(client, args)
    # Assert correct values are passed
    mock_list.assert_called_once()
    call_kwargs = mock_list.call_args.kwargs
    assert call_kwargs["entity_id"] == entity_id
    assert call_kwargs["entity_type"] == entity_type


def test_vectra_detection_note_list_valid_arguments(requests_mock, client):
    """
    Given:
    - A mocked 'requests_mock' to simulate API responses.
    - A client object.
    - Mocked detection note list response data.
    - Mocked context data.

    When:
    - Calling the 'vectra_detection_note_list_command' function with valid arguments.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert the correctness of the 'outputs_prefix' property.
    - Assert that the human-readable output matches the content of the expected file.
    - Assert that the 'Contents' property in the context matches the detection note list data.
    - Assert that the 'EntryContext' property in the context matches the expected context data.
    - Assert the correctness of the 'outputs_key_field' property.
    """
    notes_res = util_load_json(f"{TEST_DATA_DIR}/detection_note_list_response.json")
    context_data = util_load_json(f"{TEST_DATA_DIR}/detection_note_list_context.json")
    with open(f"{TEST_DATA_DIR}/detection_note_list_hr.md") as f:
        result_hr = f.read()
    args = {
        "detection_id": "1",
    }
    url = BASE_URL + ENDPOINTS["ADD_AND_LIST_DETECTION_NOTE_ENDPOINT"].format(args.get("detection_id"))
    requests_mock.get(url, json=notes_res)
    # Call the function
    result = vectra_detection_note_list_command(client, args)
    result_context = result.to_context()
    # Assert the CommandResults
    assert result.outputs_prefix == "Vectra.Detection.Notes"
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("Contents") == notes_res
    assert result_context.get("EntryContext") == remove_empty_elements(context_data)
    assert result.outputs_key_field == ["detection_id", "note_id"]


def test_vectra_detection_note_add_valid_arguments(requests_mock, client):
    """
    Given:
    - A mocked client for requests.
    - A mock notes response.
    - The expected human-readable output file.
    - Arguments specifying valid parameters for adding a note to a detection.

    When:
    - Calling the 'vectra_detection_note_add_command' function with the provided client and arguments.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert the correctness of the 'outputs_prefix' property.
    - Assert that the human-readable output matches the content of the expected file.
    - Assert that the 'Contents' property in the context matches the notes response.
    - Assert that the 'EntryContext' property in the context matches the context data.
    - Assert the correctness of the 'outputs_key_field' property.
    """
    notes_res = util_load_json(f"{TEST_DATA_DIR}/detection_note_add_response.json")
    context_data = util_load_json(f"{TEST_DATA_DIR}/detection_note_add_context.json")
    requests_mock.post(BASE_URL + ENDPOINTS["ADD_AND_LIST_DETECTION_NOTE_ENDPOINT"].format("1"), json=notes_res)
    with open(f"{TEST_DATA_DIR}/detection_note_add_hr.md") as f:
        result_hr = f.read()
    args = {
        "detection_id": "1",
        "note": "test_note",
    }

    # Call the function
    result = vectra_detection_note_add_command(client, args)
    result_context = result.to_context()
    notes_res["note_id"] = notes_res["id"]
    notes_res["detection_id"] = 1
    # Assert the CommandResults
    assert result.outputs_prefix == "Vectra.Detection.Notes"
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("Contents") == notes_res
    assert result_context.get("EntryContext") == context_data
    assert result.outputs_key_field == ["detection_id", "note_id"]


@pytest.mark.parametrize(
    "args,error_msg",
    [
        ({"detection_id": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("note")),
        ({"note": "test_note"}, ERRORS["REQUIRED_ARGUMENT"].format("detection_id")),
        ({"detection_id": "0", "note": "test_note"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "0")),
        ({"detection_id": "-1", "note": "test_note"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "-1")),
        (
            {"detection_id": "1.5", "note": "test_note"},
            ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "1.5"),
        ),
    ],
)
def test_vectra_detection_note_add_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Arguments specifying different invalid values for detection_id, and note.

    When:
    - Calling the 'vectra_detection_note_add_command' function with the provided client and arguments.

    Then:
    - Assert that the function raises a ValueError.
    - Assert that the error message matches the expected error message for each invalid argument.
    """
    # Call the function and assert that it raises ValueError
    with pytest.raises(ValueError) as exception:
        vectra_detection_note_add_command(client, args)

    assert str(exception.value) == error_msg


def test_vectra_detection_note_update_valid_arguments(requests_mock, client):
    """
    Given:
    - A mocked client for requests.
    - A mock note response.
    - The expected human-readable output file.
    - Arguments specifying valid parameters for updating a note of an entity.

    When:
    - Calling the 'vectra_detection_note_update_command' function with the provided client and arguments.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert the correctness of the 'outputs_prefix' property.
    - Assert that the human-readable output matches the content of the expected file.
    - Assert that the 'Contents' property in the context matches the notes response.
    - Assert that the 'EntryContext' property in the context matches the context data.
    - Assert the correctness of the 'outputs_key_field' property.
    """
    notes_res = util_load_json(f"{TEST_DATA_DIR}/detection_note_update_response.json")
    context_data = util_load_json(f"{TEST_DATA_DIR}/detection_note_update_context.json")
    requests_mock.patch(BASE_URL + ENDPOINTS["UPDATE_AND_REMOVE_DETECTION_NOTE_ENDPOINT"].format(1, 1), json=notes_res)
    with open(f"{TEST_DATA_DIR}/detection_note_update_hr.md") as f:
        result_hr = f.read()
    args = {
        "detection_id": "1",
        "note_id": "1",
        "note": "test_note",
    }

    # Call the function
    result = vectra_detection_note_update_command(client, args)
    result_context = result.to_context()
    notes_res["note_id"] = notes_res["id"]
    notes_res["detection_id"] = 1
    # Assert the CommandResults
    assert result.outputs_prefix == "Vectra.Detection.Notes"
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("Contents") == notes_res
    assert result_context.get("EntryContext") == context_data
    assert result.outputs_key_field == ["detection_id", "note_id"]


@pytest.mark.parametrize(
    "args,error_msg",
    [
        ({"detection_id": "1", "note_id": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("note")),
        ({"note": "test_note", "note_id": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("detection_id")),
        ({"detection_id": "1", "note": "test_note"}, ERRORS["REQUIRED_ARGUMENT"].format("note_id")),
        (
            {"detection_id": "0", "note": "test_note", "note_id": "1"},
            ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "0"),
        ),
        (
            {"detection_id": "-1", "note": "test_note", "note_id": "1"},
            ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "-1"),
        ),
        (
            {"detection_id": "1.5", "note": "test_note", "note_id": "1"},
            ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "1.5"),
        ),
        (
            {"note_id": "0", "note": "test_note", "detection_id": "1"},
            ERRORS["INVALID_INTEGER_VALUE"].format("note_id", "0"),
        ),
        (
            {"note_id": "-1", "note": "test_note", "detection_id": "1"},
            ERRORS["INVALID_INTEGER_VALUE"].format("note_id", "-1"),
        ),
        (
            {"note_id": "1.5", "note": "test_note", "detection_id": "1"},
            ERRORS["INVALID_INTEGER_VALUE"].format("note_id", "1.5"),
        ),
    ],
)
def test_vectra_detection_note_update_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Invalid arguments for updating a note of an entity.

    When:
    - Calling the 'vectra_detection_note_update_command' function with the provided client and arguments.

    Then:
    - Assert that a ValueError is raised with the expected error message.
    """
    # Call the function and assert that it raises ValueError
    with pytest.raises(ValueError) as exception:
        vectra_detection_note_update_command(client, args)

    assert str(exception.value) == error_msg


def test_vectra_detection_note_remove_valid_arguments(requests_mock, client):
    """
    Tests the 'vectra_detection_note_remove_command' function with valid arguments.

    Ensures that the function removes a detection note and returns the expected CommandResults object.

    Args:
        requests_mock: The requests mocker object.
        client: The VectraClient instance.

    Returns:
        None. Raises an AssertionError if the test fails.
    """
    requests_mock.delete(BASE_URL + ENDPOINTS["UPDATE_AND_REMOVE_DETECTION_NOTE_ENDPOINT"].format(1, 1), status_code=204)
    with open(f"{TEST_DATA_DIR}/detection_note_remove_hr.md") as f:
        result_hr = f.read()
    args = {
        "detection_id": "1",
        "note_id": "1",
    }

    # Call the function
    result = vectra_detection_note_remove_command(client, args)
    result_context = result.to_context()
    # Assert the CommandResults
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("EntryContext") == {}


def test_vectra_detection_note_remove_invalid_status_code(requests_mock, client):
    """
    Tests the 'vectra_detection_note_remove_command' function with valid arguments.

    Ensures that the function gives error in HR for status code.

    Args:
        requests_mock: The requests mocker object.
        client: The VectraClient instance.

    Returns:
        None. Raises an AssertionError if the test fails.
    """
    requests_mock.delete(BASE_URL + ENDPOINTS["UPDATE_AND_REMOVE_DETECTION_NOTE_ENDPOINT"].format(1, 1), status_code=200)
    args = {
        "detection_id": "1",
        "note_id": "1",
    }

    # Call the function
    result = vectra_detection_note_remove_command(client, args)
    result_context = result.to_context()
    # Assert the CommandResults
    assert result_context.get("HumanReadable") == "Something went wrong."
    assert result_context.get("EntryContext") == {}


@pytest.mark.parametrize(
    "args,error_msg",
    [
        ({"note": "test_note", "note_id": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("detection_id")),
        ({"detection_id": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("note_id")),
        (
            {"detection_id": "0", "note": "test_note", "note_id": "1"},
            ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "0"),
        ),
        (
            {"detection_id": "-1", "note": "test_note", "note_id": "1"},
            ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "-1"),
        ),
        (
            {"detection_id": "1.5", "note": "test_note", "note_id": "1"},
            ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "1.5"),
        ),
        (
            {"note_id": "0", "note": "test_note", "detection_id": "1"},
            ERRORS["INVALID_INTEGER_VALUE"].format("note_id", "0"),
        ),
        (
            {"note_id": "-1", "note": "test_note", "detection_id": "1"},
            ERRORS["INVALID_INTEGER_VALUE"].format("note_id", "-1"),
        ),
        (
            {"note_id": "1.5", "note": "test_note", "detection_id": "1"},
            ERRORS["INVALID_INTEGER_VALUE"].format("note_id", "1.5"),
        ),
    ],
)
def test_vectra_detection_note_remove_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Invalid arguments for updating a note of an entity.

    When:
    - Calling the 'vectra_detection_note_remove_command' function with the provided client and arguments.

    Then:
    - Assert that a ValueError is raised with the expected error message.
    """
    # Call the function and assert that it raises ValueError
    with pytest.raises(ValueError) as exception:
        vectra_detection_note_remove_command(client, args)

    assert str(exception.value) == error_msg


@pytest.mark.parametrize("entity_type", ["account", "host"])
def test_vectra_entity_unresolved_priority_reset_valid_arguments(requests_mock, client, entity_type):
    """
    Given:
    - A client object.
    - Valid arguments for marking entity unresolved priority as false.
    - Mocked API response for updating entity unresolved priority status.

    When:
    - Calling the 'vectra_entity_unresolved_priority_reset_command' function with valid entity_id and entity_type.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert the correctness of the 'outputs_prefix' property.
    - Assert that the human-readable output matches the expected message.
    - Assert that the 'EntryContext' property contains the correct entity_id and entity_type.
    - Assert the correctness of the 'outputs_key_field' property.
    """
    entity_id = "123"
    mock_response = {"message": {"success": ["Successfully updated unresolved_priority"]}, "_meta": {"level": "success"}}

    url = f"{BASE_URL}{ENDPOINTS['ENTITY_ENDPOINT']}/{entity_id}?type={entity_type}"
    requests_mock.patch(url, json=mock_response, status_code=200)

    args = {"entity_id": entity_id, "entity_type": entity_type}

    result = vectra_entity_unresolved_priority_reset_command(client, args)
    result_context = result.to_context()

    assert result.outputs_prefix == "Vectra.Entity"
    assert result.outputs_key_field == ["id", "type"]
    assert (
        result_context.get("HumanReadable")
        == "##### The unresolved priority of the provided entity has been successfully changed as 'false'."
    )

    entry_context = result_context.get("EntryContext")
    context_key = "Vectra.Entity(val.id && val.id == obj.id && val.type && val.type == obj.type)"
    assert entry_context.get(context_key) is not None
    outputs = entry_context.get(context_key)
    assert outputs.get("id") == entity_id
    assert outputs.get("type") == entity_type
    assert outputs.get("unresolved_priority") is False


def test_vectra_entity_unresolved_priority_reset_with_uppercase_entity_type(requests_mock, client):
    """
    Given:
    - A client object.
    - Valid arguments with uppercase entity_type.

    When:
    - Calling the 'vectra_entity_unresolved_priority_reset_command' function with uppercase entity_type.

    Then:
    - Assert that the function correctly converts entity_type to lowercase when making the API call.
    - Assert that the CommandResults object contains the expected outputs.
    """
    entity_id = "456"
    entity_type = "Account"
    mock_response = {"message": {"success": ["Successfully updated unresolved_priority"]}, "_meta": {"level": "success"}}

    url = f"{BASE_URL}{ENDPOINTS['ENTITY_ENDPOINT']}/{entity_id}?type=account"
    requests_mock.patch(url, json=mock_response, status_code=200)

    args = {"entity_id": entity_id, "entity_type": entity_type}

    result = vectra_entity_unresolved_priority_reset_command(client, args)
    result_context = result.to_context()

    assert result.outputs_prefix == "Vectra.Entity"
    assert (
        result_context.get("HumanReadable")
        == "##### The unresolved priority of the provided entity has been successfully changed as 'false'."
    )

    context_key = "Vectra.Entity(val.id && val.id == obj.id && val.type && val.type == obj.type)"
    outputs = result_context.get("EntryContext").get(context_key)
    assert outputs.get("id") == entity_id
    assert outputs.get("type") == entity_type
    assert outputs.get("unresolved_priority") is False


def test_vectra_entity_unresolved_priority_reset_removes_nulls(requests_mock, client):
    """
    Given:
    - A client object.
    - API response containing null values.

    When:
    - Calling the 'vectra_entity_unresolved_priority_reset_command' function.

    Then:
    - Assert that null values are removed from the response before being added to outputs.
    """
    entity_id = "789"
    entity_type = "host"
    mock_response = {"message": {"success": ["Successfully updated unresolved_priority"]}, "_meta": {"level": "success"}}

    url = f"{BASE_URL}{ENDPOINTS['ENTITY_ENDPOINT']}/{entity_id}?type={entity_type}"
    requests_mock.patch(url, json=mock_response, status_code=200)

    args = {"entity_id": entity_id, "entity_type": entity_type}

    result = vectra_entity_unresolved_priority_reset_command(client, args)
    result_context = result.to_context()

    context_key = "Vectra.Entity(val.id && val.id == obj.id && val.type && val.type == obj.type)"
    outputs = result_context.get("EntryContext").get(context_key)
    assert outputs.get("id") == entity_id
    assert outputs.get("type") == entity_type
    assert outputs.get("unresolved_priority") is False


@pytest.mark.parametrize(
    "args,error_msg",
    [
        ({"entity_type": "account"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")),
        ({"entity_id": None, "entity_type": "account"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")),
        ({"entity_id": "123"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type")),
        ({"entity_id": "123", "entity_type": None}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type")),
        ({"entity_id": "123", "entity_type": ""}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type")),
        (
            {"entity_id": "0", "entity_type": "account"},
            ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "0"),
        ),
        (
            {"entity_id": "-1", "entity_type": "account"},
            ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "-1"),
        ),
        (
            {"entity_id": "1.5", "entity_type": "account"},
            ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "1.5"),
        ),
        (
            {"entity_id": "abc", "entity_type": "account"},
            ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "abc"),
        ),
        (
            {"entity_id": "123", "entity_type": "invalid_type"},
            ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)),
        ),
        (
            {"entity_id": "123", "entity_type": "user"},
            ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)),
        ),
        (
            {"entity_id": "123", "entity_type": "detection"},
            ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)),
        ),
    ],
)
def test_vectra_entity_unresolved_priority_reset_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Invalid arguments for marking entity unresolved priority as false.

    When:
    - Calling the 'vectra_entity_unresolved_priority_reset_command' function with invalid arguments.

    Then:
    - Assert that a ValueError is raised.
    - Assert that the error message matches the expected error message for each invalid argument.
    """
    with pytest.raises(ValueError) as exception:
        vectra_entity_unresolved_priority_reset_command(client, args)

    assert str(exception.value) == error_msg


def test_vectra_entity_unresolved_priority_reset_api_error(requests_mock, client):
    """
    Given:
    - A client object.
    - API returns an error response.

    When:
    - Calling the 'vectra_entity_unresolved_priority_reset_command' function.

    Then:
    - Assert that the function raises an appropriate exception when the API call fails.
    """
    entity_id = "999"
    entity_type = "account"
    mock_response = {
        "errors": {"unresolved_priority": ["unresolved_priority can only be set to false when is_prioritized is false"]},
        "_meta": {"level": "error"},
    }

    url = f"{BASE_URL}{ENDPOINTS['ENTITY_ENDPOINT']}/{entity_id}?type={entity_type}"
    requests_mock.patch(url, json=mock_response, status_code=400)

    args = {"entity_id": entity_id, "entity_type": entity_type}

    with pytest.raises(DemistoException):
        vectra_entity_unresolved_priority_reset_command(client, args)


@pytest.mark.parametrize("detection_status", ["open", "acknowledged", "escalated", "paused", "closed", "expired"])
def test_vectra_detection_status_update_valid_single_detection(requests_mock, client, detection_status):
    """
    Given:
    - A client object.
    - Valid arguments for updating detection status with a single detection ID.
    - Mocked API response for updating detection status.

    When:
    - Calling the 'vectra_detection_investigation_status_update_command' function with valid detection_id and detection_status.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert the correctness of the 'outputs_prefix' property.
    - Assert that the human-readable output matches the expected message.
    - Assert that the 'EntryContext' property contains the correct detection_ids and detection_status.
    - Assert the correctness of the 'outputs_key_field' property.
    """
    detection_id = "123"
    mock_response = {"message": {"success": ["Successfully updated detection statuses"]}, "_meta": {"level": "success"}}

    url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}"
    requests_mock.patch(url, json=mock_response, status_code=200)

    args = {"detection_ids": detection_id, "investigation_status": detection_status}

    result = vectra_detection_investigation_status_update_command(client, args)
    result_context = result.to_context()

    assert result.outputs_prefix == "Vectra.Detection"
    assert result.outputs_key_field == "id"
    assert result_context.get("HumanReadable") == (
        f"##### The investigation status for provided Detection ID(s) ['{detection_id}'] have been updated as {detection_status}."
    )

    context_key = "Vectra.Detection(val.id && val.id == obj.id)"
    outputs = result_context.get("EntryContext").get(context_key)
    assert isinstance(outputs, list)
    assert len(outputs) == 1
    assert outputs[0].get("id") == detection_id
    assert outputs[0].get("investigation_status") == detection_status


def test_vectra_detection_status_update_valid_multiple_detections(requests_mock, client):
    """
    Given:
    - A client object.
    - Valid arguments for updating detection status with multiple detection IDs.

    When:
    - Calling the 'vectra_detection_investigation_status_update_command' function with multiple detection IDs.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert that detection IDs are sorted in the output.
    """
    detection_ids = "3,1,2"
    detection_status = "escalated"
    mock_response = {"message": {"success": ["Successfully updated detection statuses"]}, "_meta": {"level": "success"}}

    url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}"
    requests_mock.patch(url, json=mock_response, status_code=200)

    args = {"detection_ids": detection_ids, "investigation_status": detection_status}

    result = vectra_detection_investigation_status_update_command(client, args)
    result_context = result.to_context()

    assert result.outputs_prefix == "Vectra.Detection"
    assert (
        result_context.get("HumanReadable")
        == f"##### The investigation status for provided Detection ID(s) ['3', '1', '2'] have been updated as {detection_status}."
    )

    context_key = "Vectra.Detection(val.id && val.id == obj.id)"
    outputs = result_context.get("EntryContext").get(context_key)
    assert isinstance(outputs, list)
    assert len(outputs) == 3
    detection_ids_in_output = [item.get("id") for item in outputs]
    assert set(detection_ids_in_output) == {"3", "1", "2"}
    for item in outputs:
        assert item.get("investigation_status") == detection_status


def test_vectra_detection_status_update_with_uppercase_status(requests_mock, client):
    """
    Given:
    - A client object.
    - Valid arguments with uppercase detection_status.

    When:
    - Calling the 'vectra_detection_investigation_status_update_command' function with uppercase detection_status.

    Then:
    - Assert that the function accepts uppercase status values.
    - Assert that the CommandResults object contains the expected outputs.
    """
    detection_id = "456"
    detection_status = "Escalated"
    mock_response = {"message": {"success": ["Successfully updated detection statuses"]}, "_meta": {"level": "success"}}

    url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}"
    requests_mock.patch(url, json=mock_response, status_code=200)

    args = {"detection_ids": detection_id, "investigation_status": detection_status}

    result = vectra_detection_investigation_status_update_command(client, args)
    result_context = result.to_context()

    assert result.outputs_prefix == "Vectra.Detection"
    context_key = "Vectra.Detection(val.id && val.id == obj.id)"
    outputs = result_context.get("EntryContext").get(context_key)
    assert isinstance(outputs, list)
    assert len(outputs) == 1
    assert outputs[0].get("investigation_status") == detection_status


def test_vectra_detection_status_update_removes_nulls(requests_mock, client):
    """
    Given:
    - A client object.
    - API response containing null values.

    When:
    - Calling the 'vectra_detection_investigation_status_update_command' function.

    Then:
    - Assert that null values are removed from the response before being added to outputs.
    """
    detection_id = "789"
    detection_status = "closed"
    mock_response = {
        "message": {"success": ["Successfully updated detection statuses"]},
        "_meta": {"level": "success"},
        "extra_field": None,
        "another_null": None,
    }

    url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}"
    requests_mock.patch(url, json=mock_response, status_code=200)

    args = {"detection_ids": detection_id, "investigation_status": detection_status}

    result = vectra_detection_investigation_status_update_command(client, args)
    result_context = result.to_context()

    context_key = "Vectra.Detection(val.id && val.id == obj.id)"
    outputs = result_context.get("EntryContext").get(context_key)
    assert isinstance(outputs, list)
    assert len(outputs) == 1
    assert outputs[0].get("id") == detection_id
    assert outputs[0].get("investigation_status") == detection_status


def test_vectra_detection_status_update_with_whitespace_in_ids(requests_mock, client):
    """
    Given:
    - A client object.
    - Detection IDs with whitespace.

    When:
    - Calling the 'vectra_detection_investigation_status_update_command' function with detection IDs containing whitespace.

    Then:
    - Assert that whitespace is properly stripped from detection IDs.
    """
    detection_ids = " 1 , 2 , 3 "
    detection_status = "acknowledged"
    mock_response = {"message": {"success": ["Successfully updated detection statuses"]}, "_meta": {"level": "success"}}

    url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}"
    requests_mock.patch(url, json=mock_response, status_code=200)

    args = {"detection_ids": detection_ids, "investigation_status": detection_status}

    result = vectra_detection_investigation_status_update_command(client, args)
    result_context = result.to_context()

    context_key = "Vectra.Detection(val.id && val.id == obj.id)"
    outputs = result_context.get("EntryContext").get(context_key)
    assert isinstance(outputs, list)
    assert len(outputs) == 3
    detection_ids_in_output = [item.get("id") for item in outputs]
    assert set(detection_ids_in_output) == {"1", "2", "3"}


def test_vectra_detection_status_update_with_mixed_valid_invalid_ids(mocker, requests_mock, client):
    """
    Given:
    - A client object.
    - Mixed valid and invalid detection IDs.

    When:
    - Calling the 'vectra_detection_investigation_status_update_command' function with mixed valid/invalid IDs.

    Then:
    - Assert that a warning is returned for invalid IDs.
    - Assert that valid IDs are processed successfully.
    """
    detection_ids = "1,abc,2,0,-3"
    detection_status = "paused"
    mock_response = {"message": {"success": ["Successfully updated detection statuses"]}, "_meta": {"level": "success"}}

    url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}"
    requests_mock.patch(url, json=mock_response, status_code=200)

    mock_return_warning = mocker.patch("VectraRUXEventsDetections.return_warning")

    args = {"detection_ids": detection_ids, "investigation_status": detection_status}

    result = vectra_detection_investigation_status_update_command(client, args)

    mock_return_warning.assert_called_once()
    call_args = mock_return_warning.call_args
    assert "abc,0,-3" in call_args[1]["message"]
    assert call_args[1]["exit"] is False

    result_context = result.to_context()
    context_key = "Vectra.Detection(val.id && val.id == obj.id)"
    outputs = result_context.get("EntryContext").get(context_key)
    assert isinstance(outputs, list)
    assert len(outputs) == 2
    detection_ids_in_output = [item.get("id") for item in outputs]
    assert set(detection_ids_in_output) == {"1", "2"}


@pytest.mark.parametrize(
    "args,error_msg",
    [
        ({}, ERRORS["REQUIRED_ARGUMENT"].format("detection_ids")),
        ({"detection_ids": None}, ERRORS["REQUIRED_ARGUMENT"].format("detection_ids")),
        ({"detection_ids": ""}, ERRORS["REQUIRED_ARGUMENT"].format("detection_ids")),
        ({"detection_ids": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("investigation_status")),
        ({"detection_ids": "1", "investigation_status": None}, ERRORS["REQUIRED_ARGUMENT"].format("investigation_status")),
        ({"detection_ids": "1", "investigation_status": ""}, ERRORS["REQUIRED_ARGUMENT"].format("investigation_status")),
        (
            {"detection_ids": "1", "investigation_status": "invalid_status"},
            ERRORS["INVALID_ARG_VALUE"].format(
                "investigation_status", ", ".join([status.lower() for status in VALID_DETECTION_STATUS])
            ),
        ),
        (
            {"detection_ids": "1", "investigation_status": "pending"},
            ERRORS["INVALID_ARG_VALUE"].format(
                "investigation_status", ", ".join([status.lower() for status in VALID_DETECTION_STATUS])
            ),
        ),
        (
            {"detection_ids": "1", "investigation_status": "resolved"},
            ERRORS["INVALID_ARG_VALUE"].format(
                "investigation_status", ", ".join([status.lower() for status in VALID_DETECTION_STATUS])
            ),
        ),
    ],
)
def test_vectra_detection_status_update_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Invalid arguments for updating detection status.

    When:
    - Calling the 'vectra_detection_investigation_status_update_command' function with invalid arguments.

    Then:
    - Assert that a ValueError is raised.
    - Assert that the error message matches the expected error message for each invalid argument.
    """
    with pytest.raises(ValueError) as exception:
        vectra_detection_investigation_status_update_command(client, args)

    assert str(exception.value) == error_msg


def test_vectra_detection_status_update_all_invalid_ids(mocker, requests_mock, client):
    """
    Given:
    - A client object.
    - All invalid detection IDs.

    When:
    - Calling the 'vectra_detection_investigation_status_update_command' function with all invalid IDs.

    Then:
    - Assert that return_warning is called with exit=True.
    """
    detection_ids = "abc,xyz,0,-1"
    detection_status = "escalated"

    args = {"detection_ids": detection_ids, "investigation_status": detection_status}

    with pytest.raises(DemistoException) as exception:
        vectra_detection_investigation_status_update_command(client, args)

    assert str(exception.value) == ERRORS["INVALID_INTEGER_VALUE"].format("detection_ids", ",".join(["abc", "xyz", "0", "-1"]))


def test_vectra_detection_status_update_api_error(requests_mock, client):
    """
    Given:
    - A client object.
    - API returns an error response.

    When:
    - Calling the 'vectra_detection_investigation_status_update_command' function.

    Then:
    - Assert that the function raises an appropriate exception when the API call fails.
    """
    detection_id = "999"
    detection_status = "escalated"

    url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}"
    requests_mock.patch(url, json={"error": "Detection not found"}, status_code=404)

    args = {"detection_ids": detection_id, "investigation_status": detection_status}

    with pytest.raises(DemistoException):
        vectra_detection_investigation_status_update_command(client, args)


def test_vectra_detection_external_id_update_valid_single_detection(requests_mock, client):
    """
    Given:
    - A client object.
    - Valid arguments for updating external reference ID with a single detection ID.
    - Mocked API response for updating detection external reference ID.

    When:
    - Calling the 'vectra_detection_external_id_update_command' function with valid detection_id and external_reference_id.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert the correctness of the 'outputs_prefix' property.
    - Assert that the human-readable output matches the expected message.
    - Assert that the 'EntryContext' property contains the correct detection_id and external_reference_id.
    - Assert the correctness of the 'outputs_key_field' property.
    """
    detection_id = "123"
    external_reference_id = "EXT-12345"
    mock_response = {"message": {"success": ["Successfully updated external reference ID"]}, "_meta": {"level": "success"}}

    url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}"
    requests_mock.patch(url, json=mock_response, status_code=200)

    args = {"detection_ids": detection_id, "external_reference_id": external_reference_id}

    result = vectra_detection_external_id_update_command(client, args)
    result_context = result.to_context()

    assert result.outputs_prefix == "Vectra.Detection"
    assert result.outputs_key_field == "id"
    assert result_context.get("HumanReadable") == (
        f"##### The external reference ID for provided Detection ID(s) ['{detection_id}'] "
        f"have been updated as {external_reference_id}."
    )

    context_key = "Vectra.Detection(val.id && val.id == obj.id)"
    outputs = result_context.get("EntryContext").get(context_key)
    assert isinstance(outputs, list)
    assert len(outputs) == 1
    assert outputs[0].get("id") == detection_id
    assert outputs[0].get("external_reference_id") == external_reference_id


def test_vectra_detection_external_id_update_valid_multiple_detections(requests_mock, client):
    """
    Given:
    - A client object.
    - Valid arguments for updating external reference ID with multiple detection IDs.

    When:
    - Calling the 'vectra_detection_external_id_update_command' function with multiple detection IDs.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert that all detection IDs are present in the output.
    """
    detection_ids = "3,1,2"
    external_reference_id = "EXT-67890"
    mock_response = {"message": {"success": ["Successfully updated external reference ID"]}, "_meta": {"level": "success"}}

    url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}"
    requests_mock.patch(url, json=mock_response, status_code=200)

    args = {"detection_ids": detection_ids, "external_reference_id": external_reference_id}

    result = vectra_detection_external_id_update_command(client, args)
    result_context = result.to_context()

    assert result.outputs_prefix == "Vectra.Detection"
    assert result_context.get("HumanReadable") == (
        "##### The external reference ID for provided Detection ID(s) ['3', '1', '2'] "
        f"have been updated as {external_reference_id}."
    )

    context_key = "Vectra.Detection(val.id && val.id == obj.id)"
    outputs = result_context.get("EntryContext").get(context_key)
    assert isinstance(outputs, list)
    assert len(outputs) == 3
    detection_ids_in_output = [item.get("id") for item in outputs]
    assert set(detection_ids_in_output) == {"3", "1", "2"}
    for item in outputs:
        assert item.get("external_reference_id") == external_reference_id


def test_vectra_detection_external_id_update_removes_nulls(requests_mock, client):
    """
    Given:
    - A client object.
    - API response containing null values.

    When:
    - Calling the 'vectra_detection_external_id_update_command' function.

    Then:
    - Assert that null values are removed from the response before being added to outputs.
    """
    detection_id = "789"
    external_reference_id = "EXT-TICKET-001"
    mock_response = {
        "message": {"success": ["Successfully updated external reference ID"]},
        "_meta": {"level": "success"},
        "extra_field": None,
        "another_null": None,
    }

    url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}"
    requests_mock.patch(url, json=mock_response, status_code=200)

    args = {"detection_ids": detection_id, "external_reference_id": external_reference_id}

    result = vectra_detection_external_id_update_command(client, args)
    result_context = result.to_context()

    context_key = "Vectra.Detection(val.id && val.id == obj.id)"
    outputs = result_context.get("EntryContext").get(context_key)
    assert isinstance(outputs, list)
    assert len(outputs) == 1
    assert outputs[0].get("id") == detection_id
    assert outputs[0].get("external_reference_id") == external_reference_id


def test_vectra_detection_external_id_update_with_whitespace_in_ids(requests_mock, client):
    """
    Given:
    - A client object.
    - Detection IDs with whitespace.

    When:
    - Calling the 'vectra_detection_external_id_update_command' function with detection IDs containing whitespace.

    Then:
    - Assert that whitespace is properly stripped from detection IDs.
    """
    detection_ids = " 1 , 2 , 3 "
    external_reference_id = "EXT-WS-123"
    mock_response = {"message": {"success": ["Successfully updated external reference ID"]}, "_meta": {"level": "success"}}

    url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}"
    requests_mock.patch(url, json=mock_response, status_code=200)

    args = {"detection_ids": detection_ids, "external_reference_id": external_reference_id}

    result = vectra_detection_external_id_update_command(client, args)
    result_context = result.to_context()

    context_key = "Vectra.Detection(val.id && val.id == obj.id)"
    outputs = result_context.get("EntryContext").get(context_key)
    assert isinstance(outputs, list)
    assert len(outputs) == 3
    detection_ids_in_output = [item.get("id") for item in outputs]
    assert set(detection_ids_in_output) == {"1", "2", "3"}


def test_vectra_detection_external_id_update_with_mixed_valid_invalid_ids(mocker, requests_mock, client):
    """
    Given:
    - A client object.
    - A mix of valid and invalid detection IDs.

    When:
    - Calling the 'vectra_detection_external_id_update_command' function with mixed valid and invalid IDs.

    Then:
    - Assert that return_warning is called for invalid IDs.
    - Assert that valid IDs are processed successfully.
    """
    detection_ids = "1,abc,2,0,-3"
    external_reference_id = "EXT-MIXED-456"
    mock_response = {"message": {"success": ["Successfully updated external reference ID"]}, "_meta": {"level": "success"}}

    url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}"
    requests_mock.patch(url, json=mock_response, status_code=200)

    mock_return_warning = mocker.patch("VectraRUXEventsDetections.return_warning")

    args = {"detection_ids": detection_ids, "external_reference_id": external_reference_id}

    result = vectra_detection_external_id_update_command(client, args)

    mock_return_warning.assert_called_once()
    call_args = mock_return_warning.call_args
    assert "abc,0,-3" in call_args[1]["message"]
    assert call_args[1]["exit"] is False

    result_context = result.to_context()
    context_key = "Vectra.Detection(val.id && val.id == obj.id)"
    outputs = result_context.get("EntryContext").get(context_key)
    assert isinstance(outputs, list)
    assert len(outputs) == 2
    detection_ids_in_output = [item.get("id") for item in outputs]
    assert set(detection_ids_in_output) == {"1", "2"}


@pytest.mark.parametrize(
    "args,error_msg",
    [
        ({}, ERRORS["REQUIRED_ARGUMENT"].format("detection_ids")),
        ({"detection_ids": None}, ERRORS["REQUIRED_ARGUMENT"].format("detection_ids")),
        ({"detection_ids": ""}, ERRORS["REQUIRED_ARGUMENT"].format("detection_ids")),
        ({"detection_ids": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("external_reference_id")),
        ({"detection_ids": "1", "external_reference_id": None}, ERRORS["REQUIRED_ARGUMENT"].format("external_reference_id")),
        ({"detection_ids": "1", "external_reference_id": ""}, ERRORS["REQUIRED_ARGUMENT"].format("external_reference_id")),
    ],
)
def test_vectra_detection_external_id_update_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Invalid arguments for updating external reference ID.

    When:
    - Calling the 'vectra_detection_external_id_update_command' function with invalid arguments.

    Then:
    - Assert that a ValueError is raised.
    - Assert that the error message matches the expected error message for each invalid argument.
    """
    with pytest.raises(ValueError) as exception:
        vectra_detection_external_id_update_command(client, args)

    assert str(exception.value) == error_msg


def test_vectra_detection_external_id_update_all_invalid_ids(mocker, requests_mock, client):
    """
    Given:
    - A client object.
    - All invalid detection IDs.

    When:
    - Calling the 'vectra_detection_external_id_update_command' function with all invalid IDs.

    Then:
    - Assert that return_warning is called with exit=True.
    """
    detection_ids = "abc,xyz,0,-1"
    external_reference_id = "EXT-INVALID-789"

    args = {"detection_ids": detection_ids, "external_reference_id": external_reference_id}

    with pytest.raises(DemistoException) as exception:
        vectra_detection_external_id_update_command(client, args)

    assert str(exception.value) == ERRORS["INVALID_INTEGER_VALUE"].format("detection_ids", ",".join(["abc", "xyz", "0", "-1"]))


def test_vectra_detection_external_id_update_api_error(requests_mock, client):
    """
    Given:
    - A client object.
    - API returns an error response.

    When:
    - Calling the 'vectra_detection_external_id_update_command' function.

    Then:
    - Assert that the function raises an appropriate exception when the API call fails.
    """
    detection_id = "999"
    external_reference_id = "EXT-ERROR-404"

    url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}"
    requests_mock.patch(url, json={"error": "Detection not found"}, status_code=404)

    args = {"detection_ids": detection_id, "external_reference_id": external_reference_id}

    with pytest.raises(DemistoException):
        vectra_detection_external_id_update_command(client, args)


def test_vectra_detection_external_id_update_with_special_characters(requests_mock, client):
    """
    Given:
    - A client object.
    - External reference ID with special characters.

    When:
    - Calling the 'vectra_detection_external_id_update_command' function with special characters in external_reference_id.

    Then:
    - Assert that special characters are handled correctly.
    """
    detection_id = "456"
    external_reference_id = "TICKET-2024-#123-ABC_XYZ"
    mock_response = {"message": {"success": ["Successfully updated external reference ID"]}, "_meta": {"level": "success"}}

    url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}"
    requests_mock.patch(url, json=mock_response, status_code=200)

    args = {"detection_ids": detection_id, "external_reference_id": external_reference_id}

    result = vectra_detection_external_id_update_command(client, args)
    result_context = result.to_context()

    context_key = "Vectra.Detection(val.id && val.id == obj.id)"
    outputs = result_context.get("EntryContext").get(context_key)
    assert isinstance(outputs, list)
    assert len(outputs) == 1
    assert outputs[0].get("id") == detection_id
    assert outputs[0].get("external_reference_id") == external_reference_id


@pytest.mark.parametrize("entity_type", ["account", "host"])
def test_vectra_entity_external_id_update_valid_arguments(requests_mock, client, entity_type):
    """
    Given:
    - A client object.
    - Valid arguments for updating entity external reference ID.
    - Mocked API response for updating entity external reference ID.

    When:
    - Calling the 'vectra_entity_external_id_update_command' function with valid
    entity_id, entity_type, and external_reference_id.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert the correctness of the 'outputs_prefix' property.
    - Assert that the human-readable output matches the expected message.
    - Assert that the 'EntryContext' property contains the correct entity_id, entity_type, and external_reference_id.
    - Assert the correctness of the 'outputs_key_field' property.
    """
    entity_id = "123"
    external_reference_id = "EXT-ENTITY-12345"
    mock_response = {"message": {"success": ["Successfully updated external reference ID"]}, "_meta": {"level": "success"}}

    url = f"{BASE_URL}{ENDPOINTS['ENTITY_ENDPOINT']}/{entity_id}"
    requests_mock.patch(url, json=mock_response, status_code=200)

    args = {"entity_id": entity_id, "entity_type": entity_type, "external_reference_id": external_reference_id}

    result = vectra_entity_external_id_update_command(client, args)
    result_context = result.to_context()

    assert result.outputs_prefix == "Vectra.Entity"
    assert result.outputs_key_field == ["id", "type"]
    assert (
        result_context.get("HumanReadable")
        == f"##### The external reference ID for provided Entity have been updated as {external_reference_id}."
    )

    context_key = "Vectra.Entity(val.id && val.id == obj.id && val.type && val.type == obj.type)"
    outputs = result_context.get("EntryContext").get(context_key)
    assert outputs.get("id") == entity_id
    assert outputs.get("type") == entity_type
    assert outputs.get("external_reference_id") == external_reference_id


def test_vectra_entity_external_id_update_with_uppercase_entity_type(requests_mock, client):
    """
    Given:
    - A client object.
    - Valid arguments with uppercase entity_type.

    When:
    - Calling the 'vectra_entity_external_id_update_command' function with uppercase entity_type.

    Then:
    - Assert that the function accepts uppercase entity type values.
    - Assert that the CommandResults object contains the expected outputs.
    """
    entity_id = "456"
    entity_type = "Account"
    external_reference_id = "EXT-UPPER-789"
    mock_response = {"message": {"success": ["Successfully updated external reference ID"]}, "_meta": {"level": "success"}}

    url = f"{BASE_URL}{ENDPOINTS['ENTITY_ENDPOINT']}/{entity_id}"
    requests_mock.patch(url, json=mock_response, status_code=200)

    args = {"entity_id": entity_id, "entity_type": entity_type, "external_reference_id": external_reference_id}

    result = vectra_entity_external_id_update_command(client, args)
    result_context = result.to_context()

    assert result.outputs_prefix == "Vectra.Entity"
    context_key = "Vectra.Entity(val.id && val.id == obj.id && val.type && val.type == obj.type)"
    outputs = result_context.get("EntryContext").get(context_key)
    assert outputs.get("id") == entity_id
    assert outputs.get("type") == entity_type
    assert outputs.get("external_reference_id") == external_reference_id


def test_vectra_entity_external_id_update_removes_nulls(requests_mock, client):
    """
    Given:
    - A client object.
    - API response containing null values.

    When:
    - Calling the 'vectra_entity_external_id_update_command' function.

    Then:
    - Assert that null values are removed from the response before being added to outputs.
    """
    entity_id = "789"
    entity_type = "host"
    external_reference_id = "EXT-NULL-TEST"
    mock_response = {
        "message": {"success": ["Successfully updated external reference ID"]},
        "_meta": {"level": "success"},
        "extra_field": None,
        "another_null": None,
    }

    url = f"{BASE_URL}{ENDPOINTS['ENTITY_ENDPOINT']}/{entity_id}"
    requests_mock.patch(url, json=mock_response, status_code=200)

    args = {"entity_id": entity_id, "entity_type": entity_type, "external_reference_id": external_reference_id}

    result = vectra_entity_external_id_update_command(client, args)
    result_context = result.to_context()

    context_key = "Vectra.Entity(val.id && val.id == obj.id && val.type && val.type == obj.type)"
    outputs = result_context.get("EntryContext").get(context_key)
    assert outputs.get("id") == entity_id
    assert outputs.get("type") == entity_type
    assert outputs.get("external_reference_id") == external_reference_id


@pytest.mark.parametrize(
    "args,error_msg",
    [
        ({}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")),
        ({"entity_id": None}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")),
        ({"entity_id": ""}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")),
        ({"entity_id": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type")),
        ({"entity_id": "1", "entity_type": None}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type")),
        ({"entity_id": "1", "entity_type": ""}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type")),
        ({"entity_id": "1", "entity_type": "account"}, ERRORS["REQUIRED_ARGUMENT"].format("external_reference_id")),
        (
            {"entity_id": "1", "entity_type": "account", "external_reference_id": None},
            ERRORS["REQUIRED_ARGUMENT"].format("external_reference_id"),
        ),
        (
            {"entity_id": "1", "entity_type": "account", "external_reference_id": ""},
            ERRORS["REQUIRED_ARGUMENT"].format("external_reference_id"),
        ),
        (
            {"entity_id": "0", "entity_type": "account", "external_reference_id": "EXT-123"},
            ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "0"),
        ),
        (
            {"entity_id": "-1", "entity_type": "account", "external_reference_id": "EXT-123"},
            ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "-1"),
        ),
        (
            {"entity_id": "1.5", "entity_type": "account", "external_reference_id": "EXT-123"},
            ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "1.5"),
        ),
        (
            {"entity_id": "abc", "entity_type": "account", "external_reference_id": "EXT-123"},
            ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "abc"),
        ),
        (
            {"entity_id": "1", "entity_type": "invalid_type", "external_reference_id": "EXT-123"},
            ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)),
        ),
        (
            {"entity_id": "1", "entity_type": "user", "external_reference_id": "EXT-123"},
            ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)),
        ),
        (
            {"entity_id": "1", "entity_type": "detection", "external_reference_id": "EXT-123"},
            ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)),
        ),
    ],
)
def test_vectra_entity_external_id_update_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Invalid arguments for updating entity external reference ID.

    When:
    - Calling the 'vectra_entity_external_id_update_command' function with invalid arguments.

    Then:
    - Assert that a ValueError is raised.
    - Assert that the error message matches the expected error message for each invalid argument.
    """
    with pytest.raises(ValueError) as exception:
        vectra_entity_external_id_update_command(client, args)

    assert str(exception.value) == error_msg


def test_vectra_entity_external_id_update_api_error(requests_mock, client):
    """
    Given:
    - A client object.
    - API returns an error response.

    When:
    - Calling the 'vectra_entity_external_id_update_command' function.

    Then:
    - Assert that the function raises an appropriate exception when the API call fails.
    """
    entity_id = "999"
    entity_type = "account"
    external_reference_id = "EXT-ERROR-404"

    url = f"{BASE_URL}{ENDPOINTS['ENTITY_ENDPOINT']}/{entity_id}"
    requests_mock.patch(url, json={"error": "Entity not found"}, status_code=404)

    args = {"entity_id": entity_id, "entity_type": entity_type, "external_reference_id": external_reference_id}

    with pytest.raises(DemistoException):
        vectra_entity_external_id_update_command(client, args)


def test_vectra_entity_external_id_update_with_special_characters(requests_mock, client):
    """
    Given:
    - A client object.
    - External reference ID with special characters.

    When:
    - Calling the 'vectra_entity_external_id_update_command' function with special characters in external_reference_id.

    Then:
    - Assert that special characters are handled correctly.
    """
    entity_id = "555"
    entity_type = "host"
    external_reference_id = "TICKET-2024-#456-XYZ_ABC"
    mock_response = {"message": {"success": ["Successfully updated external reference ID"]}, "_meta": {"level": "success"}}

    url = f"{BASE_URL}{ENDPOINTS['ENTITY_ENDPOINT']}/{entity_id}"
    requests_mock.patch(url, json=mock_response, status_code=200)

    args = {"entity_id": entity_id, "entity_type": entity_type, "external_reference_id": external_reference_id}

    result = vectra_entity_external_id_update_command(client, args)
    result_context = result.to_context()

    context_key = "Vectra.Entity(val.id && val.id == obj.id && val.type && val.type == obj.type)"
    outputs = result_context.get("EntryContext").get(context_key)
    assert outputs.get("id") == entity_id
    assert outputs.get("type") == entity_type
    assert outputs.get("external_reference_id") == external_reference_id


def test_vectra_detection_list_command_success(requests_mock, client):
    """
    Given:
    - A client object.
    - Valid arguments for listing detections.
    - Mocked API response with detection results.

    When:
    - Calling the 'vectra_detection_list_command' function.

    Then:
    - Assert that the CommandResults object contains the expected outputs.
    - Assert the correctness of the 'outputs_prefix' property.
    - Assert that the 'outputs' contains detection data.
    - Assert the correctness of the 'outputs_key_field' property.
    """
    mock_response = {
        "count": 2,
        "results": [
            {
                "id": 123,
                "detection": "Test Detection 1",
                "state": "active",
                "certainty": 85,
                "threat": 75,
                "url": "https://example.com/api/v3.3/detections/123",
            },
            {
                "id": 456,
                "detection": "Test Detection 2",
                "state": "active",
                "certainty": 90,
                "threat": 80,
                "url": "https://example.com/api/v3.3/detections/456",
            },
        ],
    }

    url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}"
    requests_mock.get(url, json=mock_response, status_code=200)

    args = {"page": "1", "page_size": "50"}

    result = vectra_detection_list_command(client, args)

    assert result.outputs_prefix == "Vectra.Detection"
    assert result.outputs_key_field == "id"
    assert isinstance(result.outputs, list)
    assert len(result.outputs) == 2
    assert result.outputs[0]["id"] == 123
    assert result.outputs[1]["id"] == 456


def test_vectra_detection_list_command_no_results(requests_mock, client):
    """
    Given:
    - A client object.
    - Valid arguments for listing detections.
    - Mocked API response with no detection results.

    When:
    - Calling the 'vectra_detection_list_command' function.

    Then:
    - Assert that the function returns a message indicating no detections found.
    - Assert that outputs is an empty dictionary.
    """
    mock_response = {"count": 0, "results": []}

    url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}"
    requests_mock.get(url, json=mock_response, status_code=200)

    args = {"page": "1", "page_size": "50"}

    result = vectra_detection_list_command(client, args)
    result_context = result.to_context()

    assert result_context.get("HumanReadable") == "##### Couldn't find any detections for provided filters."
    assert result_context.get("EntryContext") == {}


def test_vectra_detection_list_command_with_filters(requests_mock, client):
    """
    Given:
    - A client object.
    - Arguments with multiple filters (detection_name, detection_state, tags).

    When:
    - Calling the 'vectra_detection_list_command' function with filters.

    Then:
    - Assert that the function returns filtered detection results.
    """
    mock_response = {
        "count": 1,
        "results": [
            {
                "id": 789,
                "detection": "Filtered Detection",
                "state": "active",
                "tags": ["critical", "malware"],
                "url": "https://example.com/api/v3.3/detections/789",
            }
        ],
    }

    url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}"
    requests_mock.get(url, json=mock_response, status_code=200)

    args = {
        "detection_name": "Filtered Detection",
        "detection_state": "active",
        "tags": "critical,malware",
        "page": "1",
        "page_size": "50",
    }

    result = vectra_detection_list_command(client, args)

    assert result.outputs_prefix == "Vectra.Detection"
    assert len(result.outputs) == 1
    assert result.outputs[0]["id"] == 789


def test_vectra_detection_list_command_with_pagination(requests_mock, client):
    """
    Given:
    - A client object.
    - Arguments with custom page and page_size.

    When:
    - Calling the 'vectra_detection_list_command' function with pagination parameters.

    Then:
    - Assert that the function returns the correct page of results.
    """
    mock_response = {
        "count": 100,
        "results": [
            {"id": i, "detection": f"Detection {i}", "url": f"https://example.com/api/v3.3/detections/{i}"}
            for i in range(51, 101)
        ],
    }

    url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}"
    requests_mock.get(url, json=mock_response, status_code=200)

    args = {"page": "2", "page_size": "50"}

    result = vectra_detection_list_command(client, args)

    assert result.outputs_prefix == "Vectra.Detection"
    assert len(result.outputs) == 50


def test_vectra_detection_list_command_with_time_filters(requests_mock, client):
    """
    Given:
    - A client object.
    - Arguments with time-based filters (created_after, created_before).

    When:
    - Calling the 'vectra_detection_list_command' function with time filters.

    Then:
    - Assert that the function returns detections within the time range.
    """
    mock_response = {
        "count": 1,
        "results": [
            {
                "id": 999,
                "detection": "Recent Detection",
                "created_timestamp": "2024-01-15T10:00:00Z",
                "url": "https://example.com/api/v3.3/detections/999",
            }
        ],
    }

    url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}"
    requests_mock.get(url, json=mock_response, status_code=200)

    args = {"created_after": "2024-01-01", "created_before": "2024-01-31", "page": "1", "page_size": "50"}

    result = vectra_detection_list_command(client, args)

    assert result.outputs_prefix == "Vectra.Detection"
    assert len(result.outputs) == 1
    assert result.outputs[0]["id"] == 999


@pytest.mark.parametrize(
    "args,error_msg",
    [
        (
            {"include_info_category_detections": "invalid_bool", "page": "1", "page_size": "50"},
            ERRORS["INVALID_COMMAND_ARG_VALUE"].format("include_info_category_detections", ", ".join(VALID_BOOL_VALUES)),
        ),
        (
            {"is_triaged": "invalid_bool", "page": "1", "page_size": "50"},
            ERRORS["INVALID_COMMAND_ARG_VALUE"].format("is_triaged", ", ".join(VALID_BOOL_VALUES)),
        ),
        (
            {"close_reason": "invalid_reason", "page": "1", "page_size": "50"},
            ERRORS["INVALID_COMMAND_ARG_VALUE"].format("close_reason", ", ".join(VALID_CLOSE_REASON)),
        ),
        (
            {"entity_type": "invalid_type", "page": "1", "page_size": "50"},
            ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", (", ".join(VALID_ENTITY_TYPES)).lower()),
        ),
        ({"page": "0", "page_size": "50"}, ERRORS["INVALID_INTEGER_VALUE"].format("page", "0")),
        ({"page": "-1", "page_size": "50"}, ERRORS["INVALID_INTEGER_VALUE"].format("page", "-1")),
        ({"page": "1", "page_size": "0"}, ERRORS["INVALID_INTEGER_VALUE"].format("page_size", "0")),
        ({"page": "1", "page_size": "6000"}, ERRORS["INVALID_PAGE_SIZE"]),
    ],
)
def test_vectra_detection_list_command_invalid_args(client, args, error_msg):
    """
    Given:
    - A client object.
    - Invalid arguments for listing detections.

    When:
    - Calling the 'vectra_detection_list_command' function with invalid arguments.

    Then:
    - Assert that a ValueError is raised.
    - Assert that the error message matches the expected error message.
    """
    with pytest.raises(ValueError) as exception:
        vectra_detection_list_command(client, args)

    assert str(exception.value) == error_msg


def test_vectra_detection_list_command_invalid_time_range(client):
    """
    Given:
    - A client object.
    - Arguments with invalid time range (created_after > created_before).

    When:
    - Calling the 'vectra_detection_list_command' function.

    Then:
    - Assert that a ValueError is raised for invalid time range.
    """
    args = {"created_after": "2024-01-31", "created_before": "2024-01-01", "page": "1", "page_size": "50"}

    with pytest.raises(ValueError) as exception:
        vectra_detection_list_command(client, args)

    assert "Invalid time range" in str(exception.value)


def test_vectra_detection_list_command_with_description_filter(requests_mock, client):
    """
    Given:
    - A client object.
    - Arguments with description filter.

    When:
    - Calling the 'vectra_detection_list_command' function with description filter.

    Then:
    - Assert that the function returns detections matching the description.
    """
    mock_response = {
        "count": 1,
        "results": [
            {
                "id": 111,
                "detection": "Suspicious Activity",
                "description": "Malware detected on endpoint",
                "url": "https://example.com/api/v3.3/detections/111",
            }
        ],
    }

    url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}"
    requests_mock.get(url, json=mock_response, status_code=200)

    args = {"description": "Malware", "page": "1", "page_size": "50"}

    result = vectra_detection_list_command(client, args)

    assert result.outputs_prefix == "Vectra.Detection"
    assert len(result.outputs) == 1
    assert result.outputs[0]["id"] == 111


def test_vectra_detection_list_command_with_triaged_filter(requests_mock, client):
    """
    Given:
    - A client object.
    - Arguments with is_triaged filter set to true.

    When:
    - Calling the 'vectra_detection_list_command' function with is_triaged filter.

    Then:
    - Assert that the function returns only triaged detections.
    """
    mock_response = {
        "count": 1,
        "results": [
            {
                "id": 222,
                "detection": "Triaged Detection",
                "is_triaged": True,
                "url": "https://example.com/api/v3.3/detections/222",
            }
        ],
    }

    url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}"
    requests_mock.get(url, json=mock_response, status_code=200)

    args = {"is_triaged": "true", "page": "1", "page_size": "50"}

    result = vectra_detection_list_command(client, args)

    assert result.outputs_prefix == "Vectra.Detection"
    assert len(result.outputs) == 1
    assert result.outputs[0]["is_triaged"] is True


def test_vectra_investigation_query_send_command_success(mocker, client):
    """
    Given:
    - A client object.
    - Valid query and version arguments.

    When:
    - Calling vectra_investigation_query_send_command with valid arguments.

    Then:
    - Assert that the command returns a CommandResults object with the correct outputs.
    - Assert that the investigation_query_send method was called with correct parameters.
    - Assert that the human readable output contains the request_id.
    """
    mock_response = {
        "request_id": "inv-12345",
        "query": "SELECT * FROM detections WHERE severity > 5",
        "version": "v1",
        "status": "pending",
    }
    mocker.patch.object(client, "investigation_query_send", return_value=mock_response)

    args = {
        "query": "SELECT * FROM detections WHERE severity > 5",
        "version": "v1",
    }

    result = vectra_investigation_query_send_command(client, args)

    client.investigation_query_send.assert_called_once_with(
        query="SELECT * FROM detections WHERE severity > 5",
        version="v1",
    )

    assert result.outputs_prefix == "Vectra.Investigation"
    assert result.outputs_key_field == "request_id"
    assert result.outputs == mock_response
    assert result.raw_response == mock_response
    assert result.readable_output is not None
    assert "inv-12345" in result.readable_output
    assert "!vectra-investigation-result-get" in result.readable_output


def test_vectra_investigation_query_send_command_without_version(mocker, client):
    """
    Given:
    - A client object.
    - Valid query argument without version.

    When:
    - Calling vectra_investigation_query_send_command without version.

    Then:
    - Assert that the command returns a CommandResults object.
    - Assert that the investigation_query_send method was called with query and None for version.
    """
    mock_response = {
        "request_id": "inv-67890",
        "query": "SELECT * FROM hosts",
        "version": None,
        "status": "pending",
    }
    mocker.patch.object(client, "investigation_query_send", return_value=mock_response)

    args = {
        "query": "SELECT * FROM hosts",
    }

    result = vectra_investigation_query_send_command(client, args)

    client.investigation_query_send.assert_called_once_with(
        query="SELECT * FROM hosts",
        version=None,
    )

    assert result.outputs_prefix == "Vectra.Investigation"
    assert result.outputs_key_field == "request_id"
    assert result.outputs == mock_response
    assert result.readable_output is not None
    assert "inv-67890" in result.readable_output


def test_vectra_investigation_query_send_command_missing_query(client):
    """
    Given:
    - A client object.
    - Arguments without the required 'query' parameter.

    When:
    - Calling vectra_investigation_query_send_command without query.

    Then:
    - Assert that ValueError is raised with the appropriate error message.
    """
    args = {
        "version": "v1",
    }

    with pytest.raises(ValueError) as exception:
        vectra_investigation_query_send_command(client, args)

    assert ERRORS["REQUIRED_ARGUMENT"].format("query") in str(exception.value)


def test_vectra_investigation_query_send_command_empty_query(client):
    """
    Given:
    - A client object.
    - Arguments with an empty 'query' parameter.

    When:
    - Calling vectra_investigation_query_send_command with empty query.

    Then:
    - Assert that ValueError is raised with the appropriate error message.
    """
    args = {
        "query": "",
        "version": "v1",
    }

    with pytest.raises(ValueError) as exception:
        vectra_investigation_query_send_command(client, args)

    assert ERRORS["REQUIRED_ARGUMENT"].format("query") in str(exception.value)


def test_vectra_investigation_query_send_command_with_complex_query(mocker, client):
    """
    Given:
    - A client object.
    - A complex SQL-like query with multiple conditions.

    When:
    - Calling vectra_investigation_query_send_command with a complex query.

    Then:
    - Assert that the command returns a CommandResults object.
    - Assert that the query is passed correctly to the client method.
    """
    complex_query = """
    SELECT d.id, d.name, d.severity, h.ip_address
    FROM detections d
    JOIN hosts h ON d.host_id = h.id
    WHERE d.severity > 7 AND d.status = 'active'
    ORDER BY d.severity DESC
    LIMIT 100
    """
    mock_response = {
        "request_id": "inv-complex-001",
        "query": complex_query,
        "version": "v2",
        "status": "pending",
    }
    mocker.patch.object(client, "investigation_query_send", return_value=mock_response)

    args = {
        "query": complex_query,
        "version": "v2",
    }

    result = vectra_investigation_query_send_command(client, args)

    client.investigation_query_send.assert_called_once_with(
        query=complex_query,
        version="v2",
    )

    assert result.outputs_prefix == "Vectra.Investigation"
    assert result.outputs == mock_response
    assert result.readable_output is not None
    assert "inv-complex-001" in result.readable_output


def test_vectra_investigation_query_send_command_response_with_nulls(mocker, client):
    """
    Given:
    - A client object.
    - Valid query argument.
    - API response contains null values.

    When:
    - Calling vectra_investigation_query_send_command.

    Then:
    - Assert that null values are removed from the response.
    - Assert that the command returns a CommandResults object without nulls.
    """
    mock_response_with_nulls = {
        "request_id": "inv-nulls-123",
        "query": "SELECT * FROM accounts",
        "version": "v1",
        "status": "pending",
        "error": None,
        "metadata": None,
    }
    expected_response = {
        "request_id": "inv-nulls-123",
        "query": "SELECT * FROM accounts",
        "version": "v1",
        "status": "pending",
    }
    mocker.patch.object(client, "investigation_query_send", return_value=mock_response_with_nulls)

    args = {
        "query": "SELECT * FROM accounts",
        "version": "v1",
    }

    result = vectra_investigation_query_send_command(client, args)

    assert result.outputs == expected_response
    assert isinstance(result.outputs, dict)
    assert "error" not in result.outputs
    assert "metadata" not in result.outputs


def test_vectra_investigation_query_send_command_api_error(mocker, client):
    """
    Given:
    - A client object.
    - Valid query argument.
    - API call raises an exception.

    When:
    - Calling vectra_investigation_query_send_command.

    Then:
    - Assert that the exception is propagated.
    """
    mocker.patch.object(
        client,
        "investigation_query_send",
        side_effect=DemistoException("API connection error"),
    )

    args = {
        "query": "SELECT * FROM detections",
        "version": "v1",
    }

    with pytest.raises(DemistoException) as exception:
        vectra_investigation_query_send_command(client, args)

    assert "API connection error" in str(exception.value)


def test_vectra_investigation_result_get_command_success(mocker, client):
    """
    Given:
    - A client object.
    - Valid request ID with page and page_size arguments.

    When:
    - Calling vectra_investigation_result_get_command with valid arguments.

    Then:
    - Assert that the command returns a CommandResults object with the correct outputs.
    - Assert that the investigation_result_get method was called with correct parameters.
    - Assert that the human readable output contains the request_id and metadata.
    """
    mock_response = {
        "request_id": "inv-12345",
        "meta": {
            "query_status": "completed",
            "page": 1,
            "page_size": 50,
            "num_rows_available": 100,
            "estimated_file_size_bytes": 2048,
            "columns": ["id", "name", "severity"],
        },
        "data": [
            {"id": 1, "name": "Detection 1", "severity": 8},
            {"id": 2, "name": "Detection 2", "severity": 6},
        ],
    }
    mocker.patch.object(client, "investigation_result_get", return_value=mock_response)

    args = {
        "id": "inv-12345",
        "page": "1",
        "page_size": "50",
    }

    result = vectra_investigation_result_get_command(client, args)

    client.investigation_result_get.assert_called_once_with(
        request_id="inv-12345",
        page=1,
        page_size=50,
    )

    assert result.outputs_prefix == "Vectra.Investigation"
    assert result.outputs_key_field == "request_id"
    assert result.outputs == mock_response
    assert result.raw_response == mock_response
    assert result.readable_output is not None
    assert "inv-12345" in result.readable_output
    assert "completed" in result.readable_output
    assert "Detection 1" in result.readable_output


def test_vectra_investigation_result_get_command_with_defaults(mocker, client):
    """
    Given:
    - A client object.
    - Valid request ID without page and page_size arguments.

    When:
    - Calling vectra_investigation_result_get_command with only request ID.

    Then:
    - Assert that the command uses default values for page and page_size.
    - Assert that the investigation_result_get method was called with default parameters.
    """
    mock_response = {
        "request_id": "inv-67890",
        "meta": {
            "query_status": "pending",
            "page": 1,
            "page_size": 50,
            "num_rows_available": 0,
        },
        "data": [],
    }
    mocker.patch.object(client, "investigation_result_get", return_value=mock_response)

    args = {
        "id": "inv-67890",
    }

    result = vectra_investigation_result_get_command(client, args)

    client.investigation_result_get.assert_called_once_with(
        request_id="inv-67890",
        page=1,
        page_size=50,
    )

    assert result.outputs_prefix == "Vectra.Investigation"
    assert result.outputs_key_field == "request_id"
    assert result.outputs == mock_response
    assert result.readable_output is not None
    assert "inv-67890" in result.readable_output
    assert "pending" in result.readable_output


def test_vectra_investigation_result_get_command_missing_id(client):
    """
    Given:
    - A client object.
    - Arguments without the required 'id' parameter.

    When:
    - Calling vectra_investigation_result_get_command without id.

    Then:
    - Assert that ValueError is raised with the appropriate error message.
    """
    args = {
        "page": "1",
        "page_size": "50",
    }

    with pytest.raises(ValueError) as exception:
        vectra_investigation_result_get_command(client, args)

    assert ERRORS["REQUIRED_ARGUMENT"].format("id") in str(exception.value)


def test_vectra_investigation_result_get_command_empty_id(client):
    """
    Given:
    - A client object.
    - Arguments with an empty 'id' parameter.

    When:
    - Calling vectra_investigation_result_get_command with empty id.

    Then:
    - Assert that ValueError is raised with the appropriate error message.
    """
    args = {
        "id": "",
        "page": "1",
        "page_size": "50",
    }

    with pytest.raises(ValueError) as exception:
        vectra_investigation_result_get_command(client, args)

    assert ERRORS["REQUIRED_ARGUMENT"].format("id") in str(exception.value)


def test_vectra_investigation_result_get_command_invalid_page(client):
    """
    Given:
    - A client object.
    - Valid request ID with invalid page number (negative).

    When:
    - Calling vectra_investigation_result_get_command with invalid page.

    Then:
    - Assert that ValueError is raised for invalid page number.
    """
    args = {
        "id": "inv-12345",
        "page": "-1",
        "page_size": "50",
    }

    with pytest.raises(ValueError):
        vectra_investigation_result_get_command(client, args)


def test_vectra_investigation_result_get_command_invalid_page_size(client):
    """
    Given:
    - A client object.
    - Valid request ID with invalid page_size (zero).

    When:
    - Calling vectra_investigation_result_get_command with invalid page_size.

    Then:
    - Assert that ValueError is raised for invalid page_size.
    """
    args = {
        "id": "inv-12345",
        "page": "1",
        "page_size": "0",
    }

    with pytest.raises(ValueError):
        vectra_investigation_result_get_command(client, args)


def test_vectra_investigation_result_get_command_with_nulls(mocker, client):
    """
    Given:
    - A client object.
    - Valid request ID.
    - API response contains null values.

    When:
    - Calling vectra_investigation_result_get_command.

    Then:
    - Assert that null values are removed from the response.
    - Assert that the command returns a CommandResults object without nulls.
    """
    mock_response_with_nulls = {
        "request_id": "inv-nulls-456",
        "meta": {
            "query_status": "completed",
            "page": 1,
            "page_size": 50,
            "num_rows_available": 10,
            "estimated_file_size_bytes": None,
            "columns": ["id", "name"],
        },
        "data": [
            {"id": 1, "name": "Test", "extra": None},
        ],
        "error": None,
    }
    mocker.patch.object(client, "investigation_result_get", return_value=mock_response_with_nulls)

    args = {
        "id": "inv-nulls-456",
    }

    result = vectra_investigation_result_get_command(client, args)

    # remove_nulls_from_dictionary removes top-level None values but not nested ones
    assert isinstance(result.outputs, dict)
    assert result.outputs["request_id"] == "inv-nulls-456"
    assert "error" not in result.outputs  # Top-level None removed
    assert result.outputs["meta"]["query_status"] == "completed"
    # Nested None values may still be present depending on remove_nulls_from_dictionary implementation


def test_vectra_investigation_result_get_command_empty_data(mocker, client):
    """
    Given:
    - A client object.
    - Valid request ID.
    - API response with empty data array.

    When:
    - Calling vectra_investigation_result_get_command.

    Then:
    - Assert that the command handles empty data gracefully.
    - Assert that the readable output is generated correctly.
    """
    mock_response = {
        "request_id": "inv-empty-789",
        "meta": {
            "query_status": "completed",
            "page": 1,
            "page_size": 50,
            "num_rows_available": 0,
            "columns": [],
        },
        "data": [],
    }
    mocker.patch.object(client, "investigation_result_get", return_value=mock_response)

    args = {
        "id": "inv-empty-789",
    }

    result = vectra_investigation_result_get_command(client, args)

    # remove_nulls_from_dictionary removes empty lists, so 'data' and 'columns' keys may be removed
    assert isinstance(result.outputs, dict)
    assert result.outputs["request_id"] == "inv-empty-789"
    assert result.outputs["meta"]["query_status"] == "completed"
    assert result.readable_output is not None
    assert "inv-empty-789" in result.readable_output
    assert "completed" in result.readable_output


def test_vectra_investigation_result_get_command_large_page_size(mocker, client):
    """
    Given:
    - A client object.
    - Valid request ID with large page_size.

    When:
    - Calling vectra_investigation_result_get_command with large page_size.

    Then:
    - Assert that the command accepts the large page_size value.
    - Assert that the investigation_result_get method was called with the specified page_size.
    """
    mock_response = {
        "request_id": "inv-large-001",
        "meta": {
            "query_status": "completed",
            "page": 1,
            "page_size": 1000,
            "num_rows_available": 500,
        },
        "data": [],
    }
    mocker.patch.object(client, "investigation_result_get", return_value=mock_response)

    args = {
        "id": "inv-large-001",
        "page": "1",
        "page_size": "1000",
    }

    result = vectra_investigation_result_get_command(client, args)

    client.investigation_result_get.assert_called_once_with(
        request_id="inv-large-001",
        page=1,
        page_size=1000,
    )

    assert result.outputs == mock_response


def test_vectra_investigation_result_get_command_pagination(mocker, client):
    """
    Given:
    - A client object.
    - Valid request ID with specific page number.

    When:
    - Calling vectra_investigation_result_get_command with page 3.

    Then:
    - Assert that the command correctly passes the page parameter.
    - Assert that the investigation_result_get method was called with page 3.
    """
    mock_response = {
        "request_id": "inv-page-003",
        "meta": {
            "query_status": "completed",
            "page": 3,
            "page_size": 50,
            "num_rows_available": 200,
        },
        "data": [
            {"id": 101, "name": "Detection 101"},
            {"id": 102, "name": "Detection 102"},
        ],
    }
    mocker.patch.object(client, "investigation_result_get", return_value=mock_response)

    args = {
        "id": "inv-page-003",
        "page": "3",
        "page_size": "50",
    }

    result = vectra_investigation_result_get_command(client, args)

    client.investigation_result_get.assert_called_once_with(
        request_id="inv-page-003",
        page=3,
        page_size=50,
    )

    assert isinstance(result.outputs, dict)
    assert result.outputs["meta"]["page"] == 3  # type: ignore
    assert result.readable_output is not None
    assert "Detection 101" in result.readable_output


def test_vectra_investigation_result_get_command_api_error(mocker, client):
    """
    Given:
    - A client object.
    - Valid request ID.
    - API call raises an exception.

    When:
    - Calling vectra_investigation_result_get_command.

    Then:
    - Assert that the exception is propagated.
    """
    mocker.patch.object(
        client,
        "investigation_result_get",
        side_effect=DemistoException("API connection error"),
    )

    args = {
        "id": "inv-error-999",
        "page": "1",
        "page_size": "50",
    }

    with pytest.raises(DemistoException) as exception:
        vectra_investigation_result_get_command(client, args)

    assert "API connection error" in str(exception.value)


def test_vectra_investigation_result_get_command_query_status_failed(mocker, client):
    """
    Given:
    - A client object.
    - Valid request ID.
    - API response with query_status as 'failed'.

    When:
    - Calling vectra_investigation_result_get_command.

    Then:
    - Assert that the command returns the response with failed status.
    - Assert that the readable output contains the failed status.
    """
    mock_response = {
        "request_id": "inv-failed-111",
        "meta": {
            "query_status": "failed",
            "page": 1,
            "page_size": 50,
            "num_rows_available": 0,
            "error_message": "Query execution failed",
        },
        "data": [],
    }
    mocker.patch.object(client, "investigation_result_get", return_value=mock_response)

    args = {
        "id": "inv-failed-111",
    }

    result = vectra_investigation_result_get_command(client, args)

    assert isinstance(result.outputs, dict)
    assert result.outputs == mock_response
    assert result.outputs["meta"]["query_status"] == "failed"  # type: ignore
    assert result.readable_output is not None
    assert "failed" in result.readable_output
    assert "inv-failed-111" in result.readable_output


def test_vectra_investigation_result_get_command_complex_data(mocker, client):
    """
    Given:
    - A client object.
    - Valid request ID.
    - API response with complex nested data structures.

    When:
    - Calling vectra_investigation_result_get_command.

    Then:
    - Assert that the command handles complex data structures correctly.
    - Assert that the readable output is generated properly.
    """
    mock_response = {
        "request_id": "inv-complex-222",
        "meta": {
            "query_status": "completed",
            "page": 1,
            "page_size": 50,
            "num_rows_available": 2,
            "columns": ["id", "name", "metadata", "tags"],
        },
        "data": [
            {
                "id": 1,
                "name": "Complex Detection",
                "metadata": {"severity": 9, "category": "exfiltration"},
                "tags": ["critical", "reviewed"],
            },
            {
                "id": 2,
                "name": "Another Detection",
                "metadata": {"severity": 5, "category": "reconnaissance"},
                "tags": ["medium"],
            },
        ],
    }
    mocker.patch.object(client, "investigation_result_get", return_value=mock_response)

    args = {
        "id": "inv-complex-222",
    }

    result = vectra_investigation_result_get_command(client, args)

    assert isinstance(result.outputs, dict)
    assert result.outputs == mock_response
    assert len(result.outputs["data"]) == 2  # type: ignore
    assert result.outputs["data"][0]["metadata"]["severity"] == 9  # type: ignore
    assert result.readable_output is not None
    assert "Complex Detection" in result.readable_output


class TestUserRoleMapping:
    """Tests for the USER_ROLE_MAPPING constant added in v3.5 migration."""

    def test_user_role_mapping_constant_exists(self):
        """
        Given:
        - The USER_ROLE_MAPPING constant.

        When:
        - Checking if the constant is defined and contains expected mappings.

        Then:
        - Assert that the mapping contains all expected role conversions.
        """
        assert USER_ROLE_MAPPING is not None
        assert isinstance(USER_ROLE_MAPPING, dict)

        expected_mappings = {
            "Admin": "admins",
            "Auditor": "auditor",
            "Global Analyst": "global_analyst",
            "Read-Only": "read_only",
            "Restricted Admin": "restricted_admins",
            "Security Analyst": "security_analyst",
            "Setting Admin": "setting_admins",
            "Super Admin": "super_admins",
        }
        assert expected_mappings == USER_ROLE_MAPPING

    def test_user_role_mapping_admin_role(self):
        """Test Admin role mapping."""
        assert USER_ROLE_MAPPING.get("Admin") == "admins"

    def test_user_role_mapping_super_admin_role(self):
        """Test Super Admin role mapping."""
        assert USER_ROLE_MAPPING.get("Super Admin") == "super_admins"

    def test_user_role_mapping_security_analyst_role(self):
        """Test Security Analyst role mapping."""
        assert USER_ROLE_MAPPING.get("Security Analyst") == "security_analyst"


class TestVectraUserListCommandRoleMapping:
    """Tests for the role mapping functionality in vectra_user_list_command."""

    def test_vectra_user_list_with_role_mapping(self, mocker, client):
        """
        Given:
        - A mocked client for requests.
        - Arguments with a user role that needs to be mapped.

        When:
        - Calling the 'vectra_user_list_command' function with a role like 'Super Admin'.

        Then:
        - Assert that the role is mapped to 'super_admins' and sent to the API.
        - Assert that the command returns expected user data.
        """
        user_res = {
            "count": 1,
            "next": None,
            "previous": None,
            "results": [
                {
                    "id": 32,
                    "name": "user.name2",
                    "email": "user@example.com",
                    "role": "Super Admin",
                    "last_login_timestamp": "2023-07-02T18:41:19Z",
                }
            ],
        }

        # Mock the list_users_request method
        mocker.patch.object(client, "list_users_request", return_value=user_res)

        # Call the function with a role that needs mapping
        result = vectra_user_list_command(client, {"role": "Super Admin"})

        # Assert the CommandResults
        assert result.outputs_prefix == "Vectra.User"
        assert len(result.outputs) == 1
        assert result.outputs_key_field == ["user_id"]

    def test_vectra_user_list_with_unmapped_role(self, mocker, client):
        """
        Given:
        - A mocked client for requests.
        - Arguments with a user role that doesn't need mapping.

        When:
        - Calling the 'vectra_user_list_command' function with an unmapped role.

        Then:
        - Assert that the role is passed as-is to the API.
        """
        user_res = {
            "count": 1,
            "next": None,
            "previous": None,
            "results": [
                {
                    "id": 59,
                    "name": "user.name1",
                    "email": "user@example.com",
                    "role": "custom_role",
                    "last_login_timestamp": "2023-08-22T09:24:44Z",
                }
            ],
        }
        mocker.patch.object(client, "list_users_request", return_value=user_res)

        # Call with an unmapped role (should be passed as-is)
        result = vectra_user_list_command(client, {"role": "custom_role"})

        assert result.outputs_prefix == "Vectra.User"
        assert len(result.outputs) > 0

    def test_vectra_user_list_with_auditor_role_mapping(self, mocker, client):
        """
        Given:
        - A mocked client for requests.
        - Arguments with the 'Auditor' role.

        When:
        - Calling the 'vectra_user_list_command' function.

        Then:
        - Assert that 'Auditor' is mapped to 'auditor'.
        """
        user_res = {
            "count": 1,
            "next": None,
            "previous": None,
            "results": [
                {
                    "id": 10,
                    "name": "auditor_user",
                    "email": "auditor@example.com",
                    "role": "Auditor",
                    "last_login_timestamp": "2023-08-22T09:24:44Z",
                }
            ],
        }
        mocker.patch.object(client, "list_users_request", return_value=user_res)

        result = vectra_user_list_command(client, {"role": "Auditor"})

        assert result.outputs_prefix == "Vectra.User"
        assert result.outputs_key_field == ["user_id"]

    @pytest.mark.parametrize(
        "role_input",
        [
            "Admin",
            "Security Analyst",
            "Super Admin",
            "Auditor",
            "Global Analyst",
            "Read-Only",
            "Restricted Admin",
            "Setting Admin",
        ],
    )
    def test_user_list_command_all_role_mappings(self, mocker, client, role_input):
        """
        Test all possible role mappings for vectra_user_list_command.
        """
        user_res = {
            "count": 1,
            "next": None,
            "previous": None,
            "results": [
                {
                    "id": 1,
                    "name": "test_user",
                    "email": "test@example.com",
                    "role": role_input,
                    "last_login_timestamp": "2023-08-22T09:24:44Z",
                }
            ],
        }
        mocker.patch.object(client, "list_users_request", return_value=user_res)

        result = vectra_user_list_command(client, {"role": role_input})

        assert result.outputs_prefix == "Vectra.User"


class TestUserListCommandWithNameField:
    """Tests for the updated 'name' field in user list command output."""

    def test_user_list_uses_name_field(self, mocker, client):
        """
        Given:
        - A mocked user response with 'name' field.

        When:
        - Calling the 'vectra_user_list_command' function.

        Then:
        - Assert that the human-readable output uses 'name' field instead of 'username'.
        """
        # Create a user response with 'name' field (new v3.5 format)
        user_res = {
            "count": 1,
            "next": None,
            "previous": None,
            "results": [
                {
                    "id": 59,
                    "name": "user.name1",  # Using 'name' instead of 'username'
                    "email": "user@example.com",
                    "role": "Security Analyst",
                    "last_login_timestamp": "2023-08-22T09:24:44Z",
                }
            ],
        }
        mocker.patch.object(client, "list_users_request", return_value=user_res)

        result = vectra_user_list_command(client, {})

        # Check that the readable output contains the name
        assert result.readable_output is not None
        assert "user.name1" in result.readable_output
        assert result.outputs_prefix == "Vectra.User"


class TestDetectionInvestigationStatusUpdateValidation:
    """Tests for the enhanced validation in vectra_detection_investigation_status_update_command."""

    def test_detection_investigation_status_no_valid_ids_raises_exception(self, client):
        """
        Given:
        - Arguments with detection IDs that are all invalid (non-numeric, negative, zero).

        When:
        - Calling the 'vectra_detection_investigation_status_update_command' function.

        Then:
        - Assert that a DemistoException is raised with appropriate error message.
        """
        args = {
            "detection_ids": "-1,-5,0",
            "investigation_status": "Open",
        }

        with pytest.raises(DemistoException) as exc_info:
            vectra_detection_investigation_status_update_command(client, args)

        assert "INVALID_INTEGER_VALUE" in str(exc_info.value) or "invalid" in str(exc_info.value).lower()

    def test_detection_investigation_status_empty_detection_ids_raises_error(self, client):
        """
        Given:
        - Arguments with empty detection_ids.

        When:
        - Calling the 'vectra_detection_investigation_status_update_command' function.

        Then:
        - Assert that a ValueError is raised.
        """
        args = {
            "detection_ids": "",
            "investigation_status": "Open",
        }

        with pytest.raises(ValueError):
            vectra_detection_investigation_status_update_command(client, args)

    def test_detection_investigation_status_valid_ids_with_some_invalid(self, mocker, client):
        """
        Given:
        - Arguments with both valid and invalid detection IDs.

        When:
        - Calling the 'vectra_detection_investigation_status_update_command' function.

        Then:
        - Assert that the command processes valid IDs and returns a warning for invalid ones.
        """
        response = {
            "status": "success",
            "data": {"updated_count": 1},
        }
        mocker.patch.object(client, "update_detection_status_request", return_value=response)

        args = {
            "detection_ids": "1,invalid,-5",  # Mix of valid and invalid
            "investigation_status": "Open",
        }

        result = vectra_detection_investigation_status_update_command(client, args)

        # The command should process valid ID (1) and issue warnings for invalid ones
        assert result.outputs_prefix == "Vectra.Detection"

    def test_detection_investigation_status_only_valid_ids(self, mocker, client):
        """
        Given:
        - Arguments with only valid detection IDs.

        When:
        - Calling the 'vectra_detection_investigation_status_update_command' function.

        Then:
        - Assert that the command successfully updates the detection status.
        """
        response = {
            "status": "success",
            "data": {"updated_count": 3},
        }
        mocker.patch.object(client, "update_detection_status_request", return_value=response)

        args = {
            "detection_ids": "1,2,3",
            "investigation_status": "Acknowledged",
        }

        result = vectra_detection_investigation_status_update_command(client, args)

        assert result.outputs_prefix == "Vectra.Detection"
        assert len(result.outputs) == 3  # type: ignore


class TestJsonDataParameterChanges:
    """Tests to verify the 'json_data' parameter changes in HTTP requests work correctly."""

    def test_detection_note_add_uses_json_data(self, mocker, client):
        """
        Given:
        - Arguments for adding a detection note.

        When:
        - Calling the 'vectra_detection_note_add_command' function.

        Then:
        - Assert that the function executes successfully, implying json_data parameter works.
        """
        notes_res = util_load_json(f"{TEST_DATA_DIR}/detection_note_add_response.json")
        mocker.patch.object(client, "add_detection_note_request", return_value=notes_res)

        args = {
            "detection_id": "1",
            "note": "test_note",
        }

        result = vectra_detection_note_add_command(client, args)

        assert result.outputs_prefix == "Vectra.Detection.Notes"
        # Verify the mocked method was called
        client.add_detection_note_request.assert_called()

    def test_detection_note_update_uses_json_data(self, mocker, client):
        """
        Given:
        - Arguments for updating a detection note.

        When:
        - Calling the 'vectra_detection_note_update_command' function.

        Then:
        - Assert that the function executes successfully with json_data parameter.
        """
        notes_res = util_load_json(f"{TEST_DATA_DIR}/detection_note_update_response.json")
        mocker.patch.object(client, "update_detection_note_request", return_value=notes_res)

        args = {
            "detection_id": "1",
            "note_id": "1",
            "note": "updated_note",
        }

        result = vectra_detection_note_update_command(client, args)

        assert result.outputs_prefix == "Vectra.Detection.Notes"
        client.update_detection_note_request.assert_called()

    def test_http_request_accepts_json_data_parameter(self, client):
        """
        Given:
        - A VectraEventsDetectionsClient instance.

        When:
        - Verifying that the http_request method accepts json_data parameter.

        Then:
        - Assert that the method signature includes json_data parameter.
        """
        # Verify that http_request method has json_data parameter
        import inspect

        sig = inspect.signature(client.http_request)
        assert "json_data" in sig.parameters or "json_data" in str(sig)

    def test_entity_note_add_uses_json_data(self, mocker, client):
        """
        Given:
        - Arguments for adding an entity note.

        When:
        - Calling the 'vectra_entity_note_add_command' function.

        Then:
        - Assert that the function executes with json_data parameter.
        """
        notes_res = {
            "id": 1,
            "note": "test_note",
            "tags": [],
            "created_by": {"id": 1, "username": "test_user"},
        }
        mocker.patch.object(client, "add_entity_note_request", return_value=notes_res)

        args = {
            "entity_type": "account",
            "entity_id": "1",
            "note": "test_note",
        }

        result = vectra_entity_note_add_command(client, args)

        assert result.outputs_prefix == "Vectra.Entity.Notes"
        client.add_entity_note_request.assert_called()