Vectra_Detect

This integration allows to create incidents based on Vectra Accounts/Hosts/Detections objects.

Network Security · Vectra AI

Details

IDVectra_Detect
ProviderVectra AI
CategoryNetwork Security
From Version6.5.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

This integration allows to create incidents based on Vectra Accounts/Hosts/Detections objects.
This integration was integrated and tested with version 7.1 of Vectra Detect

Use cases

  1. Fetch accounts, hosts and detections from Vectra Detect.
  2. Bi-Directional mirroring for accounts and hosts.
  3. List and describe accounts, hosts, detections, and users.
  4. List, describe, create, and resolve assignments for accounts and hosts.
  5. List, describe, and create assignment outcomes.
  6. List, create, update, and delete notes for accounts, hosts, and detections.
  7. List, create, and remove tags for accounts, hosts, and detections.
  8. List, assign, and unassign members in group.
  9. Mark and unmark detection as fixed.
  10. Mark all detections as fixed for accounts and hosts.
  11. Get detection’s PCAP file.
  12. Clean up all incidents in Cortex XSOAR by closing duplicate incidents from Vectra Detect.

Configure Vectra Detect on Cortex XSOAR

  1. Navigate to Settings > Integrations > Servers & Services.
  2. Search for Vectra Detect.
  3. Click Add instance to create and configure a new integration instance.

    Parameter Description Required
    Vectra Detect FQDN or IP Enter the FQDN or IP to reach the Vectra Detect API. (e.g. “my-vectra-box.local” or “192.168.1.1”) True
    Authentication Type Select the authentication type (Default - API Token). False
    API Token Enter the API token that can be retrieved from the Vectra UI > My Profile > General (tab) > API Token. You can also use the XSOAR credentials wallet to store it. In that case, the token should be the password. False
    Client ID and Client Secret Key Enter the Client ID and Client Secret Key that can be retrieved from the Vectra UI > Manage > API Clients. You can also use the XSOAR credentials wallet to store it. False
    Trust any certificate (not secure) When checked, no SSL certificates check will be done when interacting with the Vectra Detect API. It’s insecure. (Default - unchecked) False
    Use system proxy settings Use the system proxy settings to reach with the Vectra Detect API. False
    Fetch incidents   False
    Incident type   False
    First fetch timestamp The date or relative timestamp from which to begin fetching entities.

    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 2024, 01 Aug 2024 04:45:33, 2024-07-17T14:05:44Z. (default - 7 days)
    False
    Mirroring Direction The mirroring direction in which to mirror the account and host. You can mirror “Incoming” (from Vectra to Cortex XSOAR), “Outgoing” (from Cortex XSOAR to Vectra), or in both directions. False
    Re-Fetch closed incidents via mirroring If selected, new incidents will be created (via Outgoing Mirroring). If not selected, it reopens previously closed incidents (via Incoming Mirroring).

    Note: This flow is triggered only when the relevant account or host is still active and the previously fetched incident is closed.
    False
    Mirror tag for notes The tag value should be used to mirror the account and host note by adding the same tag in the notes. False
    Entity types to fetch Choose what to fetch - Accounts and/or Hosts and/or Detections. (Default - Accounts,Hosts) False
    Tags Only Accounts or Hosts that contain any of the tags specified will be fetched.

    Note: For the partial match of the tag, use ‘*’ at the start and end of word (Only a single word is allowed). Ex. *MDR*.
    False
    Detection Category Filter the detections belonging to a specified category displayed as part of layout.

    Note: This filter applies on the ‘Vectra Account’ and ‘Vectra Host’ incident type.
    False
    Detection Type Filter the detections belonging to a specified type displayed as part of layout.

    Note: This filter applies on the ‘Vectra Account’ and ‘Vectra Host’ incident type.
     
    Hosts fetch query Only “active” Hosts matching this fetch query will be fetched. Will be used only if “Hosts” is selected in the “Entity types to fetch”. (default - host.threat:>=50) False
    Accounts fetch query Only “active” Accounts matching this fetch query will be fetched. Will be used only if “Accounts” is selected in the “Entity types to fetch”. (default - account.threat:>=50) False
    Detections fetch query Only “active” Detections matching this fetch query will be fetched. Will be used only if “Detections” is selected in the “Entity types to fetch”. (default - detection.threat:>=50 AND detection.certainty:>=50) False
    Max created incidents per fetch The maximum number of new incidents to create per fetch. This value would be split between selected “Entity types to fetch”. If the value is greater than 200, it will be considered as 200. The maximum is 200. (Default - 50) False
    Advanced: Minutes to look back when fetching Use this parameter to determine how long backward to look in the search for incidents that were created before the last run time and did not match the query when they were created. False
    Advanced: Fetch escalated Accounts and Hosts Use this parameter to fetch escalated Accounts and Hosts.

    Note: If this parameter is set to True, it will fetch Accounts and Hosts that have been updated based on the provided filter parameters, even if their detection timestamps are older than the first fetch time.
    False
    Re-Fetch Account/Host entity and Detection for new activity/event Whether to create multiple incidents for the same entity (Account or Host) or Detection if new detection activity occurs for the entity (Account or Host) or a new event is detected for the detection. False
  4. Click Test to validate the URLs, token, and connection.

Configuration for fetching Vectra Account or Vectra Host as a Cortex XSOAR incident

To fetch Vectra Account or Vectra Host as a Cortex XSOAR incident:

  1. Select Fetches incidents.
  2. Under Classifier, select “Vectra Detect”.
  3. Under Incident type, select “N/A”.
  4. Under Mapper (incoming), select “Vectra Detect - Incoming Mapper” for default mapping.
  5. Select the “Authentication Type” (Default - API Token).
  6. Enter connection parameters. (Vectra Detect FQDN or IP, API Token or Client ID and Client Secret Key)
  7. Select SSL certificate validation and Proxy if required.
  8. Update “Max created incidents per fetch” & “First fetch timestamp” based on your requirements.
  9. Select the Incident Mirroring Direction:
    1. Incoming - Mirrors changes from the Vectra into the Cortex XSOAR incident.
    2. Outgoing - Mirrors changes from the Cortex XSOAR incident to the Vectra.
    3. Incoming And Outgoing - Mirrors changes both Incoming and Outgoing directions on incidents.
  10. Check the “Re-Fetch closed incidents via mirroring” option if you want to prevent reopening of closed incidents and refetch them via mirroring on modification of the Vectra Account or Vectra Host.
  11. 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.
  12. Provide the filter parameter “Tags”, to filter entities by specific tag/s for fetch type account and host.
  13. Provide the filter parameter “Detection Category” and “Detection Type”, to filter detections by the specified category and type for fetch type account and host.
  14. Provide the “Minutes to look back when fetching” parameter to determine how long backward to look in the search for incidents that were created before the last run time and did not match the query when they were created.
  15. Check the “Fetch escalated Accounts and Hosts” option to fetch escalated Accounts and Hosts that have been updated based on the provided filter parameters, even if their detection timestamps are older than the first fetch time.
  16. Check the “Re-Fetch Account/Host entity and Detection for new activity/event” option if you want multiple Cortex XSOAR incidents to be created for the same Entity (Account or Host) or Detection whenever new detection activity is recorded. Disable this option if you want only one incident per Entity (Account or Host) or Detection regardless of ongoing activity.

Notes for mirroring:

  • The mirroring is strictly tied to incident types “Vectra Account” and “Vectra Host”, as well as the incoming mapper “Vectra Detect - Incoming Mapper”. If you want to change or use a custom incident type/mapper, ensure that related changes are also present.
  • The mirroring settings apply only for incidents that are fetched after applying the settings.
  • Any tags removed from the Vectra Account or Vectra Host will not be removed in the Cortex XSOAR incident, as Cortex XSOAR doesn’t allow the removal of the tags field via the backend. However, tags removed from the Cortex XSOAR incident UI will be removed from the Vectra Account or Vectra Host.
  • New notes from the Cortex XSOAR incident will be created as notes in the Vectra Account or Vectra Host. Updates to existing notes in the Cortex XSOAR incident will not be reflected in the Vectra Account or Vectra Host.
  • New notes from the Vectra Account or Vectra Host will be created as notes in the Cortex XSOAR incident. Updates to existing notes in the Vectra Account or Vectra Host will create new notes in the Cortex XSOAR incident.
  • If a closed Cortex XSOAR incident is tied to a specific Vectra Account or Vectra Host and new detections for that Vectra Account or Vectra Host arise or existing detections become active again:
    • If “Re-Fetch closed Incidents while Mirroring” checkbox is not selected and “Incoming Mirroring” is enabled, the incident will be automatically reopened.
    • If “Re-Fetch closed Incidents while Mirroring” checkbox is selected and “Outgoing Mirroring” is enabled, a new incident will be created for the Vectra Account or Vectra Host.
  • When a Cortex XSOAR incident is closed but there are still active detections on the Vectra side, and the Vectra Account or Vectra Host is subsequently updated:
    • If “Re-Fetch closed Incidents while Mirroring” checkbox is not selected and “Incoming Mirroring” is enabled, the corresponding XSOAR incident for that Vectra Account or Vectra Host will be reopened.
    • If “Re-Fetch closed Incidents while Mirroring” checkbox is selected and “Outgoing Mirroring” is enabled, a new incident will be created for the Vectra Account or Vectra Host.
  • If a Cortex XSOAR incident is reopened and the corresponding entity has an assignment in Vectra, the assignment will be removed from Vectra.
  • 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 Detect - Incoming 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 Cortex XSOAR to enable mirroring support.
    • mirror_tags: This field determines what would be the tag needed to mirror the Cortex XSOAR entry out to Vectra. It is a required field for XSOAR to enable mirroring support.
    • mirror_instance: This field determines from which instance the Cortex XSOAR incident was created. It is a required field for Cortex XSOAR to enable mirroring support.

Cleanup Duplicate Incidents

  • Use the Close All Duplicate XSOAR Incidents - Vectra Detect playbook to clean up duplicate incidents. You can use VectraDetectCloseDuplicateIncidents script individually to clean up duplicate incidents.
  • You can also schedule a job with Close All Duplicate XSOAR Incidents - Vectra Detect playbook in Cortex XSOAR to clean up incidents periodically. Refer to this Cortex XSOAR documentation for more information.

Commands

You can execute these commands from the Cortex XSOAR 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-search-accounts


Returns a list of Account objects. All search attributes will be cumulative unless you’re using the search_query_only one, in that case, only this one will be taken into account.

Base Command

vectra-search-accounts

Input

Argument Name Description Required
min_id Returns Accounts with an ID greater than or equal to the specified ID. Optional
max_id Returns Accounts with an ID less than or equal to the specified ID. Optional
min_threat Returns Accounts with a threat score greater than or equal to the specified score. Optional
max_threat Returns Accounts with a threat score less than or equal to the specified score. Optional
min_certainty Returns Accounts with a certainty score greater than or equal to the specified score. Optional
max_certainty Returns Accounts with a certainty score less than or equal to the specified score. Optional
state Filters by state (‘active’, ‘inactive’). Possible values are: active, inactive. Optional
search_query Search query in Lucene query syntax. Optional
search_query_only Use specifically this search query. Compared to “search_query” where default arguments are appended. Optional
min_privilege_level Returns entries with a privilege level greater than or equal to the specified score. Optional
max_privilege_level Returns entries with a privilege level greater than or equal to the specified score. Optional
privilege_category Filters by the privilege category (“low”, “medium”, “high”) provided. Optional
tags Filters by a tag or a comma-separated list tags. Optional

Context Output

Path Type Description
Vectra.Account.Assignee String Vectra user account this Account is assigned to
Vectra.Account.AssignedDate String Assignment date
Vectra.Account.CertaintyScore Number Account certainty score
Vectra.Account.ID Number Account ID (unique)
Vectra.Account.LastDetectionTimestamp String Last time a detection linked to this account has been seen
Vectra.Account.PrivilegeLevel Number Account privilege level (from 1 to 10)
Vectra.Account.PrivilegeCategory String Account privilege category (Either ‘Low’, ‘Medium’ or ‘High’ - Privilege levels of 1-2 > ‘Low’, 3-7 > ‘Medium’, 8-10 > ‘High’)
Vectra.Account.Severity String Account severity according to scores (‘Low’, ‘Medium’, ‘High’, ‘Critical’)
Vectra.Account.State String Account state (‘active’, ‘inactive’)
Vectra.Account.Tags String Account tags
Vectra.Account.ThreatScore Number Account threat score
Vectra.Account.Type String Account type (‘kerberos’ or ‘o365’)
Vectra.Account.URL String Account URL to pivot to Vectra UI
Vectra.Account.Name String The username of the account

vectra-search-hosts


Returns a list of Host objects. All search attributes will be cumulative unless you’re using the search_query_only one, in that case, only this one will be taken into account.

Base Command

vectra-search-hosts

Input

Argument Name Description Required
min_id Returns Hosts with an ID greater than or equal to the specified ID. Optional
max_id Returns Hosts with an ID less than or equal to the specified ID. Optional
min_threat Returns Hosts with a threat score greater than or equal to the specified score. Optional
max_threat Returns Hosts with a threat score less than or equal to the specified score. Optional
min_certainty Returns Hosts with a certainty score greater than or equal to the specified score. Optional
max_certainty Returns Hosts with a certainty score less than or equal to the specified score. Optional
state Filters by state (‘active’, ‘inactive’). Possible values are: active, inactive. Optional
search_query Search query in Lucene query syntax. Optional
search_query_only Use specifically this search query. Compared to “search_query” where default arguments are appended. Optional

Context Output

Path Type Description
Vectra.Host.Assignee String Vectra user account this Host is assigned to
Vectra.Host.AssignedDate String Assignment date
Vectra.Host.CertaintyScore Number Host certainty score
Vectra.Host.HasActiveTraffic Boolean Whether this Host has active traffic
Vectra.Host.Hostname String Host name
Vectra.Host.ID Number Host ID (Unique)
Vectra.Host.IP String Host IP address
Vectra.Host.IsKeyAsset Boolean Whether this Host is seen as a key asset
Vectra.Host.IsTargetingKeyAsset Boolean Whether this Host is targeting a key asset
Vectra.Host.PrivilegeLevel Number Host privilege level (from 1 to 10)
Vectra.Host.PrivilegeCategory String Host privilege category. (Either ‘Low’, ‘Medium’ or ‘High’ - Privilege levels of 1-2 > ‘Low’, 3-7 > ‘Medium’, 8-10 > ‘High’)
Vectra.Host.ProbableOwner String Host probable owner
Vectra.Host.SensorLUID String Sensor LUID that saw this Host
Vectra.Host.SensorName String Sensor Name that saw this Host
Vectra.Host.Sensor String Sensor details that have seen this Host
Vectra.Host.Severity String Host severity according to scores (‘Low’, ‘Medium’, ‘High’, ‘Critical’)
Vectra.Host.State String Host state (‘active’, ‘inactive’)
Vectra.Host.Tags String Host tags
Vectra.Host.ThreatScore Number Host threat score
Vectra.Host.URL String Host URL to pivot to Vectra UI

vectra-search-detections


Returns a list of Detection objects. All search attributes will be cumulative unless you’re using the search_query_only one, in that case, only this one will be taken into account.

Base Command

vectra-search-detections

Input

Argument Name Description Required
min_id Returns Detections with an ID greater than or equal to the specified ID. Optional
max_id Returns Detections with an ID less than or equal to the specified ID. Optional
min_threat Returns Detections with a threat score greater than or equal to the specified score. Optional
max_threat Returns Detections with a threat score less than or equal to the specified score. Optional
min_certainty Returns Detections with a certainty score greater than or equal to the specified score. Optional
max_certainty Returns Detections with a certainty score less than or equal to the specified score. Optional
state Filters by state (‘active’, ‘inactive’). Possible values are: active, inactive. Optional
search_query Search query in Lucene query syntax. Optional
search_query_only Use specifically this search query. Compared to “search_query” where default arguments are appended. Optional

Context Output

Path Type Description
Vectra.Detection.Assignee String Vectra user account this detection is assigned to
Vectra.Detection.AssignedDate String Assignment date
Vectra.Detection.Category String Detection category (Lateral, Exfil, …)
Vectra.Detection.CertaintyScore Number Detection certainty score
Vectra.Detection.Description String Detection description
Vectra.Detection.DestinationIPs String Detection destination IPs
Vectra.Detection.DestinationPorts String Detection destination ports
Vectra.Detection.FirstTimestamp String First time this detection has been seen
Vectra.Detection.ID Number Detection ID (unique)
Vectra.Detection.IsTargetingKeyAsset Boolean Whether this detection is targeting a key asset
Vectra.Detection.LastTimestamp String Last time this detection has been seen
Vectra.Detection.Name String The name of the detection. Would be a user defined name if this detection is triaged or the default type name instead
Vectra.Detection.Severity String Detection severity according to scores (‘Low’, ‘Medium’, ‘High’, ‘Critical’)
Vectra.Detection.SensorLUID String Sensor LUID that saw this detection
Vectra.Detection.SensorName String Sensor name that saw this detection.
Vectra.Detection.SourceAccountID String Account ID relating to this detection
Vectra.Detection.SourceHostID String Host ID relating to this detection
Vectra.Detection.SourceIP String Source IP relating to this detection
Vectra.Detection.State String Detection state (‘active’, ‘inactive’)
Vectra.Detection.Tags String Detection tags
Vectra.Detection.ThreatScore Number Detection threat score
Vectra.Detection.TriageRuleID String Triage rule ID related to this detection
Vectra.Detection.Type String Detection type (Brute Force, Port Sweep, …)
Vectra.Detection.URL String Detection URL to pivot to Vectra UI

vectra-search-assignments


Return a list of assignments. By default already resolved assignment are not returned.

Base Command

vectra-search-assignments

Input

Argument Name Description Required
account_ids Filters by accounts IDs. Optional
assignee_ids Filters by assignees IDs. Optional
host_ids Filters by hosts IDs. Optional
outcome_ids Filters by outcomes IDs. Optional
resolved Filters by resolution state. Optional

Context Output

Path Type Description
Vectra.Assignment.AccountID Number Account ID this assignment is linked to
Vectra.Assignment.AssignedBy String Who lastly assigned this assignment
Vectra.Assignment.AssignedDate String When this assignment was lastly assigned
Vectra.Assignment.AssignedTo String To who this assignment is assigned
Vectra.Assignment.HostID String Host ID this assignment is linked to
Vectra.Assignment.ID Number Assignment ID (unique)
Vectra.Assignment.IsResolved Boolean Is this assignment resolved
Vectra.Assignment.OutcomeCategory String Assignment Outcome category
Vectra.Assignment.OutcomeTitle String Assignment Outcome title
Vectra.Assignment.TriagedDetections String List of Detection that have been triaged with the resolution
Vectra.Assignment.TriagedAs String Name of the triage rule if any
Vectra.Assignment.ResolvedBy String Who resolved this assignment
Vectra.Assignment.ResolvedDate string When this assignment was resolved

vectra-search-users


Returns a list of Vectra Users. All search attributes will be cumulative.

Base Command

vectra-search-users

Input

Argument Name Description Required
username Filters by user name. Optional
role Filters by user role. Optional
type Filters by type (‘Local’, ‘SAML’, …). Possible values are: local, SAML. Optional
last_login_datetime Filters for Users that logged in since the given datetime. Optional

Context Output

Path Type Description
Vectra.User.Email String User’s email address
Vectra.User.ID Number User ID (unique)
Vectra.User.Role String User’s role
Vectra.User.Type String User type (‘Local’, ‘SAML’, …)
Vectra.User.Username String Username
Vectra.User.LastLoginDate String User’s last login datetime

vectra-search-outcomes


Returns a list of assignment outcomes.

Base Command

vectra-search-outcomes

Input

There are no input arguments for this command.

Context Output

Path Type Description
Vectra.Outcome.IsBuiltIn String Is this Outcome a builtin Outcome
Vectra.Outcome.Category String Outcome’s category (‘False Positive’, ‘Benign True Positive’, ‘Malicious True Positive’)
Vectra.Outcome.ID Number Outcome ID (unique)
Vectra.Outcome.Title String Outcome title

vectra-account-describe


Returns a single Account details

Base Command

vectra-account-describe

Input

Argument Name Description Required
id Account ID you want to get details on. Optional

Context Output

Path Type Description
Vectra.Account.Assignee String Vectra user account this Account is assigned to
Vectra.Account.AssignedDate String Assignment date
Vectra.Account.CertaintyScore Number Account certainty score
Vectra.Account.ID Number Account ID (unique)
Vectra.Account.LastDetectionTimestamp String Last time a detection linked to this account has been seen
Vectra.Account.PrivilegeLevel Number Account privilege level (from 1 to 10)
Vectra.Account.PrivilegeCategory String Account privilege category (Either ‘Low’, ‘Medium’ or ‘High’ - Privilege levels of 1-2 > ‘Low’, 3-7 > ‘Medium’, 8-10 > ‘High’)
Vectra.Account.Severity String Account severity according to scores (‘Low’, ‘Medium’, ‘High’, ‘Critical’)
Vectra.Account.State String Account state (‘active’, ‘inactive’)
Vectra.Account.Tags String Account tags
Vectra.Account.ThreatScore Number Account threat score
Vectra.Account.Type String Account type (‘kerberos’ or ‘o365’)
Vectra.Account.URL String Account URL to pivot to Vectra UI
Vectra.Account.Name String The username of the account

vectra-account-add-tags


Add tags to an Account

Base Command

vectra-account-add-tags

Input

Argument Name Description Required
id Account ID you want to add tags on. Optional
tags The tags list (comma separated). Optional

Context Output

There is no context output for this command.

vectra-account-del-tags


Delete tags from an Account

Base Command

vectra-account-del-tags

Input

Argument Name Description Required
id Account ID you want to del tags from. Optional
tags The tags list (comma separated). Optional

Context Output

There is no context output for this command.

vectra-account-tag-list


Returns a list of tags for a specified account.

Base Command

vectra-account-tag-list

Input

Argument Name Description Required
id Specify the ID of the account. Required

Context Output

Path Type Description
Vectra.Account.ID Number ID of the account associated with the tags.
Vectra.Account.Tags String Tags associated to the account.

Command example

!vectra-account-tag-list id="2"

Context Example

{
    "Vectra.Account": {
        "ID": 2,
        "Tags": [
            "note",
            "tag_from_xsoar",
            "tag_from_vectra"
        ]
    }
}

Human Readable Output

List of tags: note, tag_from_xsoar, tag_from_vectra

vectra-account-note-add


Add a note to the account.

Base Command

vectra-account-note-add

Input

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

Context Output

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

Command example

!vectra-account-note-add account_id="2" note="test note"

Context Example

{
    "Vectra.Account.Notes": {
        "date_created": "2024-07-10T07:30:58.574942Z",
        "created_by": "xsoar",
        "note": "test note",
        "note_id": 1959,
        "account_id": 2
    }
}

Human Readable Output

The note has been successfully added to the account

Returned Note ID: 1959

vectra-account-note-update


Update a note in the account.

Base Command

vectra-account-note-update

Input

Argument Name Description Required
account_id Specify the ID of the account. Required
note_id Specify the ID of the note.

Note: Use the vectra-account-note-list command to get note_id.
Required
note Note to be updated for the specified note_id. Required

Context Output

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

Command example

!vectra-account-note-update account_id="2" note_id="1959" note="updated test note"

Context Example

{
    "Vectra.Account.Notes": {
        "date_created": "2024-07-10T07:30:58.574942Z",
        "date_modified": "2024-07-12T06:42:29.546835Z",
        "created_by": "xsoar",
        "modified_by": "xsoar",
        "note": "updated test note",
        "note_id": 1959,
        "account_id": 2
    }
}

Human Readable Output

The note has been successfully updated in the account

vectra-account-note-remove


Remove a note from the account.

Base Command

vectra-account-note-remove

Input

Argument Name Description Required
account_id Specify the ID of the account. Required
note_id Specify the ID of the note.

Note: Use the vectra-account-note-list command to get note_id.
Required

Context Output

There is no context output for this command.

Command example

!vectra-account-note-remove account_id="2" note_id="1959"

Human Readable Output

The note has been successfully removed from the account

vectra-account-note-list


List all notes of the specific account.

Base Command

vectra-account-note-list

Input

Argument Name Description Required
account_id Specify the ID of the account. Required

Context Output

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

Command example

!vectra-account-note-list account_id="2"

Context Example

{
    "Vectra.Account.Notes": [
        {
            "date_created": "2024-07-10T05:40:31Z",
            "date_modified": "2024-07-16T12:56:30Z",
            "created_by": "xsoar",
            "modified_by": "xsoar",
            "note": "updated_note",
            "note_id": 1959,
            "account_id": 2
        },
        {
            "date_created": "2024-07-08T07:11:49Z",
            "created_by": "xsoar",
            "note": "Here comes your note TEST",
            "note_id": 1906,
            "account_id": 2
        }
    ]
}

Human Readable Output

Notes Table
Note ID Note Created By Created Date Modified By Modified Date
1959 updated_note xsoar 2024-07-10T05:40:31Z xsoar 2024-07-16T12:56:30Z
1906 Here comes your note TEST xsoar 2024-07-08T07:11:49Z    

vectra-account-markall-detections-asfixed


Mark active detections as fixed by providing the ID of the account in the argument.

Base Command

vectra-account-markall-detections-asfixed

Input

Argument Name Description Required
account_id Provide an account ID. Required

Context Output

There is no context output for this command.

Command example

!vectra-account-markall-detections-asfixed account_id=109

Human Readable Output

The active detections of the provided account have been successfully marked as fixed.

vectra-account-markall-detections-asclosed


Mark active detections as closed by providing the ID of the account in the argument.

Base Command

vectra-account-markall-detections-asclosed

Input

Argument Name Description Required
account_id Provide an account 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-account-markall-detections-asclosed account_id=108 close_reason=benign

Human Readable Output

The active detections of the provided account have been successfully closed as benign

vectra-host-describe


Returns a single Host details

Base Command

vectra-host-describe

Input

Argument Name Description Required
id Host ID you want to get details on. Optional

Context Output

Path Type Description
Vectra.Host.Assignee String Vectra user account this Host is assigned to
Vectra.Host.AssignedDate String Assignment date
Vectra.Host.CertaintyScore Number Host certainty score
Vectra.Host.HasActiveTraffic Boolean Whether this Host has active traffic
Vectra.Host.Hostname String Host name
Vectra.Host.ID Number Host ID (Unique)
Vectra.Host.IP String Host IP address
Vectra.Host.IsKeyAsset Boolean Whether this Host is seen as a key asset
Vectra.Host.IsTargetingKeyAsset Boolean Whether this Host is targeting a key asset
Vectra.Host.PrivilegeLevel Number Host privilege level (from 1 to 10)
Vectra.Host.PrivilegeCategory String Host privilege category. (Either ‘Low’, ‘Medium’ or ‘High’ - Privilege levels of 1-2 > ‘Low’, 3-7 > ‘Medium’, 8-10 > ‘High’)
Vectra.Host.ProbableOwner String Host probable owner
Vectra.Host.SensorLUID String Sensor LUID that saw this Host
Vectra.Host.SensorName String Sensor Name that saw this Host
Vectra.Host.Sensor String Sensor details that have seen this Host
Vectra.Host.Severity String Host severity according to scores (‘Low’, ‘Medium’, ‘High’, ‘Critical’)
Vectra.Host.State String Host state (‘active’, ‘inactive’)
Vectra.Host.Tags String Host tags
Vectra.Host.ThreatScore Number Host threat score
Vectra.Host.URL String Host URL to pivot to Vectra UI

vectra-host-add-tags


Add tags to an Host

Base Command

vectra-host-add-tags

Input

Argument Name Description Required
id Host ID you want to add tags on. Optional
tags The tags list (comma separated). Optional

Context Output

There is no context output for this command.

vectra-host-del-tags


Delete tags from an Host

Base Command

vectra-host-del-tags

Input

Argument Name Description Required
id Host ID you want to del tags from. Optional
tags The tags list (comma separated). Optional

Context Output

There is no context output for this command.

vectra-host-tag-list


Returns a list of tags for a specified host.

Base Command

vectra-host-tag-list

Input

Argument Name Description Required
id Specify the ID of the host. Required

Context Output

Path Type Description
Vectra.Host.ID Number ID of the host associated with the tags.
Vectra.Host.Tags String Tags associated to the host.

Command example

!vectra-host-tag-list id="2"

Context Example

{
    "Vectra.Host": {
        "ID": 2,
        "Tags": [
            "note",
            "tag_from_xsoar",
            "tag_from_vectra"
        ]
    }
}

Human Readable Output

List of tags: note, tag_from_xsoar, tag_from_vectra

vectra-host-note-add


Add a note to the host.

Base Command

vectra-host-note-add

Input

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

Context Output

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

Command example

!vectra-host-note-add host_id="5" note="test note"

Context Example

{
    "Vectra.Host.Notes": {
        "date_created": "2024-07-10T07:31:58.574942Z",
        "created_by": "xsoar",
        "note": "test note",
        "note_id": 1960,
        "host_id": 5
    }
}

Human Readable Output

The note has been successfully added to the host

Returned Note ID: 1960

vectra-host-note-update


Update a note in the host.

Base Command

vectra-host-note-update

Input

Argument Name Description Required
host_id Specify the ID of the host. Required
note_id Specify the ID of the note.

Note: Use the vectra-host-note-list command to get note_id.
Required
note Note to be updated for the specified note_id. Required

Context Output

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

Command example

!vectra-account-note-update host_id="7" note_id="1960" note="updated test note"

Context Example

{
    "Vectra.Host.Notes": {
        "date_created": "2024-07-10T07:31:58.574942Z",
        "date_modified": "2024-07-12T06:44:29.546835Z",
        "created_by": "xsoar",
        "modified_by": "xsoar",
        "note": "updated test note",
        "note_id": 1960,
        "host_id": 7
    }
}

Human Readable Output

The note has been successfully updated in the host

vectra-host-note-remove


Remove a note from the host.

Base Command

vectra-host-note-remove

Input

Argument Name Description Required
host_id Specify the ID of the host. Required
note_id Specify the ID of the note.

Note: Use the vectra-host-note-list command to get note_id.
Required

Context Output

There is no context output for this command.

Command example

!vectra-host-note-remove host_id="7" note_id="1960"

Human Readable Output

The note has been successfully removed from the host

vectra-host-note-list


List all notes of the specific host.

Base Command

vectra-host-note-list

Input

Argument Name Description Required
host_id Specify the ID of the host. Required

Context Output

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

Command example

!vectra-host-note-list host_id="7"

Context Example

{
    "Vectra.Host.Notes": [
        {
            "date_created": "2024-07-11T07:32:31Z",
            "created_by": "xsoar",
            "note": "test note",
            "note_id": 1960,
            "host_id": 7
        },
        {
            "date_created": "2024-07-11T06:23:07Z",
            "created_by": "cds_xsoar",
            "note": "test note",
            "note_id": 1982,
            "host_id": 7
        }
    ]
}

Human Readable Output

Notes Table
Note ID Note Created By Created Date
1960 test note xsoar 2024-07-11T07:32:31Z
1982 test note cds_xsoar 2024-07-11T06:23:07Z

vectra-host-markall-detections-asfixed


Mark active detections as fixed by providing ID of the host in the argument.

Base Command

vectra-host-markall-detections-asfixed

Input

Argument Name Description Required
host_id Provide a host ID. Required

Context Output

There is no context output for this command.

Command example

!vectra-host-markall-detections-asfixed host_id=23176

Human Readable Output

The active detections of the provided host have been successfully marked as fixed.

vectra-host-markall-detections-asclosed


Mark active detections as closed by providing the ID of the host in the argument.

Base Command

vectra-host-markall-detections-asclosed

Input

Argument Name Description Required
host_id Provide a host 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-host-markall-detections-asclosed host_id=23176 close_reason=remediated

Human Readable Output

The active detections of the provided host have been successfully closed as remediated

vectra-detection-describe


Returns a single detection details

Base Command

vectra-detection-describe

Input

Argument Name Description Required
id Detection ID you want to get details on. Optional

Context Output

Path Type Description
Vectra.Detection.Assignee String Vectra user account this detection is assigned to
Vectra.Detection.AssignedDate String Assignment date
Vectra.Detection.Category String Detection category (Lateral, Exfil, …)
Vectra.Detection.CertaintyScore Number Detection certainty score
Vectra.Detection.Description String Detection description
Vectra.Detection.DestinationIPs String Detection destination IPs
Vectra.Detection.DestinationPorts String Detection destination ports
Vectra.Detection.FirstTimestamp String First time this detection has been seen
Vectra.Detection.ID Number Detection ID (unique)
Vectra.Detection.IsTargetingKeyAsset Boolean Whether this detection is targeting a key asset
Vectra.Detection.LastTimestamp String Last time this detection has been seen
Vectra.Detection.Name String The name of the detection. Would be a user defined name if this detection is triaged or the default type name instead
Vectra.Detection.Severity String Detection severity according to scores (‘Low’, ‘Medium’, ‘High’, ‘Critical’)
Vectra.Detection.SensorLUID String Sensor LUID that saw this detection
Vectra.Detection.SensorName String Sensor name that saw this detection.
Vectra.Detection.SourceAccountID String Account ID relating to this detection
Vectra.Detection.SourceHostID String Host ID relating to this detection
Vectra.Detection.SourceIP String Source IP relating to this detection
Vectra.Detection.State String Detection state (‘active’, ‘inactive’)
Vectra.Detection.Tags String Detection tags
Vectra.Detection.ThreatScore Number Detection threat score
Vectra.Detection.TriageRuleID String Triage rule ID related to this detection
Vectra.Detection.Type String Detection type (Brute Force, Port Sweep, …)
Vectra.Detection.URL String Detection URL to pivot to Vectra UI

vectra-detection-get-pcap


Returns a Detection’s PCAP file (if available)

Base Command

vectra-detection-get-pcap

Input

Argument Name Description Required
id The Detection ID you want to get the PCAP file from. Optional

Context Output

There is no context output for this command.

vectra-detection-markasfixed


Marks/Unmarks a Detection as fixed by providing the Detection ID

Base Command

vectra-detection-markasfixed

Input

Argument Name Description Required
id Detection ID you want to mark/unmark as fixed. Optional
fixed The wanted detection status (“true”, “false”). No default value. Possible values are: true, false. Optional

Context Output

There is no context output for this command.

vectra-detection-add-tags


Add tags to a Detection

Base Command

vectra-detection-add-tags

Input

Argument Name Description Required
id Detection ID you want to add tags on. Optional
tags The tags list (comma separated). Optional

Context Output

There is no context output for this command.

vectra-detection-del-tags


Delete tags from a Detection

Base Command

vectra-detection-del-tags

Input

Argument Name Description Required
id Detection ID you want to del tags from. Optional
tags The tags list (comma separated). Optional

Context Output

There is no context output for this command.

vectra-detection-tag-list


Returns a list of tags for a specified detection.

Base Command

vectra-detection-tag-list

Input

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

Context Output

Path Type Description
Vectra.Detection.ID Number ID of the detection associated with the tags.
Vectra.Detection.Tags String Tags associated to the detection.

Command example

!vectra-detection-tag-list id="2"

Context Example

{
    "Vectra.Detection": {
        "ID": 2,
        "Tags": [
            "note",
            "tag_from_xsoar",
            "tag_from_vectra"
        ]
    }
}

Human Readable Output

List of tags: note, tag_from_xsoar, tag_from_vectra

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 Number ID of the detection associated with the note.
Vectra.Detection.Notes.note_id Number ID of the note.
Vectra.Detection.Notes.date_created Date Date when the note was created.
Vectra.Detection.Notes.date_modified Date Date when the note was last modified.
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-add detection_id="7" note="test note"

Context Example

{
    "Vectra.Detection.Notes": {
        "date_created": "2024-07-10T07:32:58.574942Z",
        "created_by": "xsoar",
        "note": "test note",
        "note_id": 1961,
        "detection_id": 7
    }
}

Human Readable Output

The note has been successfully added to the detection

Returned Note ID: 1961

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.

Note: Use the vectra-detection-note-list command to get note_id.
Required
note Note to be updated for the specified note_id. Required

Context Output

Path Type Description
Vectra.Detection.Notes.detection_id Number ID of the detection associated with the note.
Vectra.Detection.Notes.note_id Number ID of the note.
Vectra.Detection.Notes.date_created Date Date when the note was created.
Vectra.Detection.Notes.date_modified Date Date when the note was last modified.
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="9" note_id="1961" note="updated test note"

Context Example

{
    "Vectra.Detection.Notes": {
        "date_created": "2024-07-10T07:32:58.574942Z",
        "date_modified": "2024-07-12T06:43:29.546835Z",
        "created_by": "xsoar",
        "modified_by": "xsoar",
        "note": "updated test note",
        "note_id": 1961,
        "detection_id": 9
    }
}

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.

Note: Use the vectra-detection-note-list command to get note_id.
Required

Context Output

There is no context output for this command.

Command example

!vectra-detection-note-remove detection_id=97" note_id="1961"

Human Readable Output

The note has been successfully removed from the detection

vectra-detection-note-list


List all notes of the specific 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.detection_id Number ID of the detection associated with the note.
Vectra.Detection.Notes.note_id Number ID of the note.
Vectra.Detection.Notes.date_created Date Date when the note was created.
Vectra.Detection.Notes.date_modified Date Date when the note was last modified.
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-list detection_id="9"

Context Example

{
    "Vectra.Detection.Notes": [
        {
            "date_created": "2024-07-12T04:52:20Z",
            "date_modified": "2024-07-12T10:21:03Z",
            "created_by": "xsoar",
            "modified_by": "xsoar",
            "note": "updated note 2nd",
            "note_id": 1961,
            "detection_id": 9
        },
        {
            "date_created": "2024-07-11T07:32:20Z",
            "created_by": "xsoar",
            "note": "your first test note",
            "note_id": 1937,
            "detection_id": 9
        }
    ]
}

Human Readable Output

Notes Table
Note ID Note Created By Created Date Modified By Modified Date
1961 updated note 2nd xsoar 2024-07-12T04:52:20Z xsoar 2024-07-12T10:21:03Z
1937 your first test note xsoar 2024-07-11T07:32:20Z    

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-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=123,345

Human Readable Output

The provided detection IDs have been successfully re-opened

vectra-outcome-describe


Returns a single outcome details

Base Command

vectra-outcome-describe

Input

Argument Name Description Required
id Outcome ID you want to get details on. Optional

Context Output

Path Type Description
Vectra.Outcome.IsBuiltIn String Is this Outcome a builtin Outcome
Vectra.Outcome.Category String Outcome’s category (‘False Positive’, ‘Benign True Positive’, ‘Malicious True Positive’)
Vectra.Outcome.ID Number Outcome ID (unique)
Vectra.Outcome.Title String Outcome title

vectra-outcome-create


Creates a new assignment outcome

Base Command

vectra-outcome-create

Input

Argument Name Description Required
title Outcome title (will be visible in the UI). Optional
category Outcome category (one of the 3). Possible values are: Benign True Positive, Malicious True Positive, False Positive. Optional

Context Output

Path Type Description
Vectra.Outcome.IsBuiltIn String Is this Outcome a builtin Outcome
Vectra.Outcome.Category String Outcome’s category (‘False Positive’, ‘Benign True Positive’, ‘Malicious True Positive’)
Vectra.Outcome.ID Number Outcome ID (unique)
Vectra.Outcome.Title String Outcome title

vectra-assignment-describe


Returns a single assignment details

Base Command

vectra-assignment-describe

Input

Argument Name Description Required
id Assignment ID you want to get details on. Optional

Context Output

Path Type Description
Vectra.Assignment.AccountID Number Account ID this assignment is linked to
Vectra.Assignment.AssignedBy String Who lastly assigned this assignment
Vectra.Assignment.AssignedDate String When this assignment was lastly assigned
Vectra.Assignment.AssignedTo String To who this assignment is assigned
Vectra.Assignment.HostID String Host ID this assignment is linked to
Vectra.Assignment.ID Number Assignment ID (unique)
Vectra.Assignment.IsResolved Boolean Is this assignment resolved
Vectra.Assignment.OutcomeCategory String Assignment Outcome category
Vectra.Assignment.OutcomeTitle String Assignment Outcome title
Vectra.Assignment.TriagedDetections String List of Detection that have been triaged with the resolution
Vectra.Assignment.TriagedAs String Name of the triage rule if any
Vectra.Assignment.ResolvedBy String Who resolved this assignment
Vectra.Assignment.ResolvedDate string When this assignment was resolved

vectra-assignment-assign


Assigns an Account/Host entity to a Vectra User for investigation. If an assignment already exists on this entity, it will be reassigned

Base Command

vectra-assignment-assign

Input

Argument Name Description Required
assignee_id Assignee’s ID (Vectra User ID). Optional
assignment_id Assignment ID if an assignment already exists for the given entity. Optional
account_id Account ID. Optional
host_id Host ID. Optional

Context Output

Path Type Description
Vectra.Assignment.AccountID Number Account ID this assignment is linked to
Vectra.Assignment.AssignedBy String Who lastly assigned this assignment
Vectra.Assignment.AssignedDate String When this assignment was lastly assigned
Vectra.Assignment.AssignedTo String To who this assignment is assigned
Vectra.Assignment.HostID String Host ID this assignment is linked to
Vectra.Assignment.ID Number Assignment ID (unique)
Vectra.Assignment.IsResolved Boolean Is this assignment resolved
Vectra.Assignment.OutcomeCategory String Assignment Outcome category
Vectra.Assignment.OutcomeTitle String Assignment Outcome title
Vectra.Assignment.TriagedDetections String List of Detection that have been triaged with the resolution
Vectra.Assignment.TriagedAs String Name of the triage rule if any
Vectra.Assignment.ResolvedBy String Who resolved this assignment
Vectra.Assignment.ResolvedDate string When this assignment was resolved

vectra-assignment-resolve


Resolves an assignment by selecting resolution scheme. Could be ‘resolving only’ or ‘resolving by filtering detections’

Base Command

vectra-assignment-resolve

Input

Argument Name Description Required
assignment_id Assignment’s ID. Optional
outcome_id Assignment Outcome’s ID. Optional
note A note to add to this resolution. Optional
detections_filter Do you want to filter detections when resolving this assignment ? [Default is None]. Possible values are: None, Filter Rule. Optional
filter_rule_name Filter rule’s name (when using filter_detections=”Filter Rule”). Optional
detections_list Detection IDs list you want to filter. Optional

Context Output

Path Type Description
Vectra.Assignment.AccountID Number Account ID this assignment is linked to
Vectra.Assignment.AssignedBy String Who lastly assigned this assignment
Vectra.Assignment.AssignedDate String When this assignment was lastly assigned
Vectra.Assignment.AssignedTo String To who this assignment is assigned
Vectra.Assignment.HostID String Host ID this assignment is linked to
Vectra.Assignment.ID Number Assignment ID (unique)
Vectra.Assignment.IsResolved Boolean Is this assignment resolved
Vectra.Assignment.OutcomeCategory String Assignment Outcome category
Vectra.Assignment.OutcomeTitle String Assignment Outcome title
Vectra.Assignment.TriagedDetections String List of Detection that have been triaged with the resolution
Vectra.Assignment.TriagedAs String Name of the triage rule if any
Vectra.Assignment.ResolvedBy String Who resolved this assignment
Vectra.Assignment.ResolvedDate string When this assignment was resolved

vectra-user-describe


Returns a single Vectra User details

Base Command

vectra-user-describe

Input

Argument Name Description Required
id User ID you want to get details on. Optional

Context Output

Path Type Description
Vectra.User.Email String User’s email address
Vectra.User.ID Number User ID (unique)
Vectra.User.Role String User’s role
Vectra.User.Type String User type (‘Local’, ‘SAML’, …)
Vectra.User.Username String Username
Vectra.User.LastLoginDate String User’s last login datetime

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.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.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

!vectra-group-list group_type=account importance=high

Context Example

{
    "Vectra": {
        "Group": [
            {
                "description": "",
                "group_id": 1,
                "id": 1,
                "last_modified": "2024-07-22T06:44:44Z",
                "last_modified_by": "cds_xsoar",
                "members": [
                    {
                        "uid": "user@lab.test.local"
                    },
                    {
                        "uid": "O365:serviceprincipal_00000000-0000-0000-0000-000000000001"
                    }
                ],
                "name": "AccountNoBlock",
                "type": "account"
            },
            {
                "description": "",
                "group_id": 2,
                "id": 2,
                "last_modified": "2024-07-22T06:44:40Z",
                "last_modified_by": "cds_xsoar",
                "members": [
                    {
                        "uid": "O365:serviceprincipal_00000000-0000-0000-0000-000000000001"
                    }
                ],
                "name": "AccountBlock",
                "type": "account"
            }
        ]
    }
}

Human Readable Output

Groups Table

Group ID Name Group Type Members Last Modified Timestamp
1 AccountNoBlock account user@lab.test.local, O365:serviceprincipal_00000000-0000-0000-0000-000000000001 2024-07-22T06:44:44Z
2 AccountBlock account O365:serviceprincipal_00000000-0000-0000-0000-000000000001 2024-07-22T06:44:40Z

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.

Note: You can get the group_id by executing the "vectra-group-list" command.
Required
members A comma-separated list of member values based on the group type.

Note:
You can get the members by executing the "vectra-group-list" command.
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.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.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=3557 members="account_4"

Context Example

{
    "Vectra": {
        "Group": {
        "id": 3,
        "name": "xsoar-account-group-2",
        "last_modified": "2023-09-04T09:22:46Z",
        "last_modified_by": "TEST Client",
        "members": [
            {
                "uid": "account_1"
            },
            {
                "uid": "account_2"
            },
            {
                "uid": "account_3"
            },
            {
                "uid": "account_4"
            }
        ],
        "type": "account",
        "group_id": 3
        }
    }
}

Human Readable Output

Member(s) account_4 have been assigned to the group

Updated group details

Group ID Name Group Type Members Last Modified Timestamp
3 xsoar-account-group-2 account account_1, account_2, account_3, account_4 2023-09-04T09:22:46Z

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.

Note: You can get the group_id by executing the "vectra-group-list" command.
Required
members A comma-separated list of member values based on the group type.

Note:
You can get the members by executing the "vectra-group-list" command.
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.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.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=5 members="2126"

Context Example

{
    "Vectra": {
        "Group": {
            "id": 2,
            "group_id": 2,
            "type": "host",
            "name": "TEST RENAME",
            "description": "TEST RENAME",
            "last_modified": "2023-09-04T06:27:57Z",
            "last_modified_by": "TEST Client"
        }
    }
}

Human Readable Output

Member(s) 2126 have been unassigned from the group

Updated group details

Group ID Name Group Type Description Last Modified Timestamp
2 TEST RENAME host TEST RENAME 2023-09-04T06:27:57Z

Troubleshooting

Receive Notification on an Incident Fetch Error

The administrator and Cortex XSOAR users on the recipient’s list receive a notification when an integration experiences an incident fetch error. Cortex XSOAR users can select their notification method, such as email, from their user preferences. Refer to Cortex XSOAR 6.13 documentation or Cortex XSOAR 8 Cloud documentation or Cortex XSOAR 8.7 On-prem documentation for more information.

The following are tips for handling issues with mirroring incidents between Vectra and Cortex XSOAR
Issue Recommendation
Mirroring is not working. Open Context Data and search for dbot. Confirm the dbot fields are configured correctly either through the mapper for that specific incident type or using setIncident. Specifically, make sure the integration instance is configured correctly for the mirroring direction (incoming, outgoing, both) - dbotMirrorId, dbotMirrorDirection, dbotMirrorInstance, dbotMirrorTags.
Required fields are not getting sent or not visible in UI. This may be a mapping issue, specifically if you have used a custom mapper make sure you’ve covered all the out of box mapper fields.
Notes from Cortex XSOAR have not been mirrored in Vectra Tag is required for mirroring notes from Cortex XSOAR to Vectra. There might be a reason the note is not tagged as the tag needs to be added manually in Cortex XSOAR.
Click Actions > Tags and add the “note” tag (OR the specific tag name which was set up in the Instance Configuration).

Docker timeout issue for Fetch Incidents

  • If you encounter a timeout error while fetching incidents, you can try adjusting the value of the max_fetch parameter in the instance configuration. Setting it to a lower value, such as 50 can help prevent the timeout issue.

  • Another way to address this issue is to increase the timeout of the Docker container. By default, Docker containers have a timeout of 5 minutes. You can increase this timeout to a higher value, such as 10 minutes, to allow more time for the fetch command to complete. Refer to this XSOAR documentation for more information.

Handling HTTP 429 and 5xx Errors

The commands and fetch incidents mechanism will do up to 3 internal retries with a gap of 15, 30, and 60 seconds (exponentially) between the retries.

Configuration parameters

  • server_fqdn — Vectra Detect FQDN or IP (required)
  • authentication_type — Authentication Type
  • credentials — API Token
  • oauth_credentials — Client ID
  • insecure — Trust any certificate (not secure)
  • use_proxy — Use system proxy settings
  • isFetch — Fetch incidents
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval
  • first_fetch — First fetch timestamp
  • mirror_direction — Mirroring Direction
  • refetch_closed_incidents — Re-Fetch closed incidents via mirroring
  • note_tag — Mirror tag for notes
  • fetch_entity_types — Entity types to fetch
  • tags — Tags
  • detection_category — Detection Category
  • detection_type — Detection Type
  • hosts_fetch_query — Hosts fetch query
  • accounts_fetch_query — Accounts fetch query
  • detections_fetch_query — Detections fetch query
  • max_fetch — Max created incidents per fetch
  • look_back — Advanced: Minutes to look back when fetching
  • fetch_escalated_accounts_and_hosts — Advanced: Fetch escalated Accounts and Hosts
  • create_multiple_incidents_by_timestamp — Re-Fetch Account/Host entity and Detection for new activity/event

Commands (47)

  • vectra-account-add-tags

    Add tags to an Account.

  • vectra-account-del-tags

    Delete tags from an Account.

  • vectra-account-describe

    Returns a single Account details.

  • vectra-account-markall-detections-asclosed

    Mark active detections as closed by providing the ID of the account in the argument.

  • vectra-account-markall-detections-asfixed

    Mark active detections as fixed by providing the ID of the account in the argument.

  • vectra-account-note-add

    Add a note to the account.

  • vectra-account-note-list

    List all notes of the specific account.

  • vectra-account-note-remove

    Remove a note from the account.

  • vectra-account-note-update

    Update a note in the account.

  • vectra-account-tag-list

    Returns a list of tags for a specified account.

  • vectra-assignment-assign

    Assigns an Account/Host entity to a Vectra User for investigation. If an assignment already exists on this entity, it will be reassigned.

  • vectra-assignment-describe

    Returns a single assignment details.

  • vectra-assignment-resolve

    Resolves an assignment by selecting resolution scheme. Could be 'resolving only' or 'resolving by filtering detections'.

  • vectra-detection-add-tags

    Add tags to a Detection.

  • vectra-detection-del-tags

    Delete tags from a Detection.

  • vectra-detection-describe

    Returns a single detection details.

  • vectra-detection-get-pcap

    Returns a Detection's PCAP file (if available).

  • vectra-detection-markasfixed

    Marks/Unmarks a Detection as fixed by providing the Detection ID.

  • vectra-detection-note-add

    Add a note to the detection.

  • vectra-detection-note-list

    List all notes of the specific detection.

  • vectra-detection-note-remove

    Remove a note from the detection.

  • vectra-detection-note-update

    Update a note in the detection.

  • vectra-detection-tag-list

    Returns a list of tags for a specified 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-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-host-add-tags

    Add tags to an Host.

  • vectra-host-del-tags

    Delete tags from an Host.

  • vectra-host-describe

    Returns a single Host details.

  • vectra-host-markall-detections-asclosed

    Mark active detections as closed by providing the ID of the host in the argument.

  • vectra-host-markall-detections-asfixed

    Mark active detections as fixed by providing ID of the host in the argument.

  • vectra-host-note-add

    Add a note to the host.

  • vectra-host-note-list

    List all notes of the specific host.

  • vectra-host-note-remove

    Remove a note from the host.

  • vectra-host-note-update

    Update a note in the host.

  • vectra-host-tag-list

    Returns a list of tags for a specified host.

  • vectra-outcome-create

    Creates a new assignment outcome.

  • vectra-outcome-describe

    Returns a single outcome details.

  • vectra-search-accounts

    Returns a list of Account objects. All search attributes will be cumulative unless you're using the search_query_only one, in that case, only this one will be taken into account.

  • vectra-search-assignments

    Return a list of assignments. By default already resolved assignment are not returned.

  • vectra-search-detections

    Returns a list of Detection objects. All search attributes will be cumulative unless you're using the search_query_only one, in that case, only this one will be taken into account.

  • vectra-search-hosts

    Returns a list of Host objects. All search attributes will be cumulative unless you're using the search_query_only one, in that case, only this one will be taken into account.

  • vectra-search-outcomes

    Returns a list of assignment outcomes.

  • vectra-search-users

    Returns a list of Vectra Users. All search attributes will be cumulative.

  • vectra-user-describe

    Returns a single Vectra User details.

"""Base Integration for Cortex XSOAR - Unit Tests file

Pytest Unit Tests: all function names must start with "test_"

More details: https://xsoar.pan.dev/docs/integrations/unit-testing

MAKE SURE YOU REVIEW/REPLACE ALL THE COMMENTS MARKED AS "TODO"

You must add at least a Unit Test function for every XSOAR command
you are implementing with your integration
"""

import copy
import json
import os
from contextlib import nullcontext as does_not_raise

import demistomock as demisto
import pytest
import VectraDetect
from CommonServerPython import DemistoException
from VectraDetect import (
    API_ENDPOINT_GROUPS,
    BACK_IN_TIME_SEARCH_IN_MINUTES,
    ENDPOINTS,
    ERRORS,
    MAX_RESULTS,
    NOTE_OUTPUT_KEY_FIELD,
    OUTPUT_PREFIXES,
    UI_ACCOUNTS,
    UI_DETECTIONS,
    UI_HOSTS,
    UTM_PIVOT,
    VALID_GROUP_TYPE,
    VALID_IMPORTANCE_VALUE,
    Client,
    VectraException,
    fetch_incidents,
    get_modified_remote_data_command,
    get_remote_data_command,
    markall_detections_asclosed_command,
    markall_detections_asfixed_command,
    mark_detections_asclosed_command,
    mark_detections_asopen_command,
    update_remote_system_command,
    vectra_group_assign_command,
    vectra_group_list_command,
    vectra_group_unassign_command,
)

SERVER_FQDN = "vectra.test"
SERVER_URL = f"https://{SERVER_FQDN}"
API_VERSION_URI = "/api/v2.5"
API_URL = f"{SERVER_URL}{API_VERSION_URI}"
API_SEARCH_ENDPOINT_ACCOUNTS = "/search/accounts"
API_SEARCH_ENDPOINT_DETECTIONS = "/search/detections"
API_SEARCH_ENDPOINT_HOSTS = "/search/hosts"
API_ENDPOINT_ASSIGNMENTS = "/assignments"
API_ENDPOINT_DETECTIONS = "/detections"
API_ENDPOINT_OUTCOMES = "/assignment_outcomes"
API_ENDPOINT_USERS = "/users"
API_TAGGING = "/tagging"
API_ENDPOINT_HOST = "/hosts"
API_ENDPOINT_ACCOUNT = "/accounts"


def load_test_data(json_path):
    relative_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "test_data")
    with open(os.path.join(relative_dir, json_path)) as f:
        return json.load(f)


@pytest.fixture
def client():
    from VectraDetect import Client

    # Call the functionbals
    return Client(base_url=f"{API_URL}", verify=True, proxy=False, use_oauth=False, api_token="test_api_token")


@pytest.fixture
def oauth_client():
    from VectraDetect import Client

    return Client(
        base_url=f"{API_URL}",
        verify=True,
        proxy=False,
        use_oauth=True,
        client_id="test_client_id",
        client_secret="test_client_secret",
    )


#####
# ## Globals
#


integration_params = None

# helper functions


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


#####
# ## Validate helpers
#


@pytest.mark.parametrize(
    "input,expected",
    [
        ("true", True),
        ("True", True),
        ("trUE", True),
        ("YES", True),
        ("false", False),
        ("NO", False),
        ("vectra", None),
        ("", None),
        (None, None),
    ],
)
def test_str2bool(input, expected):
    """
    Tests the str2bool helper function.
    """
    from VectraDetect import str2bool

    assert str2bool(input) == expected


@pytest.mark.parametrize("input,expected", [(100, 100), (8, 8), ("10", 10), (250, MAX_RESULTS)])
def test_sanitize_max_results(input, expected):
    """
    Tests sanitize_max_results helper function.
    """
    from VectraDetect import sanitize_max_results

    assert sanitize_max_results(input) == expected


@pytest.mark.parametrize(
    "input_threat,input_certainty,expected", [(5, 5, "Low"), (39, 55, "Medium"), (51, 35, "High"), (50, 50, "Critical")]
)
def test_scores_to_severity(input_threat, input_certainty, expected):
    """
    Tests scores_to_severity helper function.
    """
    from VectraDetect import scores_to_severity

    assert scores_to_severity(input_threat, input_certainty) == expected


@pytest.mark.parametrize(
    "input_severity,expected", [("Critical", 4), ("High", 3), ("Medium", 2), ("Low", 1), ("test", 0), ("", 0)]
)
def test_severity_string_to_int(input_severity, expected):
    """
    Tests severity_string_to_int helper function.
    """
    from VectraDetect import severity_string_to_int

    assert severity_string_to_int(input_severity) == expected


@pytest.mark.parametrize(
    "input_date,expected",
    [
        ("2022-10-10T14:28:56Z", "2022-10-10T14:28:56.000Z"),
        ("2022-01-01T01:01:01Z", "2022-01-01T01:01:01.000Z"),
        ("Vectra", None),
        (None, None),
    ],
)
def test_convert_date(input_date, expected):
    """
    Tests convert_Date helper function.
    """
    from VectraDetect import convert_date

    assert convert_date(input_date) == expected


# Compute all combinations
validate_argument_test_data = []
for input_type in ("min_id", "max_id"):
    for valid_value in (1, 5):
        validate_argument_test_data.append(
            pytest.param(input_type, valid_value, does_not_raise(), id=f"{input_type}_{valid_value}_no-exception")
        )
    for invalid_value in {0, -3, 12.3, "vectra", "", None}:
        validate_argument_test_data.append(
            pytest.param(
                input_type,
                invalid_value,
                pytest.raises(ValueError, match=f'"{input_type}" must be an integer greater than 0'),
                id=f"{input_type}_{'none' if invalid_value is None else invalid_value}_gt-0",
            )
        )
for input_type in ("min_threat", "min_certainty", "max_threat", "max_certainty"):
    for valid_value in (0, 99):
        validate_argument_test_data.append(
            pytest.param(input_type, valid_value, does_not_raise(), id=f"{input_type}_{valid_value}_no-exception")
        )
    for invalid_value in {-1, 100, -3, 12.3, "vectra", "", None}:
        validate_argument_test_data.append(
            pytest.param(
                input_type,
                invalid_value,
                pytest.raises(ValueError, match=f'"{input_type}" must be an integer between 0 and 99'),
                id=f"{input_type}_{'none' if invalid_value is None else invalid_value}_0-99",
            )
        )
for input_type in ("min_privilege_level",):
    for valid_value in (1, 5, 10):
        validate_argument_test_data.append(
            pytest.param(input_type, valid_value, does_not_raise(), id=f"{input_type}_{valid_value}_no-exception")
        )
    for invalid_value in {0, 11, -3, 12.3, "vectra", "", None}:
        validate_argument_test_data.append(
            pytest.param(
                input_type,
                invalid_value,
                pytest.raises(ValueError, match=f'"{input_type}" must be an integer between 1 and 10'),
                id=f"{input_type}_{'none' if invalid_value is None else invalid_value}_1-10",
            )
        )
validate_argument_test_data.append(
    pytest.param("vectra", "vectra", pytest.raises(SystemError, match="Unknown argument type"), id="invalid-argument_exception")
)


@pytest.mark.parametrize("input_type,input_value,expected", validate_argument_test_data)
def test_validate_argument(input_type, input_value, expected):
    """
    Tests validate_argument helper command
    """
    from VectraDetect import validate_argument

    with expected:
        assert validate_argument(input_type, input_value) is not None


@pytest.mark.parametrize(
    "min_type,min_value,max_type,max_value,expected",
    [
        ("min_id", 12, "max_id", 15, does_not_raise()),
        ("min_id", 20, "max_id", 20, does_not_raise()),
        ("min_id", 30, "max_id", 25, pytest.raises(ValueError, match='"max_id" must be greater than or equal to "min_id"')),
        ("min_threat", 12, "max_threat", 35, does_not_raise()),
        ("min_certainty", 15, "max_certainty", 35, does_not_raise()),
    ],
)
def test_validate_min_max(min_type, min_value, max_type, max_value, expected):
    """
    Tests validate_min_max helper function.
    """

    from VectraDetect import validate_min_max

    with expected:
        assert validate_min_max(min_type, min_value, max_type, max_value) is True


@pytest.mark.parametrize(
    "input_list,expected,exception",
    [
        pytest.param(None, None, does_not_raise(), id="none_no-exception"),
        pytest.param("", None, does_not_raise(), id="empty_no-exception"),
        pytest.param("1", {1}, does_not_raise(), id="single-element_no-exception"),
        pytest.param("1,2,3", {1, 2, 3}, does_not_raise(), id="multiple-elements_no-exception"),
        pytest.param("1 , 2, 3", {1, 2, 3}, does_not_raise(), id="with-spaces_no-exception"),
        pytest.param("1 , 2, 3", {1, 2, 3}, does_not_raise(), id="with-spaces_no-exception"),
        pytest.param("1 , 2, , 3", {1, 2, 3}, does_not_raise(), id="with-empty-element_no-exception"),
    ],
)
def test_sanitize_str_ids_list_to_set(input_list, expected, exception):
    """
    Tests sanitize_str_ids_list_to_set helper function.
    """

    from VectraDetect import sanitize_str_ids_list_to_set

    with exception:
        assert sanitize_str_ids_list_to_set(input_list) == expected


@pytest.mark.parametrize(
    "object_type,params,expected",
    [
        pytest.param("account", {"min_id": "12"}, "account.id:>=12", id="account_min-id"),
        pytest.param("account", {"max_threat": "12"}, "account.threat:<=12", id="account_max-threat"),
        pytest.param(
            "account",
            {"min_id": "12", "max_certainty": "28"},
            "account.id:>=12 account.certainty:<=28",
            id="account_min-id_max-certainty",
        ),
        pytest.param("host", {"min_id": "12", "state": "inactive"}, 'host.id:>=12 host.state:"inactive"', id="host_min-id_state"),
        pytest.param(
            "host",
            {"last_timestamp": "20220101T0123", "state": "active"},
            'host.last_detection_timestamp:>=20220101T0123 host.state:"active"',
            id="host_last_timestamp_state",
        ),
        pytest.param(
            "detection",
            {"last_timestamp": "20220101T0123", "state": "active"},
            'detection.last_timestamp:>=20220101T0123 detection.state:"active"',
            id="detection_last_timestamp_state",
        ),
    ],
)
def test_build_search_query(object_type, params, expected):
    """
    Tests build_search_query helper command
    """

    from VectraDetect import build_search_query

    assert build_search_query(object_type, params) == expected


@pytest.mark.parametrize(
    "object_type,id,expected,exception",
    [
        pytest.param("account", 123, f"{SERVER_URL}{UI_ACCOUNTS}/123{UTM_PIVOT}", does_not_raise(), id="account_ok"),
        pytest.param("host", 234, f"{SERVER_URL}{UI_HOSTS}/234{UTM_PIVOT}", does_not_raise(), id="host_ok"),
        pytest.param("detection", 345, f"{SERVER_URL}{UI_DETECTIONS}/345{UTM_PIVOT}", does_not_raise(), id="detection_ok"),
        pytest.param("vectra", 15, True, pytest.raises(Exception, match="Unknown type : vectra"), id="invalid-type_exception"),
        pytest.param("account", None, True, pytest.raises(Exception, match="Missing ID"), id="invalid-id_exception"),
    ],
)
def test_forge_entity_url(object_type, id, expected, exception):
    """
    Tests forge_entity_url helper function
    """
    # Force some integration settings for testing purpose
    # It's used inside the forge_entity_url function
    # Need to import all module due to global variable
    import VectraDetect

    VectraDetect.global_UI_URL = SERVER_URL

    with exception:
        assert VectraDetect.forge_entity_url(object_type, id) == expected


@pytest.mark.parametrize(
    "api_entry,expected",
    [
        pytest.param(
            load_test_data("single_account.json"),
            load_test_data("single_account_extracted.json").get("common_extract"),
            id="common_account_ok",
        ),
        pytest.param(
            load_test_data("single_host.json"),
            load_test_data("single_host_extracted.json").get("common_extract"),
            id="common_host_ok",
        ),
        pytest.param(
            load_test_data("single_detection.json"),
            load_test_data("single_detection_extracted.json").get("common_extract"),
            id="common_detection_ok",
        ),
    ],
)
def test_common_extract_data(api_entry, expected):
    """
    Tests common_extract_data helper function
    """
    from VectraDetect import common_extract_data

    assert common_extract_data(api_entry) == expected


@pytest.mark.parametrize(
    "api_entry,expected",
    [
        pytest.param(
            load_test_data("single_account.json"),
            load_test_data("single_account_extracted.json").get("account_extract"),
            id="account_ok",
        )
    ],
)
def test_extract_account_data(api_entry, expected):
    """
    Tests extract_account_data helper function
    """
    # Force some integration settings for testing purpose
    # It's used inside the forge_entity_url function
    # Need to import all module due to global variable
    import VectraDetect

    VectraDetect.global_UI_URL = SERVER_URL

    from VectraDetect import extract_account_data

    assert extract_account_data(api_entry) == expected


@pytest.mark.parametrize(
    "api_entry,expected",
    [
        pytest.param(
            load_test_data("single_detection.json"),
            load_test_data("single_detection_extracted.json").get("detection_extract"),
            id="common_detection_ok",
        ),
    ],
)
def test_extract_detection_data(api_entry, expected):
    """
    Tests extract_detection_data helper function
    """
    # Force some integration settings for testing purpose
    # It's used inside the forge_entity_url function
    # Need to import all module due to global variable
    import VectraDetect

    VectraDetect.global_UI_URL = SERVER_URL

    from VectraDetect import extract_detection_data

    assert extract_detection_data(api_entry) == expected


@pytest.mark.parametrize(
    "api_entry,expected",
    [
        pytest.param(
            load_test_data("single_host.json"),
            load_test_data("single_host_extracted.json").get("host_extract"),
            id="common_host_ok",
        ),
    ],
)
def test_extract_host_data(api_entry, expected):
    """
    Tests extract_host_data helper function
    """
    # Force some integration settings for testing purpose
    # It's used inside the forge_entity_url function
    # Need to import all module due to global variable
    import VectraDetect

    VectraDetect.global_UI_URL = SERVER_URL

    from VectraDetect import extract_host_data

    assert extract_host_data(api_entry) == expected


@pytest.mark.parametrize(
    "api_entry,expected",
    [
        pytest.param(
            load_test_data("single_assignment.json"), load_test_data("single_assignment_extracted.json"), id="assignment_ok"
        )
    ],
)
def test_extract_assignment_data(api_entry, expected):
    """
    Tests extract_assignment_data helper function
    """
    from VectraDetect import extract_assignment_data

    assert extract_assignment_data(api_entry) == expected


@pytest.mark.parametrize(
    "api_entry,expected",
    [pytest.param(load_test_data("single_outcome.json"), load_test_data("single_outcome_extracted.json"), id="outcome_ok")],
)
def test_extract_outcome_data(api_entry, expected):
    """
    Tests extract_outcome_data helper function
    """
    from VectraDetect import extract_outcome_data

    assert extract_outcome_data(api_entry) == expected


@pytest.mark.parametrize(
    "input_date,look_back,expected,exception",
    [
        pytest.param("2022-06-30T01:23:45Z", "60", "2022-06-30T0023", does_not_raise(), id="timestamp_ok"),
        pytest.param("2022-06-30T01:23:45.000Z", "60", "2022-06-30T0023", does_not_raise(), id="timestamp-with-milli_ok"),
        pytest.param(
            "vectra",
            BACK_IN_TIME_SEARCH_IN_MINUTES,
            "exception",
            pytest.raises(SystemError, match="Invalid ISO date"),
            id="string_exception",
        ),
        pytest.param(
            "2022-06-30T01:23:45.000Z",
            "-1",
            "-1",
            pytest.raises(ValueError, match=ERRORS["POSITIVE_VALUE"].format("look back")),
            id="negative_value_exception",
        ),
    ],
)
def test_iso_date_to_vectra_start_time(input_date, look_back, expected, exception):
    """
    Tests iso_date_to_vectra_start_time helper function
    """
    from VectraDetect import iso_date_to_vectra_start_time

    with exception:
        assert iso_date_to_vectra_start_time(input_date, look_back) == expected


@pytest.mark.parametrize(
    "input_severity,expected", [("critical", "Critical"), ("HIGH", "High"), ("mEdIuM", "Medium"), ("", "Unknown")]
)
def test_unify_severity(input_severity, expected):
    """
    Tests severity_string_to_int helper function.
    """
    from VectraDetect import unify_severity

    assert unify_severity(input_severity) == expected


@pytest.mark.parametrize(
    "input_category,expected",
    [
        ("benign_true_positive", "Benign True Positive"),
        ("malicious_true_positive", "Malicious True Positive"),
        ("false_positive", "False Positive"),
        ("dummy", None),
        ("", None),
    ],
)
def test_convert_outcome_category_raw2text(input_category, expected):
    """
    Tests convert_outcome_category_raw2text helper function.
    """
    from VectraDetect import convert_outcome_category_raw2text

    assert convert_outcome_category_raw2text(input_category) == expected


@pytest.mark.parametrize(
    "input_category,expected",
    [
        ("Benign True Positive", "benign_true_positive"),
        ("Malicious True Positive", "malicious_true_positive"),
        ("False Positive", "false_positive"),
        ("dummy", None),
        ("", None),
    ],
)
def test_convert_outcome_category_text2raw(input_category, expected):
    """
    Tests convert_outcome_category_text2raw helper function.
    """
    from VectraDetect import convert_outcome_category_text2raw

    assert convert_outcome_category_text2raw(input_category) == expected


#####
# ## Validate functions
#


@pytest.mark.parametrize(
    "integration_params,expected",
    [
        pytest.param({}, "ok", id="no-fetch"),
        pytest.param({"isFetch": True, "first_fetch": "vectra"}, "Fetch first timestamp is invalid.", id="wrong-fetch-time"),
        pytest.param(
            {"isFetch": True, "first_fetch": "7 days", "fetch_entity_types": ["vectra"]},
            'This entity type "vectra" is invalid.',
            id="wrong-entity-type",
        ),
        pytest.param({"isFetch": True, "first_fetch": "7 days", "fetch_entity_types": ["Hosts"]}, "ok", id="hosts-entity"),
        pytest.param(
            {"isFetch": True, "first_fetch": "7 days", "fetch_entity_types": ["Hosts"], "max_fetch": "vectra"},
            '"vectra" is an invalid value for Max incidents per fetch. The value must be between 1 to 200.',
            id="string-max-fetch",
        ),
        pytest.param(
            {"isFetch": True, "first_fetch": "7 days", "fetch_entity_types": ["Hosts"], "max_fetch": "0"},
            '"0" is an invalid value for Max incidents per fetch. The value must be between 1 to 200.',
            id="0-max-fetch",
        ),
        pytest.param(
            {"isFetch": True, "first_fetch": "7d", "fetch_entity_types": ["Hosts", "Detections"], "max_fetch": "1"},
            "Max incidents per fetch (1) must be >= to the number of entity types you're fetching (2)",
            id="too-low-max-fetch",
        ),
        pytest.param(
            {"isFetch": True, "first_fetch": "7d", "fetch_entity_types": ["Hosts", "Detections"], "max_fetch": "201"},
            ERRORS["INVALID_MAX_FETCH"].format(201),
            id="too-high-max-fetch",
        ),
        pytest.param(
            {"isFetch": True, "first_fetch": "7d", "fetch_entity_types": ["Hosts", "Accounts"], "max_fetch": "5"},
            "ok",
            id="all-good",
        ),
    ],
)
# @freeze_time("2022-07-01 11:00:00 GMT")
def test_test_module(requests_mock, client, integration_params, expected):
    """
    Tests test_module command function.
    """
    from VectraDetect import test_module

    account_data = load_test_data("single_account.json")
    account_response = {"count": 1, "results": [account_data]}
    requests_mock.get(f"{API_URL}{API_SEARCH_ENDPOINT_ACCOUNTS}", json=account_response)
    host_data = load_test_data("single_host.json")
    host_response = {"count": 1, "results": [host_data]}
    requests_mock.get(f"{API_URL}{API_SEARCH_ENDPOINT_HOSTS}", json=host_response)
    detection_data = load_test_data("single_detection.json")
    detection_response = {"count": 1, "results": [detection_data]}
    requests_mock.get(f"{API_URL}{API_SEARCH_ENDPOINT_DETECTIONS}", json=detection_response)

    assignment_data = load_test_data("single_assignment.json")
    assignment_response = {"count": 1, "results": [assignment_data]}
    requests_mock.get(f"{API_URL}{API_ENDPOINT_ASSIGNMENTS}?accounts=36", json=assignment_response)
    requests_mock.get(f"{API_URL}{API_ENDPOINT_ASSIGNMENTS}?hosts=472", json=assignment_response)

    requests_mock.get(
        f"{API_URL}{API_ENDPOINT_DETECTIONS}?state=active&host_id=472", complete_qs=True, json={"results": [detection_data]}
    )

    group_res = load_test_data("group_list_response.json")
    requests_mock.get(f"{API_URL}{API_ENDPOINT_GROUPS}", json=group_res)

    assert test_module(client=client, integration_params=integration_params) == expected


def test_fetch_incidents(mocker, client, requests_mock):
    """
    Given:
    - A client object.
    - A mocked 'getLastRun' method that returns mocked last run.
    - A mocked 'list_entities_request' method that returns a sample entity data.

    When:
    - Fetching incidents using the 'fetch_incidents' function with no additional parameters.

    Then:
    - Assert that the number of fetched incidents is equal to the number of entities in the entity data.
    """
    import VectraDetect

    VectraDetect.global_UI_URL = SERVER_URL

    last_run = load_test_data("fetch_incidents_last_run.json")
    mocker.patch.object(demisto, "getLastRun", return_value=last_run)

    account_data = load_test_data("single_account.json")
    account_response = {"count": 1, "results": [account_data]}
    mocker.patch.object(client, "search_accounts", return_value=account_response)
    host_data = load_test_data("single_host.json")
    host_response = {"count": 1, "results": [host_data]}
    mocker.patch.object(client, "search_hosts", return_value=host_response)
    detection_data = load_test_data("single_detection.json")
    detection_response = {"count": 1, "results": [detection_data]}
    mocker.patch.object(client, "search_detections", return_value=detection_response)

    assignment_data = load_test_data("single_assignment.json")
    assignment_response = {"count": 1, "results": [assignment_data]}
    requests_mock.get(f"{API_URL}{API_ENDPOINT_ASSIGNMENTS}?accounts=36", json=assignment_response)
    requests_mock.get(f"{API_URL}{API_ENDPOINT_ASSIGNMENTS}?hosts=472", json=assignment_response)

    assignment_data = load_test_data("single_assignment.json")
    assignment_response = {"count": 1, "results": [assignment_data]}
    requests_mock.get(f"{API_URL}{API_ENDPOINT_ASSIGNMENTS}?accounts=36", json=assignment_response)
    requests_mock.get(f"{API_URL}{API_ENDPOINT_ASSIGNMENTS}?hosts=472", json=assignment_response)

    requests_mock.get(
        f"{API_URL}{API_ENDPOINT_DETECTIONS}?state=active&host_id=472", complete_qs=True, json={"results": [detection_data]}
    )

    group_res = load_test_data("group_list_response.json")
    requests_mock.get(f"{API_URL}{API_ENDPOINT_GROUPS}", json=group_res)

    params = {
        "isFetch": True,
        "first_fetch": "1 hour",
        "max_fetch": "201",
        "fetch_entity_types": ["Accounts", "Hosts", "Detections"],
        "tags": "hello,world,tags%test",
        "fetch_escalated_accounts_and_hosts": True,
    }
    new_last_run, incidents = fetch_incidents(client, params)
    new_last_run_expected = load_test_data("fetch_incidents_new_last_run.json")
    assert new_last_run == new_last_run_expected

    incidents_expected = load_test_data("fetch_incidents_expected.json")
    assert incidents == incidents_expected


# Test only the exceptions for now
@pytest.mark.parametrize(
    "query_args,expected_outputs,expected_readable,exception",
    [
        pytest.param(
            {"search_query_only": "no-count"},
            None,
            None,
            pytest.raises(VectraException, match="API issue - Response is empty or invalid"),
            id="api-no-count_exception",
        ),
        pytest.param(
            {"search_query": "no-results"},
            None,
            None,
            pytest.raises(VectraException, match="API issue - Response is empty or invalid"),
            id="api-no-results_exception",
        ),
    ],
)
def test_vectra_search_accounts_command(requests_mock, client, query_args, expected_outputs, expected_readable, exception):
    """
    Tests vectra_search_accounts_command command function.
    """
    # Force some integration settings for testing purpose
    # It's used inside the forge_entity_url function
    # Need to import all module due to global variable
    import VectraDetect

    VectraDetect.global_UI_URL = SERVER_URL

    from VectraDetect import vectra_search_accounts_command

    # Default answer
    # Not implemented yet

    # Specific answers
    requests_mock.get(
        f"{API_URL}{API_SEARCH_ENDPOINT_ACCOUNTS}"
        f"?page=1&order_field=last_detection_timestamp&page_size=200"
        f"&query_string=no-count",
        complete_qs=True,
        json={"results": []},
    )
    requests_mock.get(
        f"{API_URL}{API_SEARCH_ENDPOINT_ACCOUNTS}"
        f"?page=1&order_field=last_detection_timestamp&page_size=200"
        f'&query_string=account.state:"active" AND no-results',
        complete_qs=True,
        json={"count": 1},
    )

    with exception:
        result = vectra_search_accounts_command(client=client, **query_args)
        assert result.outputs == expected_outputs
        if expected_outputs is None:
            assert result.readable_output == expected_readable


# Test only the exceptions for now
@pytest.mark.parametrize(
    "query_args,expected_outputs,expected_readable,exception",
    [
        pytest.param(
            {"search_query_only": "no-count"},
            None,
            None,
            pytest.raises(VectraException, match="API issue - Response is empty or invalid"),
            id="api-no-count_exception",
        ),
        pytest.param(
            {"search_query": "no-results"},
            None,
            None,
            pytest.raises(VectraException, match="API issue - Response is empty or invalid"),
            id="api-no-results_exception",
        ),
    ],
)
def test_vectra_search_detections_command(requests_mock, client, query_args, expected_outputs, expected_readable, exception):
    """
    Tests vectra_search_detections_command command function.
    """
    # Force some integration settings for testing purpose
    # It's used inside the forge_entity_url function
    # Need to import all module due to global variable
    import VectraDetect

    VectraDetect.global_UI_URL = SERVER_URL

    from VectraDetect import vectra_search_detections_command

    # Default answer
    # Not implemented yet

    # Specific answers
    requests_mock.get(
        f"{API_URL}{API_SEARCH_ENDPOINT_DETECTIONS}?page=1&order_field=last_timestamp&page_size=200&query_string=no-count",
        complete_qs=True,
        json={"results": []},
    )
    requests_mock.get(
        f"{API_URL}{API_SEARCH_ENDPOINT_DETECTIONS}"
        f"?page=1&order_field=last_timestamp&page_size=200"
        f'&query_string=detection.state:"active" AND no-results',
        complete_qs=True,
        json={"count": 1},
    )

    with exception:
        result = vectra_search_detections_command(client=client, **query_args)
        assert result.outputs == expected_outputs
        if expected_outputs is None:
            assert result.readable_output == expected_readable


# Test only the exceptions for now
@pytest.mark.parametrize(
    "query_args,expected_outputs,expected_readable,exception",
    [
        pytest.param(
            {"search_query_only": "no-count"},
            None,
            None,
            pytest.raises(VectraException, match="API issue - Response is empty or invalid"),
            id="api-no-count_exception",
        ),
        pytest.param(
            {"search_query": "no-results"},
            None,
            None,
            pytest.raises(VectraException, match="API issue - Response is empty or invalid"),
            id="api-no-results_exception",
        ),
    ],
)
def test_vectra_search_hosts_command(requests_mock, client, query_args, expected_outputs, expected_readable, exception):
    """
    Tests vectra_search_hosts_command command function.
    """
    # Force some integration settings for testing purpose
    # It's used inside the forge_entity_url function
    # Need to import all module due to global variable
    import VectraDetect

    VectraDetect.global_UI_URL = SERVER_URL

    from VectraDetect import vectra_search_hosts_command

    # Default answer
    # Not implemented yet

    # Specific answers
    requests_mock.get(
        f"{API_URL}{API_SEARCH_ENDPOINT_HOSTS}?page=1&order_field=last_detection_timestamp&page_size=200&query_string=no-count",
        complete_qs=True,
        json={"results": []},
    )
    requests_mock.get(
        f"{API_URL}{API_SEARCH_ENDPOINT_HOSTS}"
        f"?page=1&order_field=last_detection_timestamp&page_size=200"
        f'&query_string=host.state:"active" AND no-results',
        complete_qs=True,
        json={"count": 1},
    )

    with exception:
        result = vectra_search_hosts_command(client=client, **query_args)
        assert result.outputs == expected_outputs
        if expected_outputs is None:
            assert result.readable_output == expected_readable


@pytest.mark.parametrize(
    "query_args,expected_outputs,expected_readable,exception",
    [pytest.param({}, [load_test_data("single_assignment_extracted.json")], None, does_not_raise(), id="full-pull")],
)
def test_vectra_search_assignments_command(requests_mock, client, query_args, expected_outputs, expected_readable, exception):
    """
    Tests vectra_search_assignments_command command function.
    """
    from VectraDetect import vectra_search_assignments_command

    # Specific answers
    requests_mock.get(
        f"{API_URL}{API_ENDPOINT_ASSIGNMENTS}?resolved=false",
        complete_qs=True,
        json={"count": 1, "results": [load_test_data("single_assignment.json")]},
    )

    with exception:
        result = vectra_search_assignments_command(client=client, **query_args)
        assert result.outputs == expected_outputs
        if expected_outputs is None:
            assert result.readable_output == expected_readable


# Authentication Tests


def test_generate_tokens(oauth_client, requests_mock):
    """
    Given:
    - Mocked response for generating access tokens.
    - VectraDetect Client instance.

    When:
    - Calling the `_generate_tokens` method.

    Then:
    - Ensure the generated access token matches the expected access token.
    """

    # Set up
    access_token = "test_access_token"
    response_data = {"access_token": access_token, "token_type": "Bearer"}
    requests_mock.post(f"{API_URL}/oauth2/token", json=response_data, status_code=200)

    token = oauth_client._generate_tokens()
    assert token == access_token


def test_generate_tokens_failure(oauth_client, requests_mock, mocker):
    """
    Given:
    - Mocked failed response for generating access tokens.
    - VectraDetect Client instance.

    When:
    - Calling the `_generate_tokens` method.

    Then:
    - Ensure the method raises an exception.
    """
    mocker.patch("VectraDetect.get_integration_context", return_value={})
    response_data = {"message": "Invalid payload"}
    requests_mock.post(f"{API_URL}/oauth2/token", json=response_data, status_code=401)

    with pytest.raises(DemistoException) as e:
        oauth_client._generate_tokens()

    assert ERRORS["GENERAL_AUTH_ERROR"].format("401") in str(e.value)


@pytest.mark.parametrize(
    "resp_type,response_data,error_message",
    [
        ("json", {"message": "Invalid payload"}, "Failed to generate OAuth access token - no access_token in response"),
        ("text", "Invalid Response", "Failed to parse json object from response: b'Invalid Response'"),
    ],
)
def test_generate_tokens_ivalid_response(oauth_client, requests_mock, mocker, resp_type, response_data, error_message):
    """
    Given:
    - Mocked failed response for generating access tokens.
    - VectraDetect Client instance.

    When:
    - Calling the `_generate_tokens` method.

    Then:
    - Ensure the method raises an exception.
    """
    mocker.patch("VectraDetect.get_integration_context", return_value={})
    if resp_type == "json":
        requests_mock.post(f"{API_URL}/oauth2/token", json=response_data, status_code=200)
    else:
        requests_mock.post(f"{API_URL}/oauth2/token", text=response_data, status_code=200)

    with pytest.raises(DemistoException) as e:
        oauth_client._generate_tokens()

    assert error_message in str(e.value)


@pytest.mark.parametrize(
    "use_oauth,expected_auth_header",
    [
        (False, "token test_api_token"),
        (True, "Bearer test_access_token"),
    ],
)
def test_get_auth_headers(client, oauth_client, use_oauth, expected_auth_header):
    """
    Given:
    - Client configured with different authentication methods.

    When:
    - Calling _get_auth_headers method.

    Then:
    - Ensure correct authorization header is returned for each auth type.
    """
    headers = oauth_client._get_auth_headers() if use_oauth else client._get_auth_headers()

    assert "Authorization" in headers
    assert headers["Authorization"] == expected_auth_header
    assert "User-Agent" in headers


def test_http_request_oauth_token_refresh_on_401(oauth_client, mocker, requests_mock):
    """
    Given:
    - Client configured with OAuth authentication.
    - HTTP request that returns 401 (token expired).

    When:
    - Making an HTTP request that triggers token refresh.

    Then:
    - Ensure token is refreshed and request is retried successfully.
    """
    # Mock integration context with expired token
    mocker.patch(
        "VectraDetect.get_integration_context",
        return_value={
            "oauth_token": {"access_token": "expired_token", "expire": 0}  # Already expired
        },
    )
    mock_set_context = mocker.patch("VectraDetect.set_integration_context")

    # Mock first request returning 401, then success on retry
    requests_mock.get(
        f"{API_URL}/test",
        [
            {"status_code": 401, "json": {"detail": "Incorrect authentication credentials."}},
            {"status_code": 200, "json": {"success": True}},
        ],
    )

    # Mock token refresh endpoint with expires_in field
    requests_mock.post(
        f"{API_URL}/oauth2/token", json={"access_token": "refreshed_token", "token_type": "Bearer", "expires_in": 21600}
    )

    # Make request that should trigger token refresh
    result = oauth_client.http_request("GET", "/test", resp_type="response")

    # Verify token refresh was called and context was updated
    mock_set_context.assert_called()
    # Check that the context was updated with oauth_token structure
    call_args = mock_set_context.call_args[0][0]
    assert "oauth_token" in call_args
    assert call_args["oauth_token"]["access_token"] == "refreshed_token"
    assert "expire" in call_args["oauth_token"]

    assert result.status_code == 200

    # Verify that the OAuth token refresh endpoint was called
    token_requests = [req for req in requests_mock.request_history if req.method == "POST" and "/oauth2/token" in req.url]
    assert len(token_requests) >= 1
    assert token_requests[0].method == "POST"
    assert f"{API_URL}/oauth2/token" in token_requests[0].url


def test_http_request_oauth_token_refresh_on_401_all_retries_failed(oauth_client, mocker, requests_mock):
    """
    Given:
    - Client configured with OAuth authentication.
    - HTTP request that returns 401 (token expired) for every API call.

    When:
    - Making an HTTP request that triggers token refresh.

    Then:
    - Ensure token is refreshed and request is retried successfully.
    """
    # Mock integration context with expired token
    mocker.patch(
        "VectraDetect.get_integration_context",
        return_value={
            "oauth_token": {"access_token": "expired_token", "expire": 0}  # Already expired
        },
    )
    invalid_response = {"detail": "Incorrect authentication credentials."}
    requests_mock.get(f"{API_URL}/test", json=invalid_response, status_code=401)

    requests_mock.post(
        f"{API_URL}/oauth2/token", json={"access_token": "refreshed_token", "token_type": "Bearer", "expires_in": 21600}
    )

    with pytest.raises(DemistoException) as err:
        oauth_client.http_request("GET", "/test")

    assert ERRORS["UNAUTHORIZED_REQUEST"].format(401, invalid_response) in str(err.value)


@pytest.mark.parametrize(
    "integration_params,expected_error",
    [
        pytest.param({}, "Missing integration setting : 'Server FQDN'"),
        pytest.param(
            {"server_fqdn": "test.vectra.com", "authentication_type": "API Token", "credentials": {"password": "  "}},
            "Missing integration setting : 'Credentials password' or 'API token'",
        ),
        pytest.param(
            {"server_fqdn": "test.vectra.com", "authentication_type": "OAuth 2.0", "oauth_credentials": None},
            "Missing integration setting : 'OAuth 2.0 Credentials' are required for OAuth 2.0 authentication",
        ),
        pytest.param(
            {
                "server_fqdn": "test.vectra.com",
                "authentication_type": "OAuth 2.0",
                "oauth_credentials": {"identifier": "client_id", "password": "  "},
            },
            "Missing integration setting : 'Client ID' and 'Client Secret' are required for OAuth 2.0 authentication",
        ),
        pytest.param(
            {
                "server_fqdn": "test.vectra.com",
                "authentication_type": "OAuth 2.0",
                "oauth_credentials": {"identifier": "      ", "password": "client_secret"},
            },
            "Missing integration setting : 'Client ID' and 'Client Secret' are required for OAuth 2.0 authentication",
        ),
    ],
)
def test_main_function_authentication_invalid_params(mocker, integration_params, expected_error):
    """
    Given:
    - Various integration parameter configurations.

    When:
    - Calling the main function with different authentication setups.

    Then:
    - Ensure proper authentication validation and client creation.
    """
    from VectraDetect import main

    # Mock demisto functions
    mocker.patch.object(demisto, "params", return_value=integration_params)
    with pytest.raises(DemistoException) as e:
        main()

    assert expected_error in str(e.value)


@pytest.mark.parametrize(
    "query_args,expected_outputs,expected_readable,exception",
    [pytest.param({}, [load_test_data("single_outcome_extracted.json")], None, does_not_raise(), id="full-pull")],
)
def test_vectra_search_outcomes_command(requests_mock, client, query_args, expected_outputs, expected_readable, exception):
    """
    Tests vectra_search_outcomes_command command function.
    """
    from VectraDetect import vectra_search_outcomes_command

    # Specific answers
    requests_mock.get(
        f"{API_URL}{API_ENDPOINT_OUTCOMES}?page=1&page_size=200",
        complete_qs=True,
        json={"count": 1, "results": [load_test_data("single_outcome.json")]},
    )

    with exception:
        result = vectra_search_outcomes_command(client=client, **query_args)
        assert result.outputs == expected_outputs
        if expected_outputs is None:
            assert result.readable_output == expected_readable


@pytest.mark.parametrize(
    "query_args,expected_outputs,expected_readable,exception",
    [pytest.param({}, [load_test_data("single_user_extracted.json")], None, does_not_raise(), id="full-pull")],
)
def test_vectra_search_users_command(requests_mock, client, query_args, expected_outputs, expected_readable, exception):
    """
    Tests vectra_search_users_command command function.
    """
    from VectraDetect import vectra_search_users_command

    # Specific answers
    requests_mock.get(
        f"{API_URL}{API_ENDPOINT_USERS}", complete_qs=True, json={"count": 1, "results": [load_test_data("single_user.json")]}
    )

    with exception:
        result = vectra_search_users_command(client=client, **query_args)
        assert result.outputs == expected_outputs
        if expected_outputs is None:
            assert result.readable_output == expected_readable


@pytest.mark.parametrize(
    "id,expected_outputs,expected_readable,exception",
    [
        pytest.param(None, None, None, pytest.raises(VectraException, match='"id" not specified'), id="no-id_exception"),
        pytest.param(
            "no-count",
            None,
            None,
            pytest.raises(VectraException, match="API issue - Response is empty or invalid"),
            id="api-no-count_exception",
        ),
        pytest.param(
            "no-results",
            None,
            None,
            pytest.raises(VectraException, match="API issue - Response is empty or invalid"),
            id="api-no-results_exception",
        ),
        pytest.param(
            "multiple",
            None,
            None,
            pytest.raises(VectraException, match="Multiple Accounts found"),
            id="api-multiple-results_exception",
        ),
        pytest.param("1", None, 'Cannot find Account with ID "1".', does_not_raise(), id="not-found_no-exception"),
        pytest.param(
            "36",
            load_test_data("single_account_extracted.json").get("account_extract"),
            None,
            does_not_raise(),
            id="valid-id_no-exception",
        ),
    ],
)
def test_vectra_get_account_by_id_command(requests_mock, client, id, expected_outputs, expected_readable, exception):
    """
    Tests vectra_get_account_by_id_command command function.
    """
    # Force some integration settings for testing purpose
    # It's used inside the forge_entity_url function
    # Need to import all module due to global variable
    import VectraDetect

    VectraDetect.global_UI_URL = SERVER_URL

    from VectraDetect import vectra_get_account_by_id_command

    # Default answer
    requests_mock.get(f"{API_URL}{API_SEARCH_ENDPOINT_ACCOUNTS}", json={"count": 0, "results": []})
    # Specific answers
    requests_mock.get(
        f"{API_URL}{API_SEARCH_ENDPOINT_ACCOUNTS}"
        f"?page=1&order_field=last_detection_timestamp&page_size=200"
        f"&query_string=account.id:no-count",
        complete_qs=True,
        json={"results": []},
    )
    requests_mock.get(
        f"{API_URL}{API_SEARCH_ENDPOINT_ACCOUNTS}"
        f"?page=1&order_field=last_detection_timestamp&page_size=200"
        f"&query_string=account.id:no-results",
        complete_qs=True,
        json={"count": 1},
    )
    requests_mock.get(
        f"{API_URL}{API_SEARCH_ENDPOINT_ACCOUNTS}"
        f"?page=1&order_field=last_detection_timestamp&page_size=200"
        f"&query_string=account.id:multiple",
        complete_qs=True,
        json={"count": 2, "results": []},
    )
    requests_mock.get(
        f"{API_URL}{API_SEARCH_ENDPOINT_ACCOUNTS}"
        f"?page=1&order_field=last_detection_timestamp&page_size=200"
        f"&query_string=account.id:36",
        complete_qs=True,
        json={"count": 1, "results": [load_test_data("single_account.json")]},
    )

    with exception:
        result = vectra_get_account_by_id_command(client=client, id=id)
        assert result.outputs == expected_outputs
        if expected_outputs is None:
            assert result.readable_output == expected_readable


@pytest.mark.parametrize(
    "id,expected_outputs,expected_readable,exception",
    [
        pytest.param(None, None, None, pytest.raises(VectraException, match='"id" not specified'), id="no-id_exception"),
        pytest.param(
            "no-count",
            None,
            None,
            pytest.raises(VectraException, match="API issue - Response is empty or invalid"),
            id="api-no-count_exception",
        ),
        pytest.param(
            "no-results",
            None,
            None,
            pytest.raises(VectraException, match="API issue - Response is empty or invalid"),
            id="api-no-results_exception",
        ),
        pytest.param(
            "multiple",
            None,
            None,
            pytest.raises(VectraException, match="Multiple Detections found"),
            id="api-multiple-results_exception",
        ),
        pytest.param("1", None, 'Cannot find Detection with ID "1".', does_not_raise(), id="not-found_no-exception"),
        pytest.param(
            "14",
            load_test_data("single_detection_extracted.json").get("detection_extract"),
            None,
            does_not_raise(),
            id="valid-id_no-exception",
        ),
    ],
)
def test_vectra_get_detection_by_id_command(requests_mock, client, id, expected_outputs, expected_readable, exception):
    """
    Tests vectra_get_detection_by_id_command command function.
    """
    # Force some integration settings for testing purpose
    # It's used inside the forge_entity_url function
    # Need to import all module due to global variable
    import VectraDetect

    VectraDetect.global_UI_URL = SERVER_URL

    from VectraDetect import vectra_get_detection_by_id_command

    # Default answer
    requests_mock.get(f"{API_URL}{API_SEARCH_ENDPOINT_DETECTIONS}", json={"count": 0, "results": []})
    # Specific answers
    requests_mock.get(
        f"{API_URL}{API_SEARCH_ENDPOINT_DETECTIONS}"
        f"?page=1&order_field=last_timestamp&page_size=200"
        f"&query_string=detection.id:no-count",
        complete_qs=True,
        json={"results": []},
    )
    requests_mock.get(
        f"{API_URL}{API_SEARCH_ENDPOINT_DETECTIONS}"
        f"?page=1&order_field=last_timestamp&page_size=200"
        f"&query_string=detection.id:no-results",
        complete_qs=True,
        json={"count": 1},
    )
    requests_mock.get(
        f"{API_URL}{API_SEARCH_ENDPOINT_DETECTIONS}"
        f"?page=1&order_field=last_timestamp&page_size=200"
        f"&query_string=detection.id:multiple",
        complete_qs=True,
        json={"count": 2, "results": []},
    )
    requests_mock.get(
        f"{API_URL}{API_SEARCH_ENDPOINT_DETECTIONS}?page=1&order_field=last_timestamp&page_size=200&query_string=detection.id:14",
        complete_qs=True,
        json={"count": 1, "results": [load_test_data("single_detection.json")]},
    )

    with exception:
        result = vectra_get_detection_by_id_command(client=client, id=id)
        assert result.outputs == expected_outputs
        if expected_outputs is None:
            assert result.readable_output == expected_readable


@pytest.mark.parametrize(
    "id,expected_outputs,expected_readable,exception",
    [
        pytest.param(None, None, None, pytest.raises(VectraException, match='"id" not specified'), id="no-id_exception"),
        pytest.param(
            "no-count",
            None,
            None,
            pytest.raises(VectraException, match="API issue - Response is empty or invalid"),
            id="api-no-count_exception",
        ),
        pytest.param(
            "no-results",
            None,
            None,
            pytest.raises(VectraException, match="API issue - Response is empty or invalid"),
            id="api-no-results_exception",
        ),
        pytest.param(
            "multiple",
            None,
            None,
            pytest.raises(VectraException, match="Multiple Hosts found"),
            id="api-multiple-results_exception",
        ),
        pytest.param("1", None, 'Cannot find Host with ID "1".', does_not_raise(), id="not-found_no-exception"),
        pytest.param(
            "472",
            load_test_data("single_host_extracted.json").get("host_extract"),
            None,
            does_not_raise(),
            id="valid-id_no-exception",
        ),
    ],
)
def test_vectra_get_host_by_id_command(requests_mock, client, id, expected_outputs, expected_readable, exception):
    """
    Tests vectra_get_host_by_id_command command function.
    """
    # Force some integration settings for testing purpose
    # It's used inside the forge_entity_url function
    # Need to import all module due to global variable
    import VectraDetect

    VectraDetect.global_UI_URL = SERVER_URL

    from VectraDetect import vectra_get_host_by_id_command

    # Default answer
    requests_mock.get(f"{API_URL}{API_SEARCH_ENDPOINT_HOSTS}", json={"count": 0, "results": []})
    # Specific answers
    requests_mock.get(
        f"{API_URL}{API_SEARCH_ENDPOINT_HOSTS}"
        f"?page=1&order_field=last_detection_timestamp&page_size=200"
        f"&query_string=host.id:no-count",
        complete_qs=True,
        json={"results": []},
    )
    requests_mock.get(
        f"{API_URL}{API_SEARCH_ENDPOINT_HOSTS}"
        f"?page=1&order_field=last_detection_timestamp&page_size=200"
        f"&query_string=host.id:no-results",
        complete_qs=True,
        json={"count": 1},
    )
    requests_mock.get(
        f"{API_URL}{API_SEARCH_ENDPOINT_HOSTS}"
        f"?page=1&order_field=last_detection_timestamp&page_size=200"
        f"&query_string=host.id:multiple",
        complete_qs=True,
        json={"count": 2, "results": []},
    )
    requests_mock.get(
        f"{API_URL}{API_SEARCH_ENDPOINT_HOSTS}"
        f"?page=1&order_field=last_detection_timestamp&page_size=200"
        f"&query_string=host.id:472",
        complete_qs=True,
        json={"count": 1, "results": [load_test_data("single_host.json")]},
    )

    with exception:
        result = vectra_get_host_by_id_command(client=client, id=id)
        assert result.outputs == expected_outputs
        if expected_outputs is None:
            assert result.readable_output == expected_readable


# Test only the exceptions for now
@pytest.mark.parametrize(
    "id,expected,exception",
    [
        pytest.param(None, None, pytest.raises(VectraException, match='"id" not specified'), id="no-id_exception"),
        pytest.param("15", None, pytest.raises(DemistoException, match="Error in API call"), id="no-pcap_exception"),
    ],
)
def test_get_detection_pcap_file_command(requests_mock, client, id, expected, exception):
    """
    Tests get_detection_pcap_file_command command function.
    """
    from VectraDetect import get_detection_pcap_file_command

    requests_mock.get(f"{API_URL}{API_ENDPOINT_DETECTIONS}/10/pcap", complete_qs=True, content=b"0000")
    requests_mock.get(
        f"{API_URL}{API_ENDPOINT_DETECTIONS}/15/pcap",
        complete_qs=True,
        status_code=404,
        json={"status": 404, "reason": "File Not Found"},
    )

    with exception:
        assert get_detection_pcap_file_command(client=client, id=id) == expected


@pytest.mark.parametrize(
    "id,fixed,expected_outputs,expected_readable,exception",
    [
        pytest.param(None, None, None, None, pytest.raises(VectraException, match='"id" not specified'), id="no-id_exception"),
        pytest.param(
            "12", None, None, None, pytest.raises(VectraException, match='"fixed" not specified'), id="no-fixed_exception"
        ),
        pytest.param(
            "12", "vectra", None, None, pytest.raises(VectraException, match='"fixed" not specified'), id="no-fixed_exception"
        ),
        pytest.param(
            "12", "true", None, 'Detection "12" successfully marked as fixed.', does_not_raise(), id="fixed_no-exception"
        ),
        pytest.param(
            "12", "no", None, 'Detection "12" successfully unmarked as fixed.', does_not_raise(), id="unfixed_no-exception"
        ),
    ],
)
def test_mark_detection_as_fixed_command(requests_mock, client, id, fixed, expected_outputs, expected_readable, exception):
    """
    Tests mark_detection_as_fixed_command command function.
    """
    from VectraDetect import mark_detection_as_fixed_command

    requests_mock.patch(
        f"{API_URL}{API_ENDPOINT_DETECTIONS}",
        complete_qs=True,
        json={"_meta": {"level": "Success", "message": "Successfully marked detections"}},
    )

    with exception:
        result = mark_detection_as_fixed_command(client=client, id=id, fixed=fixed)
        assert result.outputs == expected_outputs
        if expected_outputs is None:
            assert result.readable_output == expected_readable


@pytest.mark.parametrize(
    "id,expected_outputs,expected_readable,exception",
    [
        pytest.param(None, None, None, pytest.raises(VectraException, match='"id" not specified'), id="none-id_exception"),
        pytest.param(
            "25", load_test_data("single_assignment_extracted.json"), None, does_not_raise(), id="valid-id_no-exception"
        ),
    ],
)
def test_vectra_get_assignment_by_id_command(requests_mock, client, id, expected_outputs, expected_readable, exception):
    """
    Tests vectra_get_assignment_by_id_command command function.
    """
    from VectraDetect import vectra_get_assignment_by_id_command

    # Default answer
    requests_mock.get(f"{API_URL}{API_ENDPOINT_ASSIGNMENTS}", json={})
    # Specific answers
    requests_mock.get(
        f"{API_URL}{API_ENDPOINT_ASSIGNMENTS}/25",
        complete_qs=True,
        json={"assignment": load_test_data("single_assignment.json")},
    )

    with exception:
        result = vectra_get_assignment_by_id_command(client=client, id=id)
        assert result.outputs == expected_outputs
        if expected_outputs is None:
            assert result.readable_output == expected_readable


# Test only the exceptions for now
@pytest.mark.parametrize(
    "assignee_id,account_id,host_id,assignment_id,expected_outputs,expected_readable,exception",
    [
        pytest.param(
            None,
            None,
            None,
            None,
            None,
            None,
            pytest.raises(VectraException, match='"assignee_id" not specified'),
            id="none-assignee-id_exception",
        ),
        pytest.param(
            "1",
            None,
            None,
            None,
            None,
            None,
            pytest.raises(VectraException, match='You must specify one of "assignment_id", "account_id" or "host_id"'),
            id="none-entity-ids_exception",
        ),
        pytest.param(
            "1",
            "2",
            "3",
            None,
            None,
            None,
            pytest.raises(VectraException, match='You must specify one of "assignment_id", "account_id" or "host_id"'),
            id="account-and-host-ids_exception",
        ),
        pytest.param(
            "1",
            "2",
            None,
            "4",
            None,
            None,
            pytest.raises(VectraException, match='You must specify one of "assignment_id", "account_id" or "host_id"'),
            id="account-and-assignment-ids_exception",
        ),
        pytest.param(
            "1",
            None,
            "3",
            "4",
            None,
            None,
            pytest.raises(VectraException, match='You must specify one of "assignment_id", "account_id" or "host_id"'),
            id="host-and-assignment-ids_exception",
        ),
        pytest.param(
            "1",
            "2",
            "3",
            "4",
            None,
            None,
            pytest.raises(VectraException, match='You must specify one of "assignment_id", "account_id" or "host_id"'),
            id="all-ids_exception",
        ),
        pytest.param(
            "text-id",
            None,
            None,
            "4",
            None,
            None,
            pytest.raises(ValueError, match='"assignee_id" value is invalid'),
            id="text-assignee-id_exception",
        ),
        pytest.param(
            "1",
            "text-id",
            None,
            None,
            None,
            None,
            pytest.raises(ValueError, match='"account_id" value is invalid'),
            id="text-account-id_exception",
        ),
        pytest.param(
            "1",
            None,
            "text-id",
            None,
            None,
            None,
            pytest.raises(ValueError, match='"host_id" value is invalid'),
            id="text-host-id_exception",
        ),
        pytest.param(
            "1",
            None,
            None,
            "text-id",
            None,
            None,
            pytest.raises(ValueError, match='"assignment_id" value is invalid'),
            id="text-assignment-id_exception",
        ),
        pytest.param(
            "1", None, None, "25", load_test_data("single_assignment_extracted.json"), None, does_not_raise(), id="assignment_ok"
        ),
    ],
)
def test_vectra_assignment_assign_command(
    requests_mock, client, assignee_id, account_id, host_id, assignment_id, expected_outputs, expected_readable, exception
):
    """
    Tests vectra_assignment_assign_command command function.
    """
    from VectraDetect import vectra_assignment_assign_command

    # Test answer, useless to check XSOAR inner exceptions (none API call raised)
    # Need to create inner checks based on post query body to have a better coverage
    requests_mock.put(
        f"{API_URL}{API_ENDPOINT_ASSIGNMENTS}/25",
        complete_qs=True,
        json={"assignment": load_test_data("single_assignment.json")},
    )

    with exception:
        result = vectra_assignment_assign_command(
            client=client, assignee_id=assignee_id, account_id=account_id, host_id=host_id, assignment_id=assignment_id
        )
        assert result.outputs == expected_outputs
        if expected_outputs is None:
            assert result.readable_output == expected_readable


# Test only the exceptions for now
@pytest.mark.parametrize(
    "assignment_id,outcome_id,note,detections_filter,filter_rule_name,detections_list,"
    "expected_outputs,expected_readable,exception",
    [
        pytest.param(
            None,
            None,
            None,
            None,
            None,
            None,
            None,
            None,
            pytest.raises(VectraException, match='"assignment_id" not specified'),
            id="none-assignment-id_exception",
        ),
        pytest.param(
            "1",
            None,
            None,
            None,
            None,
            None,
            None,
            None,
            pytest.raises(VectraException, match='"outcome_id" not specified'),
            id="none-outcome-id_exception",
        ),
        pytest.param(
            "1",
            "2",
            None,
            "Filter Rule",
            None,
            None,
            None,
            None,
            pytest.raises(VectraException, match='"filter_rule_name" not specified'),
            id="none-filter-rule-name_exception",
        ),
        pytest.param(
            "1",
            "2",
            None,
            "Filter Rule",
            "Dummy Name",
            None,
            None,
            None,
            pytest.raises(VectraException, match='"detections_list" not specified'),
            id="none-detections-list_exception",
        ),
        pytest.param(
            "text-id",
            "2",
            None,
            None,
            None,
            None,
            None,
            None,
            pytest.raises(ValueError, match='"assignment_id" value is invalid'),
            id="text-assignment-id_exception",
        ),
        pytest.param(
            "1",
            "text-id",
            None,
            None,
            None,
            None,
            None,
            None,
            pytest.raises(ValueError, match='"outcome_id" value is invalid'),
            id="text-outcome-id_exception",
        ),
        pytest.param(
            "1",
            "2",
            None,
            "Filter Rule",
            "Dummy Name",
            ",",
            None,
            None,
            pytest.raises(ValueError, match='"detections_list" value is invalid'),
            id="wrong-detections-list_exception",
        ),
        pytest.param(
            "25",
            "4",
            None,
            "Filter Rule",
            "Test-Triage",
            "2201, 2202, 2203",
            load_test_data("single_assignment_extracted.json"),
            None,
            does_not_raise(),
            id="assignment-resolution_ok",
        ),
    ],
)
def test_vectra_assignment_resolve_command(
    requests_mock,
    client,
    assignment_id,
    outcome_id,
    note,
    detections_filter,
    filter_rule_name,
    detections_list,
    expected_outputs,
    expected_readable,
    exception,
):
    """
    Tests vectra_assignment_resolve_command command function.
    """
    from VectraDetect import vectra_assignment_resolve_command

    # Default answer, useless to check XSOAR inner exceptions (none API call raised)
    # Need to create inner checks based on post query body to have a better coverage
    requests_mock.put(
        f"{API_URL}{API_ENDPOINT_ASSIGNMENTS}/25/resolve",
        complete_qs=True,
        json={"assignment": load_test_data("single_assignment.json")},
    )

    with exception:
        result = vectra_assignment_resolve_command(
            client=client,
            assignment_id=assignment_id,
            outcome_id=outcome_id,
            note=note,
            detections_filter=detections_filter,
            filter_rule_name=filter_rule_name,
            detections_list=detections_list,
        )
        assert result.outputs == expected_outputs
        if expected_outputs is None:
            assert result.readable_output == expected_readable


@pytest.mark.parametrize(
    "id,expected_outputs,expected_readable,exception",
    [
        pytest.param(None, None, None, pytest.raises(VectraException, match='"id" not specified'), id="no-id_exception"),
        pytest.param("4", load_test_data("single_outcome_extracted.json"), None, does_not_raise(), id="valid-id_no-exception"),
    ],
)
def test_vectra_get_outcome_by_id_command(requests_mock, client, id, expected_outputs, expected_readable, exception):
    """
    Tests vectra_get_outcome_by_id_command command function.
    """
    from VectraDetect import vectra_get_outcome_by_id_command

    # Default answer
    requests_mock.get(f"{API_URL}{API_ENDPOINT_OUTCOMES}", json={})
    # Specific answers
    requests_mock.get(
        f"{API_URL}{API_ENDPOINT_OUTCOMES}/4?page=1&page_size=200",
        complete_qs=True,
        json=load_test_data("single_outcome.json"),
    )

    with exception:
        result = vectra_get_outcome_by_id_command(client=client, id=id)
        assert result.outputs == expected_outputs
        if expected_outputs is None:
            assert result.readable_output == expected_readable


@pytest.mark.parametrize(
    "category,title,expected_outputs,expected_readable,exception",
    [
        pytest.param(
            None,
            "Dummy-Title",
            None,
            None,
            pytest.raises(VectraException, match='"category" not specified'),
            id="none-category_exception",
        ),
        pytest.param(
            "",
            "Dummy-Title",
            None,
            None,
            pytest.raises(VectraException, match='"category" not specified'),
            id="empty-category_exception",
        ),
        pytest.param(
            "False Positive",
            None,
            None,
            None,
            pytest.raises(VectraException, match='"title" not specified'),
            id="none-title_exception",
        ),
        pytest.param(
            "Wrong Category",
            "Dummy-Title",
            None,
            None,
            pytest.raises(ValueError, match='"category" value is invalid'),
            id="wrong-category_exception",
        ),
        pytest.param(
            "False Positive",
            "",
            None,
            None,
            pytest.raises(VectraException, match='"title" not specified'),
            id="empty-title_exception",
        ),
        pytest.param(
            "Benign True Positive",
            "Vectra Outcome Test True Positive",
            load_test_data("single_outcome_extracted.json"),
            None,
            does_not_raise(),
            id="valid_no-exception",
        ),
    ],
)
def test_vectra_outcome_create_command(requests_mock, client, category, title, expected_outputs, expected_readable, exception):
    """
    Tests vectra_outcome_create_command command function.
    """
    from VectraDetect import vectra_outcome_create_command

    # Test post
    requests_mock.post(f"{API_URL}{API_ENDPOINT_OUTCOMES}", json=load_test_data("single_outcome.json"))

    with exception:
        result = vectra_outcome_create_command(client=client, category=category, title=title)
        assert result.outputs == expected_outputs
        if expected_outputs is None:
            assert result.readable_output == expected_readable


@pytest.mark.parametrize(
    "id,expected_outputs,expected_readable,exception",
    [
        pytest.param(None, None, None, pytest.raises(VectraException, match='"id" not specified'), id="no-id_exception"),
        pytest.param("123", load_test_data("single_user_extracted.json"), None, does_not_raise(), id="valid-id_no-exception"),
    ],
)
def test_vectra_get_user_by_id_command(requests_mock, client, id, expected_outputs, expected_readable, exception):
    """
    Tests vectra_get_user_by_id_command command function.
    """
    from VectraDetect import vectra_get_user_by_id_command

    # Default answer
    requests_mock.get(f"{API_URL}{API_ENDPOINT_USERS}", json={})
    # Specific answers
    requests_mock.get(f"{API_URL}{API_ENDPOINT_USERS}/123", complete_qs=True, json=load_test_data("single_user.json"))

    with exception:
        result = vectra_get_user_by_id_command(client=client, id=id)
        assert result.outputs == expected_outputs
        if expected_outputs is None:
            assert result.readable_output == expected_readable


@pytest.mark.parametrize(
    "type,id,tags,expected_outputs,expected_readable,exception",
    [
        pytest.param(
            None, None, None, None, None, pytest.raises(VectraException, match='"type" not specified'), id="no-type_exception"
        ),
        pytest.param(
            "accounts", None, None, None, None, pytest.raises(VectraException, match='"id" not specified'), id="no-id_exception"
        ),
        pytest.param(
            "accounts",
            "12",
            None,
            None,
            None,
            pytest.raises(VectraException, match='"tags" not specified'),
            id="no-tags_exception",
        ),
        pytest.param(
            "accounts",
            "12",
            "vectra",
            None,
            'Tags "vectra" successfully added.',
            does_not_raise(),
            id="del-account-tag_no-exception",
        ),
        pytest.param(
            "accounts",
            "12",
            "vectra-1,Vectra-2",
            None,
            'Tags "vectra-1,Vectra-2" successfully added.',
            does_not_raise(),
            id="del-account-tags_no-exception",
        ),
    ],
)
def test_add_tags_command(requests_mock, client, type, id, tags, expected_outputs, expected_readable, exception):
    """
    Tests add_tags_command command function.
    """
    from VectraDetect import add_tags_command

    requests_mock.get(f"{API_URL}{API_TAGGING}/{type}/{id}", complete_qs=True, json={"tags": ["vectra"]})
    requests_mock.patch(f"{API_URL}{API_TAGGING}/{type}/{id}", complete_qs=True, json={"tags": ["vectra"]})

    with exception:
        result = add_tags_command(client=client, type=type, id=id, tags=tags)
        assert result.outputs == expected_outputs
        if expected_outputs is None:
            assert result.readable_output == expected_readable


@pytest.mark.parametrize(
    "type,id,tags,expected_outputs,expected_readable,exception",
    [
        pytest.param(
            None, None, None, None, None, pytest.raises(VectraException, match='"type" not specified'), id="no-type_exception"
        ),
        pytest.param(
            "accounts", None, None, None, None, pytest.raises(VectraException, match='"id" not specified'), id="no-id_exception"
        ),
        pytest.param(
            "accounts",
            "12",
            None,
            None,
            None,
            pytest.raises(VectraException, match='"tags" not specified'),
            id="no-tags_exception",
        ),
        pytest.param(
            "accounts",
            "12",
            "vectra",
            None,
            'Tags "vectra" successfully deleted.',
            does_not_raise(),
            id="del-account-tag_no-exception",
        ),
        pytest.param(
            "accounts",
            "12",
            "vectra-1,Vectra-2",
            None,
            'Tags "vectra-1,Vectra-2" successfully deleted.',
            does_not_raise(),
            id="del-account-tags_no-exception",
        ),
    ],
)
def test_del_tags_command(requests_mock, client, type, id, tags, expected_outputs, expected_readable, exception):
    """
    Tests del_tags_command command function.
    """
    from VectraDetect import del_tags_command

    requests_mock.get(f"{API_URL}{API_TAGGING}/{type}/{id}", complete_qs=True, json={"tags": ["vectra"]})
    requests_mock.patch(f"{API_URL}{API_TAGGING}/{type}/{id}", complete_qs=True, json={"tags": ["vectra"]})

    with exception:
        result = del_tags_command(client=client, type=type, id=id, tags=tags)
        assert result.outputs == expected_outputs
        if expected_outputs is None:
            assert result.readable_output == expected_readable


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

    When:
    - Mocking the 'search_hosts' function to return a list of hosts.
    - Mocking the 'search_accounts' function to return an empty list of accounts.
    - Mocking the 'demisto.args' function to return a specific argument.

    Then:
    - Calling the 'get_modified_remote_data_command' function with the provided client and arguments.
    """
    mocker.patch.object(demisto, "args", return_value={"lastUpdate": "2023-09-20T10:00:00+00:00"})
    mocker.patch.object(Client, "search_accounts", return_value={"results": [], "next": None})

    response = load_test_data("search_hosts_response.json")
    requests_mock.get(f"{API_URL}{API_SEARCH_ENDPOINT_HOSTS}", json=response, status_code=200)

    get_modified_remote_return = {
        "Contents": get_modified_remote_data_command(client=client).to_entry().get("Contents"),
        "ContentsFormat": "json",
        "Type": 1,
    }

    assert get_modified_remote_data_command(client=client).to_entry() == get_modified_remote_return


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

    When:
    - Mocking the 'dateparser.parse' function to return a specific datetime.
    - Mocking the 'get_last_mirror_run' function to return a specific last mirror run timestamp.
    - Mocking the 'list_entities_request' function to return a large number of entities (more than the mirroring limit).
    - Mocking the 'set_last_mirror_run' function.

    Then:
    - Calling the 'get_modified_remote_data_command' function with the provided client and arguments.
    """
    mocker.patch.object(demisto, "args", return_value={"lastUpdate": "2023-09-20T10:00:00+00:00"})
    mocker.patch.object(Client, "search_accounts", return_value={"results": [], "next": None})
    mocker.patch.object(
        client, "search_hosts", return_value={"results": [{"id": id, "type": "host"} for id in range(1, 2550)], "next": None}
    )

    get_modified_remote_return = {
        "Contents": get_modified_remote_data_command(client=client).to_entry().get("Contents"),
        "ContentsFormat": "json",
        "Type": 1,
    }

    assert get_modified_remote_data_command(client=client).to_entry() == get_modified_remote_return


def test_get_remote_data_command_when_detections_found(mocker, client, requests_mock):
    """
    Given:
    - A client object.
    - A mocked get entities endpoint.
    - A mocked list detection endpoint.

    When:
    - Fetching modified incident using the 'get_remote_data_command' function with the provided parameters.

    Then:
    - Assert that the reopening entry exists.
    """
    import VectraDetect

    VectraDetect.global_UI_URL = SERVER_URL

    host_data = {"id": 1, "last_modified": "2023-09-20T19:00:00+00:00", "certainty": 90, "notes": []}
    requests_mock.get(f"{API_URL}{API_ENDPOINT_HOST}/1", json=host_data, status_code=200)
    mocker.patch.object(client, "get_account_by_account_id", return_value={})

    detection_data = load_test_data("list_detection_by_host_id_response.json")
    requests_mock.get(f"{API_URL}{API_ENDPOINT_DETECTIONS}?host_id=1", json=detection_data, status_code=200)

    mocker.patch.object(client, "list_assignments_request", return_value={})

    args = {"id": "1-host", "lastUpdate": "2023-06-20T10:00:00+00:00"}
    mocker.patch.object(demisto, "args", return_value=args)

    remote_data = load_test_data("get_remote_data_entry.json")
    assert get_remote_data_command(client).extract_for_local() == remote_data


def test_get_remote_data_command_when_assignment_found(mocker, requests_mock, client):
    """
    Given:
    - A client object.
    - A mocked get entities endpoint.
    - A mocked list detection endpoint.

    When:
    - Fetching modified incident using the 'get_remote_data_command' function with the provided parameters.

    Then:
    - Assert that the reopening entry exists.
    """
    import VectraDetect

    VectraDetect.global_UI_URL = SERVER_URL

    account_data = load_test_data("get_account_by_account_id.json")
    mocker.patch.object(client, "get_account_by_account_id", return_value=account_data)

    detection_data = load_test_data("single_detection.json")
    detection_response = {"count": 1, "results": [detection_data]}
    mocker.patch.object(client, "search_detections", return_value=detection_response)

    group_res = load_test_data("group_list_response.json")
    requests_mock.get(f"{API_URL}{API_ENDPOINT_GROUPS}", json=group_res)

    args = {"id": "107-account", "lastUpdate": "2023-06-20T10:00:00+00:00"}
    mocker.patch.object(demisto, "args", return_value=args)

    remote_data = load_test_data("get_remote_data_entry_by_account.json")
    assert get_remote_data_command(client).extract_for_local() == remote_data


def test_get_remote_data_command_when_past_assignment_found(mocker, requests_mock, client):
    """
    Given:
    - A client object.
    - A mocked get entities endpoint.
    - A mocked list detection endpoint.

    When:
    - Fetching modified incident using the 'get_remote_data_command' function with the provided parameters.

    Then:
    - Assert that the reopening entry exists.
    """
    import VectraDetect

    VectraDetect.global_UI_URL = SERVER_URL

    account_data = load_test_data("get_account_by_id_with_past_assignment.json")
    mocker.patch.object(client, "get_account_by_account_id", return_value=account_data)

    detection_data = load_test_data("single_detection.json")
    detection_response = {"count": 1, "results": [detection_data]}
    mocker.patch.object(client, "search_detections", return_value=detection_response)

    group_res = load_test_data("group_list_response.json")
    requests_mock.get(f"{API_URL}{API_ENDPOINT_GROUPS}", json=group_res)

    args = {"id": "107-account", "lastUpdate": "2023-06-20T10:00:00+00:00"}
    mocker.patch.object(demisto, "args", return_value=args)

    remote_data = load_test_data("get_remote_data_entry_by_account_past_assignment.json")
    assert get_remote_data_command(client).extract_for_local() == remote_data


def test_get_remote_data_command_entity_not_needs_update(client, mocker):
    """
    Given:
    - A client object.
    - Mocked arguments specifying an entity ID and last update timestamp.

    When:
    - Mocking the 'get_entity_request' and 'list_assignments_request' functions to return empty data.

    Then:
    - Calling the 'get_remote_data_command' function with the provided client and arguments.
    """
    mocker.patch.object(client, "get_account_by_account_id", return_value={})
    mocker.patch.object(client, "get_host_by_host_id", return_value={})

    args = {"id": "1-host", "lastUpdate": "2023-09-20T10:00:00+00:00"}
    mocker.patch.object(demisto, "args", return_value=args)

    assert get_remote_data_command(client) == "Incident was not found."


def test_get_remote_data_command_entity_needs_update_notes(client, mocker, requests_mock):
    """
    Given:
    - A client object.
    - Mocked arguments specifying an entity ID and last update timestamp.

    When:
    - Mocking the 'get_entity_request' function to return entity data with a note.

    Then:
    - Calling the 'get_remote_data_command' function with the provided client and arguments.
    """
    args = {"id": "1-host", "lastUpdate": "2023-09-20T10:00:00+00:00"}
    mocker.patch.object(demisto, "args", return_value=args)

    note_response_1 = {"id": 239, "date_created": "2023-09-20T10:33:14Z", "created_by": "dummy_api_client", "note": "test note."}
    note_response_2 = copy.deepcopy(note_response_1)
    note_response_2["date_created"] = "2023-08-20T10:33:14Z"
    note_response_3 = copy.deepcopy(note_response_1)
    note_response_3["note"] = "[Mirrored From XSOAR]"
    note_response_4 = copy.deepcopy(note_response_1)
    note_response_4["date_modified"] = "2023-09-20T08:33:14Z"

    response = {
        "id": 1,
        "last_modified": "2023-09-20T19:00:00+00:00",
        "certainty": 90,
        "notes": [note_response_1, note_response_2, note_response_3, note_response_4],
    }

    mocker.patch.object(client, "get_account_by_account_id", return_value={})
    requests_mock.get(f"{API_URL}{API_ENDPOINT_HOST}/1", json=response, status_code=200)

    mocker.patch.object(client, "list_assignments_request", return_value={})

    remote_data = load_test_data("get_remote_data_note_update_entry.json")
    assert get_remote_data_command(client).extract_for_local() == remote_data


def test_update_remote_system_command_when_tags_mirror(client, mocker):
    """
    Given:
    - A client object.
    - Mocked arguments specifying a remote ID and delta of tags.

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

    Then:
    - Assert that the ID of the updated remote entity is returned.
    """
    mocker.patch.object(
        demisto,
        "args",
        return_value={"remoteId": "1-account", "delta": {"tags": ["tag1", "tag2", "tags%test"]}, "data": {"id": "1"}},
    )

    mocker.patch.object(client, "add_note_request", return_value={})
    mocker.patch.object(client, "update_entity_tags_request", return_value={})
    mocker.patch.object(client, "list_entity_tags_request", return_value={})

    assert update_remote_system_command(client) == "1-account"


def test_update_remote_system_command_when_note_mirror(client, mocker):
    """
    Given:
    - A client object.
    - Mocked arguments specifying a remote ID and delta of tags.

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

    Then:
    - Assert that the ID of the updated remote entity is returned.
    """
    mocker.patch.object(
        demisto,
        "args",
        return_value={
            "remoteId": "1-account",
            "delta": {},
            "entries": load_test_data("update_remote_system_entry.json"),
            "data": {"id": "1"},
        },
    )

    mocker.patch.object(client, "add_note_request", return_value={})
    mocker.patch.object(client, "update_entity_tags_request", return_value={})
    mocker.patch.object(client, "list_entity_tags_request", return_value={})

    assert update_remote_system_command(client) == "1-account"


def test_update_remote_system_command_when_incident_reopened(client, mocker, requests_mock):
    """
    Given:
    - A client object.
    - Mocked arguments specifying a remote ID and delta of tags.
    - Mocked requests to Vectra API.

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

    Then:
    - Assert that the ID of the updated remote entity is returned.
    """
    mocker.patch.object(
        demisto,
        "args",
        return_value={
            "remoteId": "1-account",
            "delta": {"closingUserId": "", "runStatus": "waiting"},
            "entries": [],
            "data": {"id": "1"},
        },
    )

    mocker.patch.object(client, "add_note_request", return_value={})
    mocker.patch.object(client, "update_entity_tags_request", return_value={})
    mocker.patch.object(client, "list_entity_tags_request", return_value={})
    mocker.patch.object(client, "list_assignments_request", return_value={"id": 1})
    requests_mock.delete(f"{API_URL}{API_ENDPOINT_ASSIGNMENTS}/1", json={}, status_code=200)

    assert update_remote_system_command(client) == "1-account"


def test_update_remote_system_command_when_closing_note_mirror(client, mocker):
    """
    Given:
    - A client object.
    - Mocked arguments specifying a remote ID and delta of tags.

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

    Then:
    - Assert that the ID of the updated remote entity is returned.
    """
    mocker.patch.object(
        demisto,
        "args",
        return_value={
            "remoteId": "1-account",
            "delta": {"closeNotes": "resolved", "closeReason": "Resolved", "closingUserId": "admin", "runStatus": ""},
            "data": {"closeNotes": "resolved", "closeReason": "Resolved", "closingUserId": "admin", "id": 1},
            "incidentChanged": True,
            "status": 2,
        },
    )

    mocker.patch.object(client, "add_note_request", return_value={})
    mocker.patch.object(client, "update_entity_tags_request", return_value={})
    mocker.patch.object(client, "list_entity_tags_request", return_value={})

    assert update_remote_system_command(client) == "1-account"


def test_update_remote_system_command_when_no_arguments_provided(client, mocker):
    """
    Given:
    - A client object.
    - Mocked arguments specifying a remote ID and delta of tags.

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

    Then:
    - Assert that the ID of the updated remote entity is returned.
    """
    mocker.patch.object(demisto, "args", return_value={"remoteId": "1-account"})

    mocker.patch.object(client, "add_note_request", return_value={})
    mocker.patch.object(client, "update_entity_tags_request", return_value={})
    mocker.patch.object(client, "list_entity_tags_request", return_value={})

    assert update_remote_system_command(client) == "1-account"


def test_markall_detections_asfixed_command_for_host_when_success(client, requests_mock, mocker):
    """
    Tests markall_detections_asfixed_command command function when success with host.
    """
    host_id = "472"

    host_data = load_test_data("single_host.json")
    requests_mock.get(f"{API_URL}{API_ENDPOINT_HOST}/{host_id}", json=host_data, status_code=200)

    response = {"_meta": {"level": "Success", "message": "Successfully marked detections"}}

    requests_mock.patch(f"{API_URL}{API_ENDPOINT_DETECTIONS}", json=response, status_code=200)

    result = markall_detections_asfixed_command(client=client, type="host", host_id=host_id)

    assert result.readable_output == "The active detections of the provided host have been successfully marked as fixed."
    assert result.raw_response == response


def test_markall_detections_asfixed_command_for_account_when_success(client, requests_mock, mocker):
    """
    Tests markall_detections_asfixed_command command function when success with account.
    """
    account_id = "36"

    host_data = load_test_data("single_account.json")
    requests_mock.get(f"{API_URL}{API_ENDPOINT_ACCOUNT}/{account_id}", json=host_data, status_code=200)

    response = {"_meta": {"level": "Success", "message": "Successfully marked detections"}}

    requests_mock.patch(f"{API_URL}{API_ENDPOINT_DETECTIONS}", json=response, status_code=200)

    result = markall_detections_asfixed_command(client=client, type="account", account_id=account_id)

    assert result.readable_output == "The active detections of the provided account have been successfully marked as fixed."
    assert result.raw_response == response


def test_markall_detections_asfixed_command_for_account_when_no_detection(client, requests_mock, mocker):
    """
    Tests markall_detections_asfixed_command command function when no detection in account.
    """
    account_id = "36"

    account_data = load_test_data("single_account.json")
    account_data["detection_summaries"] = []

    requests_mock.get(f"{API_URL}{API_ENDPOINT_ACCOUNT}/{account_id}", json=account_data, status_code=200)

    result = markall_detections_asfixed_command(client=client, type="account", account_id=account_id)

    assert result.readable_output == "There are no active detections present."
    assert result.raw_response == {}


def test_markall_detections_asfixed_command_for_host_when_host_id_missing(client, mocker):
    """
    Tests markall_detections_asfixed_command command function when host_id is missing.
    """
    with pytest.raises(ValueError) as err:
        markall_detections_asfixed_command(client=client, type="host", host_id="")

    assert str(err.value) == ERRORS["REQUIRED_ARGUMENT"].format("host_id")


def test_markall_detections_asfixed_command_for_host_when_host_id_wrong(client, mocker):
    """
    Tests markall_detections_asfixed_command command function when host_id is wrong.
    """
    host_id = "id"

    with pytest.raises(ValueError) as err:
        markall_detections_asfixed_command(client=client, type="host", host_id=host_id)

    assert str(err.value) == ERRORS["INVALID_INTEGER_VALUE"].format("host_id")


def test_vectra_account_tag_list_valid_arguments(client, requests_mock):
    """
    Given:
    - A mocked 'requests_mock' to simulate API responses.

    When:
    - Calling the 'tag_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 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.
    """
    from VectraDetect import tag_list_command

    args = {"id": "2"}

    notes_res = load_test_data("tag_list_response.json")
    context_data = load_test_data("account_tag_list_context.json")
    with open("test_data/tag_list_hr.md") as f:
        result_hr = f.read()

    requests_mock.get(f"{API_URL}{API_TAGGING}/account/2", json=notes_res)

    result = tag_list_command(client=client, entity_type="account", args=args)

    result_context = result.to_context()

    assert result.outputs_prefix == "Vectra.Account"
    assert result.outputs_key_field == "ID"
    assert result_context.get("Contents") == notes_res
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("EntryContext") == context_data


def test_vectra_host_tag_list_valid_arguments(client, requests_mock):
    """
    Given:
    - A mocked 'requests_mock' to simulate API responses.

    When:
    - Calling the 'tag_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 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.
    """
    from VectraDetect import tag_list_command

    args = {"id": "2"}

    notes_res = load_test_data("tag_list_response.json")
    context_data = load_test_data("host_tag_list_context.json")
    with open("test_data/tag_list_hr.md") as f:
        result_hr = f.read()

    requests_mock.get(f"{API_URL}{API_TAGGING}/host/2", json=notes_res)

    result = tag_list_command(client=client, entity_type="host", args=args)

    result_context = result.to_context()

    assert result.outputs_prefix == "Vectra.Host"
    assert result.outputs_key_field == "ID"
    assert result_context.get("Contents") == notes_res
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("EntryContext") == context_data


def test_vectra_detection_tag_list_valid_arguments(client, requests_mock):
    """
    Given:
    - A mocked 'requests_mock' to simulate API responses.

    When:
    - Calling the 'tag_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 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.
    """
    from VectraDetect import tag_list_command

    args = {"id": "2"}

    notes_res = load_test_data("tag_list_response.json")
    context_data = load_test_data("detection_tag_list_context.json")
    with open("test_data/tag_list_hr.md") as f:
        result_hr = f.read()

    requests_mock.get(f"{API_URL}{API_TAGGING}/detection/2", json=notes_res)

    result = tag_list_command(client=client, entity_type="detection", args=args)

    result_context = result.to_context()

    assert result.outputs_prefix == "Vectra.Detection"
    assert result.outputs_key_field == "ID"
    assert result_context.get("Contents") == notes_res
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("EntryContext") == context_data


@pytest.mark.parametrize(
    "args,error_msg",
    [({}, 'Missing "id"'), ({"id": " "}, 'Missing "id"'), ({"id": "-3"}, ERRORS["INVALID_INTEGER_VALUE"].format("id"))],
)
def test_vectra_tag_list_invalid_args(client, args, error_msg):
    """
    Given:
    - Arguments specifying different invalid values for id.
    When:
    - Calling the '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.
    """
    from VectraDetect import tag_list_command

    for key in args:
        if isinstance(args[key], str):
            args[key] = args[key].strip()

    with pytest.raises(ValueError) as exception:
        tag_list_command(client=client, entity_type="account", args=args)

    assert str(exception.value) == error_msg


def test_vectra_account_tag_list_when_tag_response_is_empty(client, requests_mock):
    """
    Given:
    - An empty tag list response.

    When:
    - Calling the 'note_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.
    """
    from VectraDetect import tag_list_command

    args = {"id": "5"}

    notes_res = load_test_data("tag_list_empty_response.json")
    context_data = load_test_data("tag_list_empty_context.json")

    requests_mock.get(f"{API_URL}{API_TAGGING}/account/5", json=notes_res)

    result = tag_list_command(client=client, entity_type="account", args=args)
    result_context = result.to_context()

    assert result.outputs_prefix == "Vectra.Account"
    assert result.outputs_key_field == "ID"
    assert result_context.get("Contents") == notes_res
    assert result_context.get("HumanReadable") == "##### No tags were found for the given account ID."
    assert result_context.get("EntryContext") == context_data


def test_vectra_tag_list_when_something_went_wrong(client, requests_mock):
    """
    Given:
    response with 'failure' status.
    When:
    - Calling the 'tag_list_command' function with the provided client and arguments.
    Then:
    - Assert that the function raises a VectraException.
    - Assert that the error message matches the expected error message.
    """
    from VectraDetect import tag_list_command

    args = {"id": "3"}
    notes_res = {"status": "failure", "message": "Could not find requested object"}

    requests_mock.get(f"{API_URL}{API_TAGGING}/account/3", json=notes_res)

    with pytest.raises(VectraException) as exception:
        tag_list_command(client=client, entity_type="account", args=args)

    assert str(exception.value) == "Something went wrong. Message: Could not find requested object."


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

    When:
    - Calling the '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.
    """
    from VectraDetect import note_add_command

    args = {"account_id": "2", "note": "test note"}

    notes_res = util_load_json("test_data/account_note_add_response.json")
    context_data = util_load_json("test_data/account_note_add_context.json")
    requests_mock.post(f"{API_URL}{ENDPOINTS['ADD_AND_LIST_ACCOUNT_NOTE_ENDPOINT'].format(2)}", json=notes_res)
    with open("test_data/account_note_add_hr.md") as f:
        result_hr = f.read()

    result = note_add_command(client=client, entity_type="account", args=args)
    result_context = result.to_context()

    assert result.outputs_prefix == OUTPUT_PREFIXES["ACCOUNT_NOTES"]
    assert result.outputs_key_field == NOTE_OUTPUT_KEY_FIELD
    assert result_context.get("Contents") == notes_res
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("EntryContext") == context_data


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

    When:
    - Calling the '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.
    """

    from VectraDetect import note_add_command

    args = {"host_id": "7", "note": "test note"}

    notes_res = util_load_json("test_data/host_note_add_response.json")
    context_data = util_load_json("test_data/host_note_add_context.json")
    requests_mock.post(f"{API_URL}{ENDPOINTS['ADD_AND_LIST_HOST_NOTE_ENDPOINT'].format(7)}", json=notes_res)
    with open("test_data/host_note_add_hr.md") as f:
        result_hr = f.read()

    result = note_add_command(client=client, entity_type="host", args=args)
    result_context = result.to_context()

    assert result.outputs_prefix == OUTPUT_PREFIXES["HOST_NOTES"]
    assert result.outputs_key_field == NOTE_OUTPUT_KEY_FIELD
    assert result_context.get("Contents") == notes_res
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("EntryContext") == context_data


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

    When:
    - Calling the '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.
    """
    from VectraDetect import note_add_command

    args = {"detection_id": "9", "note": "test note"}

    notes_res = util_load_json("test_data/detection_note_add_response.json")
    context_data = util_load_json("test_data/detection_note_add_context.json")
    requests_mock.post(f"{API_URL}{ENDPOINTS['ADD_AND_LIST_DETECTION_NOTE_ENDPOINT'].format(9)}", json=notes_res)
    with open("test_data/detection_note_add_hr.md") as f:
        result_hr = f.read()

    result = note_add_command(client=client, entity_type="detection", args=args)
    result_context = result.to_context()

    assert result.outputs_prefix == OUTPUT_PREFIXES["DETECTION_NOTES"]
    assert result.outputs_key_field == NOTE_OUTPUT_KEY_FIELD
    assert result_context.get("Contents") == notes_res
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("EntryContext") == context_data


@pytest.mark.parametrize(
    "args,error_msg",
    [
        ({"account_id": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("note")),
        ({"account_id": "1", "note": " "}, ERRORS["REQUIRED_ARGUMENT"].format("note")),
        ({"note": "test note"}, 'Missing "account_id"'),
        ({"account_id": " ", "note": "test note"}, 'Missing "account_id"'),
        ({"account_id": "-3", "note": "test note"}, ERRORS["INVALID_INTEGER_VALUE"].format("account_id")),
    ],
)
def test_note_add_command_invalid_args(client, args, error_msg):
    """
    Given:
    - Arguments specifying different invalid values for id and note.

    When:
    - Calling the '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.
    """
    from VectraDetect import note_add_command

    for key in args:
        if isinstance(args[key], str):
            args[key] = args[key].strip()

    with pytest.raises(ValueError) as exception:
        note_add_command(client=client, entity_type="account", args=args)

    assert str(exception.value) == error_msg


def test_account_note_update_command_valid_arguments(client, requests_mock):
    """
    Given:
    - A mock notes response.
    - The expected human-readable output file.
    - Arguments specifying valid parameters to update a note to an account.

    When:
    - Calling the '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.
    """
    from VectraDetect import note_update_command

    args = {"account_id": "2", "note_id": "1959", "note": "updated test note"}

    notes_res = util_load_json("test_data/account_note_update_response.json")
    context_data = util_load_json("test_data/account_note_update_context.json")
    requests_mock.patch(f"{API_URL}{ENDPOINTS['UPDATE_AND_REMOVE_ACCOUNT_NOTE_ENDPOINT'].format(2, 1959)}", json=notes_res)
    with open("test_data/account_note_update_hr.md") as f:
        result_hr = f.read()

    result = note_update_command(client=client, entity_type="account", args=args)
    result_context = result.to_context()

    assert result.outputs_prefix == OUTPUT_PREFIXES["ACCOUNT_NOTES"]
    assert result.outputs_key_field == NOTE_OUTPUT_KEY_FIELD
    assert result_context.get("Contents") == notes_res  # Replace with the expected output
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("EntryContext") == context_data  # Replace with the expected raw response


def test_host_note_update_command_valid_arguments(client, requests_mock):
    """
    Given:
    - A mock notes response.
    - The expected human-readable output file.
    - Arguments specifying valid parameters to update a note to a host.

    When:
    - Calling the '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.
    """
    from VectraDetect import note_update_command

    args = {"host_id": "7", "note_id": "1960", "note": "updated test note"}

    entity_type = "host"

    notes_res = util_load_json("test_data/host_note_update_response.json")
    context_data = util_load_json("test_data/host_note_update_context.json")
    requests_mock.patch(f"{API_URL}{ENDPOINTS['UPDATE_AND_REMOVE_HOST_NOTE_ENDPOINT'].format(7, 1960)}", json=notes_res)
    with open("test_data/host_note_update_hr.md") as f:
        result_hr = f.read()

    result = note_update_command(client=client, entity_type=entity_type, args=args)
    result_context = result.to_context()

    assert result.outputs_prefix == OUTPUT_PREFIXES["HOST_NOTES"]
    assert result.outputs_key_field == NOTE_OUTPUT_KEY_FIELD
    assert result_context.get("Contents") == notes_res  # Replace with the expected output
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("EntryContext") == context_data  # Replace with the expected raw response


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

    When:
    - Calling the '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.
    """
    from VectraDetect import note_update_command

    args = {"detection_id": "9", "note_id": "1961", "note": "updated test note"}

    entity_type = "detection"

    notes_res = util_load_json("test_data/detection_note_update_response.json")
    context_data = util_load_json("test_data/detection_note_update_context.json")
    requests_mock.patch(f"{API_URL}{ENDPOINTS['UPDATE_AND_REMOVE_DETECTION_NOTE_ENDPOINT'].format(9, 1961)}", json=notes_res)
    with open("test_data/detection_note_update_hr.md") as f:
        result_hr = f.read()

    result = note_update_command(client=client, entity_type=entity_type, args=args)
    result_context = result.to_context()

    assert result.outputs_prefix == OUTPUT_PREFIXES["DETECTION_NOTES"]
    assert result.outputs_key_field == NOTE_OUTPUT_KEY_FIELD
    assert result_context.get("Contents") == notes_res  # Replace with the expected output
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("EntryContext") == context_data  # Replace with the expected raw response


@pytest.mark.parametrize(
    "args,error_msg",
    [
        ({"account_id": "1", "note_id": "5"}, ERRORS["REQUIRED_ARGUMENT"].format("note")),
        ({"account_id": "1", "note_id": "5", "note": " "}, ERRORS["REQUIRED_ARGUMENT"].format("note")),
        ({"note_id": "5", "note": "test note"}, 'Missing "account_id"'),
        ({"account_id": " ", "note_id": "5", "note": "test note"}, 'Missing "account_id"'),
        ({"account_id": "-3", "note_id": "5", "note": "test note"}, ERRORS["INVALID_INTEGER_VALUE"].format("account_id")),
        ({"account_id": "1", "note": "test note"}, 'Missing "note_id"'),
        ({"account_id": "1", "note_id": " ", "note": "test note"}, 'Missing "note_id"'),
        ({"account_id": "1", "note_id": "-3", "note": "test note"}, ERRORS["INVALID_INTEGER_VALUE"].format("note_id")),
    ],
)
def test_note_update_command_invalid_args(client, args, error_msg):
    """
    Given:
    - Arguments specifying different invalid arguments.

    When:
    - Calling the 'note_update_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.
    """

    from VectraDetect import note_update_command

    for key in args:
        if isinstance(args[key], str):
            args[key] = args[key].strip()

    with pytest.raises(ValueError) as exception:
        note_update_command(client, entity_type="account", args=args)

    assert str(exception.value) == error_msg


def test_vectra_account_note_remove_valid_arguments(client, requests_mock):
    """
    Given:
    - Arguments specifying valid parameters to remove a note from an account.

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

    Then:
    - Assert that the human-readable output matches the content of the expected file.
    - Assert that the 'EntryContext' property in the context matches the context data.
    """
    from VectraDetect import note_remove_command

    args = {"account_id": "2", "note_id": "1959"}

    requests_mock.delete(f"{API_URL}{ENDPOINTS['UPDATE_AND_REMOVE_ACCOUNT_NOTE_ENDPOINT'].format(2, 1959)}", status_code=204)

    # Call the function
    result = note_remove_command(client, entity_type="account", args=args)

    # Assert the result
    result_context = result.to_context()
    assert result_context.get("HumanReadable") == "##### The note has been successfully removed from the account."
    assert result_context.get("Contents") == ""


def test_vectra_host_note_remove_valid_arguments(client, requests_mock):
    """
    Given:
    - Arguments specifying valid parameters to remove a note from a host.

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

    Then:
    - Assert that the human-readable output matches the content of the expected file.
    - Assert that the 'EntryContext' property in the context matches the context data.
    """
    from VectraDetect import note_remove_command

    args = {"host_id": "7", "note_id": "1960"}

    requests_mock.delete(f"{API_URL}{ENDPOINTS['UPDATE_AND_REMOVE_HOST_NOTE_ENDPOINT'].format(7, 1960)}", status_code=204)

    # Call the function
    result = note_remove_command(client, entity_type="host", args=args)

    # Assert the result
    result_context = result.to_context()
    assert result_context.get("HumanReadable") == "##### The note has been successfully removed from the host."
    assert result_context.get("Contents") == ""


def test_vectra_detection_note_remove_valid_arguments(client, requests_mock):
    """
    Given:
    - Arguments specifying valid parameters to remove a note from a detection.

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

    Then:
    - Assert that the human-readable output matches the content of the expected file.
    - Assert that the 'EntryContext' property in the context matches the context data.
    """
    from VectraDetect import note_remove_command

    args = {"detection_id": "9", "note_id": "1961"}

    requests_mock.delete(f"{API_URL}{ENDPOINTS['UPDATE_AND_REMOVE_DETECTION_NOTE_ENDPOINT'].format(9, 1961)}", status_code=204)
    result = note_remove_command(client, entity_type="detection", args=args)

    # Assert the result
    result_context = result.to_context()
    assert result_context.get("HumanReadable") == "##### The note has been successfully removed from the detection."
    assert result_context.get("Contents") == ""


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

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

    Args:
        requests_mock: The requests mock object.

    Returns:
        Human Readable and Context Output.
    """
    from VectraDetect import note_remove_command

    args = {"host_id": "7", "note_id": "1980"}

    requests_mock.delete(
        f"{API_URL}{ENDPOINTS['UPDATE_AND_REMOVE_HOST_NOTE_ENDPOINT'].format(7, 1980)}", status_code=200, text="test fail"
    )

    # Call the function
    result = note_remove_command(client, entity_type="host", args=args)
    result_context = result.to_context()
    # Assert the CommandResults
    assert result_context.get("HumanReadable") == "Something went wrong. API Response: test fail"
    assert result_context.get("Contents") == "test fail"


@pytest.mark.parametrize(
    "args,error_msg",
    [
        ({"note_id": "5"}, 'Missing "account_id"'),
        ({"account_id": " ", "note_id": "5"}, 'Missing "account_id"'),
        ({"account_id": "-3", "note_id": "5"}, ERRORS["INVALID_INTEGER_VALUE"].format("account_id")),
        ({"account_id": "1"}, 'Missing "note_id"'),
        ({"account_id": "1", "note_id": " "}, 'Missing "note_id"'),
        ({"account_id": "1", "note_id": "-3"}, ERRORS["INVALID_INTEGER_VALUE"].format("note_id")),
    ],
)
def test_note_remove_command_invalid_args(client, args, error_msg):
    """
    Given:
    - Arguments specifying different invalid values.

    When:
    - Calling the 'note_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.
    """
    from VectraDetect import note_remove_command

    for key in args:
        if isinstance(args[key], str):
            args[key] = args[key].strip()

    with pytest.raises(ValueError) as exception:
        note_remove_command(client, entity_type="account", args=args)

    assert str(exception.value) == error_msg


def test_vectra_account_note_list_valid_arguments(client, requests_mock):
    """
    Given:
    - A mocked 'requests_mock' to simulate API responses.

    When:
    - Calling the '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 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.
    """
    from VectraDetect import note_list_command

    args = {"account_id": "2"}

    notes_res = load_test_data("account_note_list_response.json")
    context_data = load_test_data("account_note_list_context.json")
    with open("test_data/account_note_list_hr.md") as f:
        result_hr = f.read()

    requests_mock.get(f"{API_URL}{ENDPOINTS['ADD_AND_LIST_ACCOUNT_NOTE_ENDPOINT'].format(2)}", json=notes_res)

    result = note_list_command(client=client, entity_type="account", args=args)

    result_context = result.to_context()

    assert result.outputs_prefix == OUTPUT_PREFIXES["ACCOUNT_NOTES"]
    assert result.outputs_key_field == NOTE_OUTPUT_KEY_FIELD
    assert result_context.get("Contents") == notes_res
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("EntryContext") == context_data


def test_vectra_host_note_list_valid_arguments(client, requests_mock):
    """
    Given:
    - A mocked 'requests_mock' to simulate API responses.

    When:
    - Calling the '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 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.
    """
    from VectraDetect import note_list_command

    args = {"host_id": "7"}

    notes_res = load_test_data("host_note_list_response.json")
    context_data = load_test_data("host_note_list_context.json")
    with open("test_data/host_note_list_hr.md") as f:
        result_hr = f.read()

    requests_mock.get(f"{API_URL}{ENDPOINTS['ADD_AND_LIST_HOST_NOTE_ENDPOINT'].format(7)}", json=notes_res)

    result = note_list_command(client=client, entity_type="host", args=args)
    result_context = result.to_context()

    assert result.outputs_prefix == OUTPUT_PREFIXES["HOST_NOTES"]
    assert result.outputs_key_field == NOTE_OUTPUT_KEY_FIELD
    assert result_context.get("Contents") == notes_res
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("EntryContext") == context_data


def test_vectra_detection_note_list_valid_arguments(client, requests_mock):
    """
    Given:
    - A mocked 'requests_mock' to simulate API responses.

    When:
    - Calling the '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 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.
    """
    from VectraDetect import note_list_command

    args = {"detection_id": "9"}

    notes_res = load_test_data("detection_note_list_response.json")
    context_data = load_test_data("detection_note_list_context.json")
    with open("test_data/detection_note_list_hr.md") as f:
        result_hr = f.read()

    requests_mock.get(f"{API_URL}{ENDPOINTS['ADD_AND_LIST_DETECTION_NOTE_ENDPOINT'].format(9)}", json=notes_res)

    result = note_list_command(client=client, entity_type="detection", args=args)
    result_context = result.to_context()

    assert result.outputs_prefix == OUTPUT_PREFIXES["DETECTION_NOTES"]
    assert result.outputs_key_field == NOTE_OUTPUT_KEY_FIELD
    assert result_context.get("Contents") == notes_res
    assert result_context.get("HumanReadable") == result_hr
    assert result_context.get("EntryContext") == context_data


@pytest.mark.parametrize(
    "args,error_msg",
    [
        ({}, 'Missing "account_id"'),
        ({"account_id": " "}, 'Missing "account_id"'),
        ({"account_id": "-3"}, ERRORS["INVALID_INTEGER_VALUE"].format("account_id")),
    ],
)
def test_vectra_note_list_invalid_args(client, args, error_msg):
    """
    Given:
    - Arguments specifying different invalid values for account_id.
    When:
    - Calling the '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.
    """
    from VectraDetect import note_list_command

    for key in args:
        if isinstance(args[key], str):
            args[key] = args[key].strip()

    with pytest.raises(ValueError) as exception:
        note_list_command(client=client, entity_type="account", args=args)

    assert str(exception.value) == error_msg


def test_vectra_account_note_list_when_note_response_is_empty(client, requests_mock):
    """
    Given:
    - An empty notes list response.

    When:
    - Calling the 'note_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.
    """
    from VectraDetect import note_list_command

    args = {"account_id": "5"}

    requests_mock.get(f"{API_URL}{ENDPOINTS['ADD_AND_LIST_ACCOUNT_NOTE_ENDPOINT'].format(5)}", json=[])

    result = note_list_command(client=client, entity_type="account", args=args)
    result_context = result.to_context()

    assert result_context.get("Contents") == []
    assert result_context.get("HumanReadable") == "Couldn't find any notes for provided account."
    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 = load_test_data("group_list_response.json")
    context_data = load_test_data("group_list_context.json")
    with open("./test_data/group_list_hr.md") as f:
        result_hr = f.read()
    requests_mock.get(f"{API_URL}{API_ENDPOINT_GROUPS}", 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")),
        (
            {"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 = load_test_data("assign_group_response.json")
    groups = load_test_data("get_groups_response.json")
    context_data = load_test_data("assign_group_context.json")
    # For Domain group
    with open("test_data/assign_domain_group_hr.md") as f:
        result_hr = f.read()
    args = {"group_id": "1", "members": "*.domain3.com,*.domain2.com"}
    requests_mock.get(API_URL + "{}/{}".format(API_ENDPOINT_GROUPS, args.get("group_id")), json=groups[0])
    requests_mock.patch(API_URL + "{}/{}".format(API_ENDPOINT_GROUPS, 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_ip_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 = load_test_data("assign_group_response.json")
    groups = load_test_data("get_groups_response.json")
    context_data = load_test_data("assign_group_context.json")
    # For Domain group
    with open("test_data/assign_ip_group_hr.md") as f:
        result_hr = f.read()
    args = {"group_id": "4", "members": "8.8.8.8/25"}
    requests_mock.get(API_URL + "{}/{}".format(API_ENDPOINT_GROUPS, args.get("group_id")), json=groups[3])
    requests_mock.patch(API_URL + "{}/{}".format(API_ENDPOINT_GROUPS, args.get("group_id")), json=assign_group_res[3])
    # 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[3]
    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 = load_test_data("assign_group_response.json")
    groups = load_test_data("get_groups_response.json")
    context_data = load_test_data("assign_group_context.json")
    # For Account group
    with open("test_data/assign_account_group_hr.md") as f:
        result_hr = f.read()
    args = {"group_id": "3", "members": "account_3,account_4"}
    requests_mock.get(API_URL + "{}/{}".format(API_ENDPOINT_GROUPS, args.get("group_id")), json=groups[2])
    requests_mock.patch(API_URL + "{}/{}".format(API_ENDPOINT_GROUPS, 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 = load_test_data("assign_group_response.json")
    groups = load_test_data("get_groups_response.json")
    context_data = load_test_data("assign_group_context.json")
    # For Host group
    with open("test_data/assign_host_group_hr.md") as f:
        result_hr = f.read()
    args = {"group_id": "2", "members": "1,2"}
    requests_mock.get(API_URL + "{}/{}".format(API_ENDPOINT_GROUPS, args.get("group_id")), json=groups[1])
    requests_mock.patch(API_URL + "{}/{}".format(API_ENDPOINT_GROUPS, 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 = load_test_data("assign_group_response.json")

    args = {"group_id": "2", "members": "1,2"}
    requests_mock.get(API_URL + "{}/{}".format(API_ENDPOINT_GROUPS, 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")),
        ({"group_id": "-1", "members": "account1"}, ERRORS["INVALID_INTEGER_VALUE"].format("group_id")),
        ({"group_id": "1.5", "members": "account1"}, ERRORS["INVALID_INTEGER_VALUE"].format("group_id")),
        ({"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_assign_account_group_invalid_group_name(requests_mock, mocker, client):
    """
    Given:
    - A client object.
    - Arguments specifying invalid parameters for assigning members to a account group.

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

    Then:
    - Assert that the function raises a return_warning.
    """
    assign_group_res = load_test_data("assign_group_response.json")
    groups = load_test_data("get_groups_response.json")

    args = {"group_id": "3", "members": "account_5"}
    requests_mock.get(API_URL + "{}/{}".format(API_ENDPOINT_GROUPS, args.get("group_id")), json=groups[2])
    requests_mock.patch(API_URL + "{}/{}".format(API_ENDPOINT_GROUPS, args.get("group_id")), json=assign_group_res[2])

    return_warning = mocker.patch.object(VectraDetect, "return_warning")
    vectra_group_assign_command(client, args)

    assert return_warning.call_args[0][0] == "The following account names were invalid: account_5"


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 = load_test_data("unassign_group_response.json")
    groups = load_test_data("get_groups_response.json")
    context_data = load_test_data("unassign_group_context.json")
    # For Domain group
    with open("test_data/unassign_domain_group_hr.md") as f:
        result_hr = f.read()
    args = {"group_id": "1", "members": "*.domain1.net"}
    requests_mock.get(API_URL + "{}/{}".format(API_ENDPOINT_GROUPS, args.get("group_id")), json=groups[0])
    requests_mock.patch(API_URL + "{}/{}".format(API_ENDPOINT_GROUPS, 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_ip_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 = load_test_data("unassign_group_response.json")
    groups = load_test_data("get_groups_response.json")
    context_data = load_test_data("unassign_group_context.json")
    # For Domain group
    with open("test_data/unassign_ip_group_hr.md") as f:
        result_hr = f.read()
    args = {"group_id": "4", "members": "0.0.0.17/8"}
    requests_mock.get(API_URL + "{}/{}".format(API_ENDPOINT_GROUPS, args.get("group_id")), json=groups[3])
    requests_mock.patch(API_URL + "{}/{}".format(API_ENDPOINT_GROUPS, args.get("group_id")), json=unassign_group_res[3])
    # 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[3]
    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 = load_test_data("unassign_group_response.json")
    groups = load_test_data("get_groups_response.json")
    context_data = load_test_data("unassign_group_context.json")

    with open("test_data/unassign_host_group_hr.md") as f:
        result_hr = f.read()
    args = {"group_id": "2", "members": "3"}
    requests_mock.get(API_URL + "{}/{}".format(API_ENDPOINT_GROUPS, args.get("group_id")), json=groups[1])
    requests_mock.patch(API_URL + "{}/{}".format(API_ENDPOINT_GROUPS, 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 = load_test_data("unassign_group_response.json")
    groups = load_test_data("get_groups_response.json")
    context_data = load_test_data("unassign_group_context.json")

    with open("test_data/unassign_account_group_hr.md") as f:
        result_hr = f.read()
    args = {"group_id": "3", "members": "account_1"}
    requests_mock.get(API_URL + "{}/{}".format(API_ENDPOINT_GROUPS, args.get("group_id")), json=groups[2])
    requests_mock.patch(API_URL + "{}/{}".format(API_ENDPOINT_GROUPS, 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 = load_test_data("assign_group_response.json")

    args = {"group_id": "2", "members": "6,7"}
    requests_mock.get(API_URL + "{}/{}".format(API_ENDPOINT_GROUPS, 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")),
        ({"group_id": "-1", "members": "account1"}, ERRORS["INVALID_INTEGER_VALUE"].format("group_id")),
        ({"group_id": "1.5", "members": "account1"}, ERRORS["INVALID_INTEGER_VALUE"].format("group_id")),
        ({"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(
    "integration_context, expected_incidents",
    [
        ({"Accounts": {"refetch_ids": ["Accounts_36", "Accounts_1017"]}, "Hosts": {"refetch_ids": ["Hosts_472"]}}, 2),
        ({"Accounts": {"refetch_ids": []}, "Hosts": {"refetch_ids": []}}, 0),
        ({}, 0),
    ],
)
def test_fetch_incidents_with_refetch_ids_scenarios(mocker, client, integration_context, expected_incidents):
    """
    Test fetch_incidents with different integration context scenarios.
    Given:
    - A client object.
    - A mocked 'getLastRun' method that returns a dictionary with already_fetched IDs.
    - Different integration context scenarios (with refetch_ids, empty refetch_ids, no refetch_ids).
    When:
    - Fetching incidents with different integration context states.
    Then:
    - Assert that the integration context is updated correctly.
    - Assert that already_fetched is handled correctly based on the scenario.
    - Assert that incidents are processed correctly.
    """
    # Setup test data
    last_run = {
        "Accounts": {"last_timestamp": "2023-05-15T09:39:09Z", "id": "", "last_created_events": ["Accounts_36"]},
        "Hosts": {"last_timestamp": "2023-05-15T09:39:09Z", "id": "", "last_created_events": ["Hosts_472"]},
    }

    # Mock the API responses
    account_data = load_test_data("single_account.json")
    account_response = {"count": 1, "results": [account_data]}
    mocker.patch.object(client, "search_accounts", return_value=account_response)
    host_data = load_test_data("single_host.json")
    host_response = {"count": 1, "results": [host_data]}
    mocker.patch.object(client, "search_hosts", return_value=host_response)

    mocker.patch.object(client, "search_detections", return_value={"count": 0, "results": []})
    mocker.patch.object(client, "list_detections_by_host_id", return_value={"count": 0, "results": []})
    mocker.patch.object(client, "list_group_request", return_value={"count": 0, "results": []})
    mocker.patch.object(client, "list_assignments_request", return_value={"count": 0, "results": []})

    # Setup mocks
    mocker.patch.object(demisto, "getLastRun", return_value=last_run)
    mocker.patch.object(demisto, "setLastRun")
    mocker.patch.object(demisto, "debug")

    # Mock the integration context functions
    mocker.patch.object(VectraDetect, "get_integration_context", return_value=integration_context)
    set_integration_context_mock = mocker.patch.object(VectraDetect, "set_integration_context")

    params = {"isFetch": True, "first_fetch": "1 hour", "max_fetch": "200", "fetch_entity_types": ["Accounts", "Hosts"]}

    # Call the function
    new_last_run, incidents = fetch_incidents(client, params)
    if set_integration_context_mock.called:
        updated_context = set_integration_context_mock.call_args[0][0]
        # Each entity type should have refetch_ids set to empty list
        for entity_type in ["Accounts", "Hosts"]:
            assert entity_type in updated_context
            assert updated_context[entity_type]["refetch_ids"] == []

    # Verify incidents were processed
    assert len(incidents) == expected_incidents
    assert isinstance(incidents, list)
    assert isinstance(new_last_run, dict)


@pytest.mark.parametrize(
    "create_multiple_flag, last_created_events_input, expected_incident_count",
    [
        pytest.param(False, ["Accounts_36"], 0, id="flag-disabled-deduplicates"),
        pytest.param(True, ["Accounts_36"], 1, id="flag-enabled-bypasses-dedup"),
        pytest.param(None, ["Accounts_36"], 0, id="flag-not-provided-defaults-to-dedup"),
    ],
)
def test_fetch_incidents_create_multiple_by_timestamp_dedup_behavior(
    mocker, client, create_multiple_flag, last_created_events_input, expected_incident_count
):
    """
    Given:
    - A client object.
    - A last_run with Accounts_36 already present in last_created_events.
    - The create_multiple_incidents_by_timestamp flag set to False, True, or not provided.

    When:
    - Fetching incidents using the 'fetch_incidents' function.

    Then:
    - When the flag is False or not provided: the already-seen event is deduplicated (0 incidents created).
    - When the flag is True: the dedup check is bypassed and a new incident is created (1 incident).
    """
    last_run = {
        "Accounts": {
            "last_timestamp": "2022-07-03T23:34:07Z",
            "id": "",
            "last_created_events": list(last_created_events_input),
        }
    }
    VectraDetect.global_UI_URL = SERVER_URL

    mocker.patch.object(demisto, "getLastRun", return_value=last_run)
    mocker.patch.object(demisto, "debug")
    mocker.patch.object(demisto, "info")

    account_data = load_test_data("single_account.json")
    mocker.patch.object(client, "search_accounts", return_value={"count": 1, "results": [account_data]})
    mocker.patch.object(client, "search_detections", return_value={"count": 0, "results": []})
    mocker.patch.object(client, "list_group_request", return_value={"count": 0, "results": []})
    mocker.patch.object(client, "list_assignments_request", return_value={"count": 0, "results": []})

    mocker.patch.object(VectraDetect, "get_integration_context", return_value={})
    mocker.patch.object(VectraDetect, "set_integration_context")

    params: dict = {
        "isFetch": True,
        "first_fetch": "1 hour",
        "max_fetch": "200",
        "fetch_entity_types": ["Accounts"],
    }
    if create_multiple_flag is not None:
        params["create_multiple_incidents_by_timestamp"] = create_multiple_flag

    _, incidents = fetch_incidents(client, params)

    assert len(incidents) == expected_incident_count


def test_fetch_incidents_create_multiple_by_timestamp_no_duplicate_uid_in_checkpoint(mocker, client):
    """
    Given:
    - A client object.
    - A last_run with an empty last_created_events for Accounts.
    - The create_multiple_incidents_by_timestamp flag enabled.

    When:
    - Fetching incidents using the 'fetch_incidents' function with a single account result.

    Then:
    - Exactly one incident is created.
    - The checkpoint uid 'Accounts_36' is written to last_created_events exactly once,
      confirming the guard against duplicate entries holds even when dedup is bypassed.
    """
    VectraDetect.global_UI_URL = SERVER_URL

    last_run = {
        "Accounts": {
            "last_timestamp": "2022-07-03T23:34:07Z",
            "id": "",
            "last_created_events": [],
        }
    }
    mocker.patch.object(demisto, "getLastRun", return_value=last_run)
    mocker.patch.object(demisto, "debug")
    mocker.patch.object(demisto, "info")

    account_data = load_test_data("single_account.json")
    mocker.patch.object(client, "search_accounts", return_value={"count": 1, "results": [account_data]})
    mocker.patch.object(client, "search_detections", return_value={"count": 0, "results": []})
    mocker.patch.object(client, "list_group_request", return_value={"count": 0, "results": []})
    mocker.patch.object(client, "list_assignments_request", return_value={"count": 0, "results": []})

    mocker.patch.object(VectraDetect, "get_integration_context", return_value={})
    mocker.patch.object(VectraDetect, "set_integration_context")

    params = {
        "isFetch": True,
        "first_fetch": "1 hour",
        "max_fetch": "200",
        "fetch_entity_types": ["Accounts"],
        "create_multiple_incidents_by_timestamp": True,
    }

    new_last_run, incidents = fetch_incidents(client, params)

    assert len(incidents) == 1
    checkpoint_events = new_last_run["Accounts"]["last_created_events"]
    assert checkpoint_events.count("Accounts_36") == 1


def test_update_remote_system_closing_notes_refetch(client, mocker):
    """
    Given:
    - A client object.
    - A mocker for patching client functions.
    - Mocked arguments with JSON data from a test file, including closing notes and related data.
    - refetch_closed_incidents is True
    When:
    - Calling the 'update_remote_system_command' function with arguments indicating the closure of an incident.
    Then:
    - Assert that the remote incident ID returned matches the expected value.
    - Assert that the {entity ID}-{entity Type} is added to refetch_ids in integration context.
    """
    mock_args = util_load_json("test_data/update_remote_system_args.json")

    mock_args["data"]["closeNotes"] = "Closing notes"
    mock_args["data"]["closeReason"] = "Closed due to testing"
    mock_args["delta"]["closingUserId"] = "user2"
    mock_args["data"]["vectraxdrentityid"] = "123"
    mock_args["data"]["vectraxdrentitytype"] = "host"

    # Mock integration context
    mocker.patch.object(VectraDetect, "get_integration_context", return_value={"Hosts": {"refetch_ids": []}})
    set_integration_context = mocker.patch.object(VectraDetect, "set_integration_context")

    mocker.patch.object(client, "update_entity_tags_request", return_value={})
    mocker.patch.object(client, "list_entity_tags_request", return_value={})
    mocker.patch.object(client, "add_note_request", return_value={})

    params = {"refetch_closed_incidents": "true"}
    mocker.patch.object(demisto, "params", return_value=params)
    mocker.patch.object(demisto, "args", return_value=mock_args)
    remote_incident_id = update_remote_system_command(client)
    assert remote_incident_id == "123-host"

    # Verify entity ID was added to refetch_ids
    expected_refetch_id = f"Hosts_{mock_args['data']['vectraxdrentityid']}"
    set_integration_context.assert_called_once_with({"Hosts": {"refetch_ids": [expected_refetch_id]}})


def test_update_remote_system_closing_notes_refetch_invalid_id(client, mocker):
    """
    Given:
    - A client object.
    - A mocker for patching client functions.
    - Mocked arguments with JSON data from a test file, including closing notes and related data.
    - refetch_closed_incidents is True
    When:
    - Calling the 'update_remote_system_command' function with arguments indicating the closure of an incident.
    Then:
    - Assert that the function raises a ValueError.
    - Assert that the raised error message matches the expected error message.
    """
    mock_args = util_load_json("test_data/update_remote_system_args.json")

    mock_args["remoteId"] = "123-"
    mock_args["data"]["closeNotes"] = "Closing notes"
    mock_args["data"]["closeReason"] = "Closed due to testing"
    mock_args["delta"]["closingUserId"] = "user2"

    mocker.patch.object(client, "update_entity_tags_request", return_value={})
    mocker.patch.object(client, "list_entity_tags_request", return_value={})
    mocker.patch.object(client, "add_note_request", return_value={})

    params = {"refetch_closed_incidents": "true"}
    mocker.patch.object(demisto, "params", return_value=params)
    mocker.patch.object(demisto, "args", return_value=mock_args)

    with pytest.raises(ValueError) as exception:
        update_remote_system_command(client)

    assert str(exception.value) == "Both 'entity_id' and 'entity_type' arguments are required."


@pytest.mark.parametrize("refetch_closed_incidents", ["invalid", ""])
def test_update_remote_system_closing_notes_refetch_invalid(client, mocker, refetch_closed_incidents):
    """
    Given:
    - A client object.
    - A mocker for patching client functions.
    - Mocked arguments with JSON data from a test file, including closing notes and related data.
    - refetch_closed_incidents is invalid or empty
    When:
    - Calling the 'update_remote_system_command' function with arguments indicating the closure of an incident.
    Then:
    - Assert that the function raises a ValueError.
    - Assert that the raised error message matches the expected error message.
    """
    mock_args = util_load_json("test_data/update_remote_system_args.json")

    mock_args["data"]["closeNotes"] = "Closing notes"
    mock_args["data"]["closeReason"] = "Closed due to testing"
    mock_args["delta"]["closingUserId"] = "user2"

    mocker.patch.object(client, "update_entity_tags_request", return_value={})
    mocker.patch.object(client, "list_entity_tags_request", return_value={})
    mocker.patch.object(client, "add_note_request", return_value={})

    params = {"refetch_closed_incidents": refetch_closed_incidents}
    mocker.patch.object(demisto, "params", return_value=params)
    mocker.patch.object(demisto, "args", return_value=mock_args)

    with pytest.raises(ValueError) as exception:
        update_remote_system_command(client)

    assert str(exception.value) == "Argument does not contain a valid boolean-like value"


@pytest.mark.parametrize("close_reason", ["benign", "remediated"])
def test_markall_detections_asclosed_command_for_account_when_success(client, requests_mock, close_reason):
    """
    Tests markall_detections_asclosed_command command function when success with host.
    """
    account_id = "36"
    args = {"account_id": account_id, "close_reason": close_reason}

    account_data = load_test_data("single_account.json")
    requests_mock.get(f"{API_URL}{API_ENDPOINT_ACCOUNT}/{account_id}", json=account_data, status_code=200)

    response = {"_meta": {"level": "Success", "message": f"Successfully closed detections as {close_reason}"}}
    requests_mock.patch(f"{API_URL}{API_ENDPOINT_DETECTIONS}/close", json=response, status_code=200)

    result = markall_detections_asclosed_command(client=client, entity_type="account", args=args)

    assert (
        result.readable_output
        == f"##### The active detections of the provided account have been successfully closed as {close_reason}."
    )
    assert result.raw_response == response


def test_markall_detections_asclosed_command_for_account_when_no_detection(client, requests_mock):
    """
    Tests markall_detections_asclosed_command command function when no detection in account.
    """
    account_id = "36"
    close_reason = "benign"
    args = {"account_id": account_id, "close_reason": close_reason}

    account_data = load_test_data("single_account.json")
    account_data["detection_summaries"] = []
    requests_mock.get(f"{API_URL}{API_ENDPOINT_ACCOUNT}/{account_id}", json=account_data, status_code=200)

    result = markall_detections_asclosed_command(client=client, entity_type="account", args=args)

    assert result.readable_output == "##### There are no active detections present."
    assert result.raw_response == {}


@pytest.mark.parametrize("close_reason", ["benign", "remediated"])
def test_markall_detections_asclosed_command_for_host_when_success(client, requests_mock, close_reason):
    """
    Tests markall_detections_asclosed_command command function when success with host.
    """
    host_id = "472"
    args = {"host_id": host_id, "close_reason": close_reason}

    host_data = load_test_data("single_host.json")
    requests_mock.get(f"{API_URL}{API_ENDPOINT_HOST}/{host_id}", json=host_data, status_code=200)

    response = {"_meta": {"level": "Success", "message": f"Successfully closed detections as {close_reason}"}}
    requests_mock.patch(f"{API_URL}{API_ENDPOINT_DETECTIONS}/close", json=response, status_code=200)

    result = markall_detections_asclosed_command(client=client, entity_type="host", args=args)

    assert (
        result.readable_output
        == f"##### The active detections of the provided host have been successfully closed as {close_reason}."
    )
    assert result.raw_response == response


def test_markall_detections_asclosed_command_for_host_when_no_detection(client, requests_mock):
    """
    Tests markall_detections_asclosed_command command function when no detection in host.
    """
    host_id = "472"
    close_reason = "benign"
    args = {"host_id": host_id, "close_reason": close_reason}

    host_data = load_test_data("single_host.json")
    host_data["detection_summaries"] = []
    requests_mock.get(f"{API_URL}{API_ENDPOINT_HOST}/{host_id}", json=host_data, status_code=200)

    result = markall_detections_asclosed_command(client=client, entity_type="host", args=args)

    assert result.readable_output == "##### There are no active detections present."
    assert result.raw_response == {}


@pytest.mark.parametrize(
    "args,type,expected_error",
    [
        pytest.param(
            {"close_reason": "benign", "account_id": ""},
            "account",
            ERRORS["REQUIRED_ARGUMENT"].format("account_id"),
        ),
        pytest.param(
            {"account_id": "-1", "close_reason": "benign"},
            "account",
            ERRORS["INVALID_INTEGER_VALUE"].format("account_id"),
        ),
        pytest.param(
            {"account_id": "36"},
            "account",
            ERRORS["REQUIRED_ARGUMENT"].format("close_reason"),
        ),
        pytest.param(
            {"host_id": "-1", "close_reason": "benign"},
            "host",
            ERRORS["INVALID_INTEGER_VALUE"].format("host_id"),
        ),
        pytest.param(
            {"host_id": "36"},
            "host",
            ERRORS["REQUIRED_ARGUMENT"].format("close_reason"),
        ),
        pytest.param(
            {"host_id": "36", "close_reason": "invalid"},
            "host",
            'Invalid close_reason. Must be "benign" or "remediated".',
        ),
        pytest.param(
            {"host_id": "36", "close_reason": ""},
            "host",
            ERRORS["REQUIRED_ARGUMENT"].format("close_reason"),
        ),
    ],
)
def test_markall_detections_asclosed_command_invalid_args(client, args, type, expected_error):
    """
    Tests markall_detections_asclosed_command command function error validation cases.
    """
    with pytest.raises(ValueError) as err:
        markall_detections_asclosed_command(client=client, entity_type=type, args=args)

    assert str(err.value) == expected_error


@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}"}}
    requests_mock.patch(f"{API_URL}{API_ENDPOINT_DETECTIONS}/close", json=response)

    args = {"detection_ids": "123,234", "close_reason": close_reason}
    result = mark_detections_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",
    [
        pytest.param({}, ERRORS["REQUIRED_ARGUMENT"].format("detection_ids")),
        pytest.param({"detection_ids": "abc"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_ids")),
        pytest.param({"detection_ids": "0"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_ids")),
        pytest.param({"detection_ids": "-5"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_ids")),
        pytest.param({"detection_ids": "123,abc,456"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_ids")),
        pytest.param({"detection_ids": "123"}, ERRORS["REQUIRED_ARGUMENT"].format("close_reason")),
        pytest.param(
            {"detection_ids": "123", "close_reason": "invalid"},
            "Invalid close_reason. Must be 'benign' or 'remediated'.",
        ),
    ],
)
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:
        mark_detections_asclosed_command(client=client, args=args)

    assert str(err.value) == error_msg


def test_mark_detections_asopen_command_valid(requests_mock, client):
    """
    Tests mark_detections_asopen_command with valid open detection.
    """
    response = {"_meta": {"level": "success", "message": "Successfully re-opened detections."}}
    requests_mock.patch(f"{API_URL}{API_ENDPOINT_DETECTIONS}/open", json=response)

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

    assert result.outputs is None
    assert result.readable_output == "##### The provided detection IDs have been successfully re-opened."
    assert result.raw_response == response


@pytest.mark.parametrize(
    "args,error_msg",
    [
        pytest.param({}, ERRORS["REQUIRED_ARGUMENT"].format("detection_ids")),
        pytest.param({"detection_ids": "abc"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_ids")),
        pytest.param({"detection_ids": "0"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_ids")),
        pytest.param({"detection_ids": "-5"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_ids")),
        pytest.param({"detection_ids": "123,abc,456"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_ids")),
    ],
)
def test_mark_detections_asopen_command_invalid_args(args, client, error_msg):
    """
    Tests mark_detections_asopen_command with invalid arguments.
    """
    with pytest.raises(ValueError) as err:
        mark_detections_asopen_command(client=client, args=args)

    assert str(err.value) == error_msg


def test_mark_detections_asopen_command_api_error(requests_mock, client):
    """
    Tests mark_detections_asopen_command when API returns error.
    """
    response = {"_meta": {"level": "error", "message": "Failed to open detections"}}
    requests_mock.patch(f"{API_URL}{API_ENDPOINT_DETECTIONS}/open", json=response)

    args = {"detection_ids": "123"}

    with pytest.raises(DemistoException) as err:
        mark_detections_asopen_command(client=client, args=args)

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


def test_mark_detections_asclosed_command_api_error(requests_mock, client):
    """
    Tests mark_detections_asclosed_commandwhen API returns error.
    """

    response = {"_meta": {"level": "error", "message": "Failed to close detections"}}
    requests_mock.patch(f"{API_URL}{API_ENDPOINT_DETECTIONS}/close", json=response)

    args = {"detection_ids": "123", "close_reason": "benign"}

    with pytest.raises(DemistoException) as err:
        mark_detections_asclosed_command(client=client, args=args)

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