Vectra_Detect
This integration allows to create incidents based on Vectra Accounts/Hosts/Detections objects.
Network Security · Vectra AI
Details
| ID | Vectra_Detect |
|---|---|
| Provider | Vectra AI |
| Category | Network Security |
| From Version | 6.5.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix 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
- Fetch accounts, hosts and detections from Vectra Detect.
- Bi-Directional mirroring for accounts and hosts.
- List and describe accounts, hosts, detections, and users.
- List, describe, create, and resolve assignments for accounts and hosts.
- List, describe, and create assignment outcomes.
- List, create, update, and delete notes for accounts, hosts, and detections.
- List, create, and remove tags for accounts, hosts, and detections.
- List, assign, and unassign members in group.
- Mark and unmark detection as fixed.
- Mark all detections as fixed for accounts and hosts.
- Get detection’s PCAP file.
- Clean up all incidents in Cortex XSOAR by closing duplicate incidents from Vectra Detect.
Configure Vectra Detect on Cortex XSOAR
- Navigate to Settings > Integrations > Servers & Services.
- Search for Vectra Detect.
-
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 - 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:
- Select Fetches incidents.
- Under Classifier, select “Vectra Detect”.
- Under Incident type, select “N/A”.
- Under Mapper (incoming), select “Vectra Detect - Incoming Mapper” for default mapping.
- Select the “Authentication Type” (Default - API Token).
- Enter connection parameters. (Vectra Detect FQDN or IP, API Token or Client ID and Client Secret Key)
- Select SSL certificate validation and Proxy if required.
- Update “Max created incidents per fetch” & “First fetch timestamp” based on your requirements.
- Select the Incident Mirroring Direction:
- Incoming - Mirrors changes from the Vectra into the Cortex XSOAR incident.
- Outgoing - Mirrors changes from the Cortex XSOAR incident to the Vectra.
- Incoming And Outgoing - Mirrors changes both Incoming and Outgoing directions on incidents.
- 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.
- 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. - Provide the filter parameter “Tags”, to filter entities by specific tag/s for fetch type account and host.
- Provide the filter parameter “Detection Category” and “Detection Type”, to filter detections by the specified category and type for fetch type account and host.
- 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.
- 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.
- 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_fetchparameter 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 Typecredentials— API Tokenoauth_credentials— Client IDinsecure— Trust any certificate (not secure)use_proxy— Use system proxy settingsisFetch— Fetch incidentsincidentType— Incident typeincidentFetchInterval— Incidents Fetch Intervalfirst_fetch— First fetch timestampmirror_direction— Mirroring Directionrefetch_closed_incidents— Re-Fetch closed incidents via mirroringnote_tag— Mirror tag for notesfetch_entity_types— Entity types to fetchtags— Tagsdetection_category— Detection Categorydetection_type— Detection Typehosts_fetch_query— Hosts fetch queryaccounts_fetch_query— Accounts fetch querydetections_fetch_query— Detections fetch querymax_fetch— Max created incidents per fetchlook_back— Advanced: Minutes to look back when fetchingfetch_escalated_accounts_and_hosts— Advanced: Fetch escalated Accounts and Hostscreate_multiple_incidents_by_timestamp— Re-Fetch Account/Host entity and Detection for new activity/event
Commands (47)
-
vectra-account-add-tagsAdd tags to an Account.
-
vectra-account-del-tagsDelete tags from an Account.
-
vectra-account-describeReturns a single Account details.
-
vectra-account-markall-detections-asclosedMark active detections as closed by providing the ID of the account in the argument.
-
vectra-account-markall-detections-asfixedMark active detections as fixed by providing the ID of the account in the argument.
-
vectra-account-note-addAdd a note to the account.
-
vectra-account-note-listList all notes of the specific account.
-
vectra-account-note-removeRemove a note from the account.
-
vectra-account-note-updateUpdate a note in the account.
-
vectra-account-tag-listReturns a list of tags for a specified account.
-
vectra-assignment-assignAssigns an Account/Host entity to a Vectra User for investigation. If an assignment already exists on this entity, it will be reassigned.
-
vectra-assignment-describeReturns a single assignment details.
-
vectra-assignment-resolveResolves an assignment by selecting resolution scheme. Could be 'resolving only' or 'resolving by filtering detections'.
-
vectra-detection-add-tagsAdd tags to a Detection.
-
vectra-detection-del-tagsDelete tags from a Detection.
-
vectra-detection-describeReturns a single detection details.
-
vectra-detection-get-pcapReturns a Detection's PCAP file (if available).
-
vectra-detection-markasfixedMarks/Unmarks a Detection as fixed by providing the Detection ID.
-
vectra-detection-note-addAdd a note to the detection.
-
vectra-detection-note-listList all notes of the specific detection.
-
vectra-detection-note-removeRemove a note from the detection.
-
vectra-detection-note-updateUpdate a note in the detection.
-
vectra-detection-tag-listReturns a list of tags for a specified detection.
-
vectra-detections-mark-asclosedMark detections as closed with provided detection IDs in the argument.
-
vectra-detections-mark-asopenOpen detections with provided detection IDs in the argument.
-
vectra-group-assignAssign members to the specified group.
-
vectra-group-listReturns a list of all groups.
-
vectra-group-unassignUnassign members from the specified group.
-
vectra-host-add-tagsAdd tags to an Host.
-
vectra-host-del-tagsDelete tags from an Host.
-
vectra-host-describeReturns a single Host details.
-
vectra-host-markall-detections-asclosedMark active detections as closed by providing the ID of the host in the argument.
-
vectra-host-markall-detections-asfixedMark active detections as fixed by providing ID of the host in the argument.
-
vectra-host-note-addAdd a note to the host.
-
vectra-host-note-listList all notes of the specific host.
-
vectra-host-note-removeRemove a note from the host.
-
vectra-host-note-updateUpdate a note in the host.
-
vectra-host-tag-listReturns a list of tags for a specified host.
-
vectra-outcome-createCreates a new assignment outcome.
-
vectra-outcome-describeReturns a single outcome details.
-
vectra-search-accountsReturns 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-assignmentsReturn a list of assignments. By default already resolved assignment are not returned.
-
vectra-search-detectionsReturns 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-hostsReturns 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-outcomesReturns a list of assignment outcomes.
-
vectra-search-usersReturns a list of Vectra Users. All search attributes will be cumulative.
-
vectra-user-describeReturns 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."