VectraRUXEventsDetections
This integration allows the security operations center to create and manage incidents based on Vectra Events Detections.
Network Security · Vectra RUX
Details
| ID | VectraRUXEventsDetections |
|---|---|
| Provider | Vectra AI |
| Category | Network Security |
| From Version | 6.10.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix Attack Surface Management Cortex Cloud Application Security Cloud Posture Security Cloud Runtime Security EDR Email Security Exposure Management Threat Intelligence Management XSIAM |
README
This integration allows the security operations center to create and manage incidents based on Vectra Events Detections.
This integration was integrated and tested with Vectra API v3.5.
Configure Vectra RUX - Network Detection & Response in Cortex
| Parameter | Description | Required |
|---|---|---|
| Server URL | URL of the Vectra AI platform. | True |
| Client ID | Identifies a client or application for authentication and authorization in the Vectra AI platform. | True |
| Client Secret Key | Secret key used for secure communication with the Vectra AI platform. | True |
| Fetch incidents | False | |
| Max Fetch | The maximum number of events detections to fetch each time. If the value is greater than 200, it will be considered as 200. The maximum is 200. | False |
| First Fetch Time | The date or relative timestamp from which to begin fetching events detections. Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ. For example: 01 Oct 2025, 01 Mar 2021 04:45:33, 2025-12-17T14:05:44Z. |
False |
| Entity Types | Filter by entity type. If not selected, it will fetch all events detections. | False |
| Create Incidents for Prioritized Detections | Enabling this checkbox generates incidents for prioritized events detections. If not selected, incidents are created for all events detections. | False |
| Create Incidents for Escalated Detections | Enabling this checkbox generates incidents for escalated events detections. If not selected, incidents are created for all events detections. | False |
| Mirroring Direction | The mirroring direction in which to mirror the detections. You can mirror ‘Incoming’ (from Vectra to XSOAR), ‘Outgoing’ (from XSOAR to Vectra), or in both directions. | False |
| Mirror tag for notes | The tag value should be used to mirror the detection note by adding the same tag in the notes. | False |
| Open Detection on Incident Reopen | Enabling this checkbox opens the detection in Vectra when the incident is reopened in XSOAR. Note: This parameter is only used when the mirroring direction is set to ‘Outgoing’ or ‘Incoming And Outgoing’. |
False |
| Detection Status for Incident Reopen | Detection status to set in Vectra when incident is reopened in XSOAR. Default value is ‘Escalated’. Note: This parameter is only used when open detection on incident reopen is ‘checked’ and the mirroring direction is set to ‘Outgoing’ or ‘Incoming And Outgoing’. |
False |
| Close Detection on Incident Closure | Enabling this checkbox closes the detection in Vectra when the incident is closed in XSOAR. Note: This parameter is only used when the mirroring direction is set to ‘Outgoing’ or ‘Incoming And Outgoing’. |
False |
| Detection Close Reason for Incident Closure | Detection close reason to set in Vectra when closing incidents in XSOAR. Default value is ‘Remediated’. Note: This parameter is only used when close detection on incident closer is ‘checked’ and the mirroring direction is set to ‘Outgoing’ or ‘Incoming And Outgoing’. |
False |
| Incident type | False | |
| Trust any certificate (not secure) | When checked, no SSL certificates check will be done when interacting with the Vectra RUX API. It’s insecure. (Default - unchecked) | False |
| Use system proxy settings | Use the system proxy settings to reach with the Vectra RUX API. | False |
Configuration for fetching Vectra RUX Events Detections as an XSOAR Incident
To fetch Vectra RUX Events Detections follow the next steps:
- Select Fetches incidents.
- Under Classifier, select “N/A”.
- Under Incident type, select “Vectra RUX Events Detection”.
- Under Mapper (incoming), select “Vectra RUX - Incoming Mapper” for default mapping.
- Enter connection parameters. (Server URL, Client ID & Client Secret Key)
- Update “Max Fetch” & “First Fetch Time” based on your requirements.
- Filter the Detections by the “Entity Type”(Account and Host).
- Filter the Detections by “Create Incidents for Prioritized Detections”, “Create Incidents for Escalated Detections”:
- Default Behavior: By default, the integration retrieves all event detections across all entity types (Account and Host) and all detection statuses (Open, Acknowledged, Escalated, Paused). This includes both prioritized and non-prioritized detections.
- Fetch Only Prioritized Detections: Enable “Create Incidents for Prioritized Detections” to filter out non-prioritized detections. Incidents will be created only for prioritized event detections.
- Fetch Only Escalated Detections: Enable “Create Incidents for Escalated Detections” to retrieve all escalated detections, regardless of their priority level.
- Fetch Prioritized and Escalated Detections: Enable both “Create Incidents for Prioritized Detections” and “Create Incidents for Escalated Detections”. This configuration retrieves detections that are either prioritized or escalated.
- Select the Incident Mirroring Direction:
- Incoming - Mirrors changes from the Vectra RUX Detection into the Cortex XSOAR incident.
- Outgoing - Mirrors changes from the Cortex XSOAR incident to the Vectra RUX Detection.
- Incoming And Outgoing - Mirrors changes both Incoming and Outgoing directions on incidents.
- Enter the relevant tag name for mirror notes.
Note: This value is mapped to the dbotMirrorTags incident field in Cortex XSOAR, which defines how Cortex XSOAR handles notes when you tag them in the War Room. This is required for mirroring notes from Cortex XSOAR to Vectra RUX. - Uncheck the “Open Detection on Incident Reopen” option if you don’t want to open the detection in Vectra when the incident is reopened in XSOAR. This option is only used when the mirroring direction is set to ‘Outgoing’ or ‘Incoming And Outgoing’.
- Select the “Detection Status for Incident Reopen” option if you want to set the detection status in Vectra when the incident is reopened in XSOAR. Default value is ‘Escalated’. This option is only used when the “Open Detection on Incident Reopen” option is checked and the mirroring direction is set to ‘Outgoing’ or ‘Incoming And Outgoing’.
- Uncheck the “Close Detection on Incident Closure” option if you don’t want to close the detection in Vectra when the incident is closed in XSOAR. This option is only used when the mirroring direction is set to ‘Outgoing’ or ‘Incoming And Outgoing’.
- Select the “Detection Close Reason for Incident Closure” option if you want to set the detection close reason in Vectra when the incident is closed in XSOAR. Default value is ‘Benign’. This option is only used when the “Close Detection on Incident Closure” option is checked and the mirroring direction is set to ‘Outgoing’ or ‘Incoming And Outgoing’.
- Select SSL certificate validation and Proxy if required.
Notes for mirroring:
- This feature is compliant with XSOAR version 6.0 and above.
- When mirroring incidents, you can make changes in Vectra that will be reflected in Cortex XSOAR, or vice versa.
- Any tags removed from the Vectra entity will not be removed in the XSOAR incident, as XSOAR doesn’t allow the removal of the tags field via the backend. However, tags removed from the XSOAR incident UI will be removed from the Vectra entity.
- New notes from the XSOAR incident will be created as notes in the Vectra Detection. Updates to existing notes in the XSOAR incident will not be reflected in the Vectra Detection.
- New notes from the Vectra Detection will be created as notes in the XSOAR incident. Updates to existing notes in the Vectra Detection will create new notes in the XSOAR incident.
- If the Detection Status is updated in the Vectra Detection, it will be reflected in the XSOAR incident, or vice versa.
- If you want to reopen a detection in Vectra when the incident is reopened in XSOAR, check the “Open Detection on Incident Reopen” option. This option is only used when the mirroring direction is set to ‘Outgoing’ or ‘Incoming And Outgoing’.
- Set the “Detection Status for Incident Reopen” option to set the detection status in Vectra when the incident is reopened in XSOAR. Default value is ‘Escalated’. This option is only used when the “Open Detection on Incident Reopen” option is checked and the mirroring direction is set to ‘Outgoing’ or ‘Incoming And Outgoing’.
- If you want to close a detection in Vectra when the incident is closed in XSOAR, check the “Close Detection on Incident Closure” option. This option is only used when the mirroring direction is set to ‘Outgoing’ or ‘Incoming And Outgoing’.
- Set the “Detection Close Reason for Incident Closure” option to set the detection close reason in Vectra when the incident is closed in XSOAR. Default value is ‘Benign’. This option is only used when the “Close Detection on Incident Closure” option is checked and the mirroring direction is set to ‘Outgoing’ or ‘Incoming And Outgoing’.
- The mirroring settings apply only for incidents that are fetched after applying the settings.
- The mirroring is strictly tied to Incident type “Vectra RUX Events Detection” & Incoming mapper “Vectra RUX - Incoming Mapper” If you want to change or use your custom incident type/mapper then make sure changes related to these are present.
- If you want to use the mirror mechanism and you’re using custom mappers, then the incoming mapper must contain the following fields: dbotMirrorDirection, dbotMirrorId, dbotMirrorInstance, and dbotMirrorTags.
- To use a custom mapper, you must first duplicate the mapper and update the fields in the copy of the mapper. (Refer to the “Create a custom mapper consisting of the default Vectra RUX mapper” section for more information.)
- Following new fields are introduced in the response of the incident to enable the mirroring:
- mirror_direction: This field determines the mirroring direction for the incident. It is a required field for XSOAR to enable mirroring support.
- mirror_tags: This field determines what would be the tag needed to mirror the XSOAR entry out to Vectra RUX. It is a required field for XSOAR to enable mirroring support.
- mirror_instance: This field determines from which instance the XSOAR incident was created. It is a required field for XSOAR to enable mirroring support.
Expire Inactive Detections
- Use the Expire Inactive Detections - Vectra RUX playbook to expire inactive detections that are fetched in XSOAR.
- You can also schedule a job with the Expire Inactive Detections - Vectra RUX playbook in Cortex XSOAR to expire inactive detections periodically. Refer to Cortex XSOAR documentation for more information. To create a job with a 24-hour recurring schedule, follow these steps:
- In Cortex XSOAR, navigate to Jobs (via the top menu or sidebar).
- Click New Job.
- Select Time triggered and enable Recurring.
- Set the schedule to Every 24 hours (or configure a specific daily time using a cron expression such as
0 0 * * *). - Set the Name for the job (e.g.,
Expire Inactive Detections - Daily). - Under Playbook, select Expire Inactive Detections - Vectra RUX.
- Click Create new job to activate the job.
Commands
You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
vectra-detections-mark-asclosed
Mark detections as closed with provided detection IDs in the argument.
Base Command
vectra-detections-mark-asclosed
Input
| Argument Name | Description | Required |
|---|---|---|
| detection_ids | Provide a list of detection IDs separated by commas or a single detection ID. | Required |
| close_reason | Provide the close reason. Possible values are: benign, remediated. | Required |
Context Output
There is no context output for this command.
Command example
!vectra-detections-mark-asclosed detection_ids=123,345 close_reason=remediated
Human Readable Output
The provided detection IDs have been successfully closed as remediated
vectra-user-list
Returns a list of users.
Base Command
vectra-user-list
Input
| Argument Name | Description | Required |
|---|---|---|
| Filter by email. | Optional | |
| role | Filter users with the specified role. Use the role standardized name. Possible values are: Admin, Auditor, Global Analyst, Read-Only, Restricted Admin, Security Analyst, Setting Admin, Super Admin. | Optional |
| last_login_timestamp | Return only the users which have a last login timestamp equal to or after the given timestamp. Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ. For example: 01 May 2023, 01 Mar 2021 04:45:33, 2022-04-17T14:05:44Z. |
Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.User.id | Number | The ID of the User. |
| Vectra.User.user_id | Number | The ID of the User. |
| Vectra.User.name | String | Username of the user. |
| Vectra.User.email | String | The email associated with the user. |
| Vectra.User.role | String | The role associated with the user. |
| Vectra.User.last_login_timestamp | String | Last login timestamp in UTC format of the user. |
| Vectra.User.last_login | String | Last login timestamp of the user. |
Command example
#### Context Example
```json
{
"Vectra": {
"User": [
{
"id": 59,
"user_id": 59,
"username": "user.name1",
"email": "",
"role": "Security Analyst",
"last_login_timestamp": "2023-08-22T09:24:44Z",
"last_login": "2023-08-22T09:24:44Z"
},
{
"id": 32,
"user_id": 32,
"username": "user.name2",
"email": "",
"role": "Super Admin",
"last_login_timestamp": "2023-07-02T18:41:19Z",
"last_login": "2023-07-02T18:41:19Z"
},
{
"id": 23,
"user_id": 23,
"username": "vectra_mdr",
"email": "",
"role": "Vectra MDR"
}
]
}
}
Human Readable Output
Users Table
User ID User Name Role Last Login Timestamp 59 user.name1 Security Analyst 2023-08-22T09:24:44Z 32 user.name2 Super Admin 2023-07-02T18:41:19Z 23 vectra_mdr Vectra MDR
vectra-entity-list
Returns a list of entities.
Base Command
vectra-entity-list
Input
| Argument Name | Description | Required |
|---|---|---|
| prioritized | Fetch only entities whose priority score is above the configured priority threshold will be included in the response. Possible values are: true, false. | Optional |
| entity_type | Specify the type of the entity. Possible values are: account, host. | Optional |
| name | Filter by matching entity name. | Optional |
| tags | Filter by a tag or a comma-separated list of tags. | Optional |
| state | Filter on entity activation state. Possible values are: active, inactive. | Optional |
| ordering | Orders records by last timestamp or urgency score. Default sorting is by urgency score in descending order. Use the minus symbol (-) to sort scores in descending order. Multiple ordering fields can be specified with a comma-separated list (e.g., ordering=urgency_score,-name). | Optional |
| last_detection_timestamp | Return only the entities which have a last detection timestamp equal to or after the given timestamp. Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ. For example: 01 May 2023, 01 Mar 2021 04:45:33, 2022-04-17T14:05:44Z. |
Optional |
| page | Enables the caller to specify a particular page of results. Default is 1. | Optional |
| page_size | Specify the desired page size for the request. Maximum is 5000. Default is 50. | Optional |
| last_modified_timestamp | Return only the entities which have a last modified timestamp equal to or after the given timestamp. Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ. For example: 01 May 2023, 01 Mar 2021 04:45:33, 2022-04-17T14:05:44Z. |
Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.Entity.id | Number | ID of the entity. |
| Vectra.Entity.name | String | Name of the entity. |
| Vectra.Entity.breadth_contrib | Number | Breadth contribution of the entity. |
| Vectra.Entity.importance | Number | Entity importance. |
| Vectra.Entity.type | String | Type of the entity. |
| Vectra.Entity.is_prioritized | Boolean | Entity is prioritized or not. |
| Vectra.Entity.severity | String | Severity of the entity. |
| Vectra.Entity.urgency_score | Number | Urgency score of the entity. |
| Vectra.Entity.velocity_contrib | Number | Velocity contribution of the entity. |
| Vectra.Entity.detection_set | String | Set of detections related to entity. |
| Vectra.Entity.last_detection_timestamp | Date | Time of the last detection activity related to entity. |
| Vectra.Entity.notes.id | String | Notes of the entity. |
| Vectra.Entity.notes.dateCreated | String | Created date of the Note. |
| Vectra.Entity.notes.dateModified | String | Modified date of the Note. |
| Vectra.Entity.notes.createdBy | String | Created user of the Note. |
| Vectra.Entity.notes.ModifiedBy | String | Modified user of the Note. |
| Vectra.Entity.notes.note | String | Note of the entity. |
| Vectra.Entity.attack_rating | Number | Attack Ratting of the entity. |
| Vectra.Entity.privilege_level | String | Privilege Level of the entity. |
| Vectra.Entity.privilege_category | String | Privilege Category of the entity. |
| Vectra.Entity.attack_profile | String | Attack Profile of the entity. |
| Vectra.Entity.sensors | Unknown | Sensors of the entity. |
| Vectra.Entity.state | String | State of the entity. |
| Vectra.Entity.tags | Unknown | Tags of the entity. |
| Vectra.Entity.url | String | Url link of the entity. |
| Vectra.Entity.host_type | Unknown | Host type of the entity. |
| Vectra.Entity.account_type | String | Account type of the entity. |
Command example
!vectra-entity-list entity_type=account page=1 page_size=4 tags=test,test1 prioritized=true state=active
Context Example
{
[
{
"id": 334,
"name": "account_name",
"breadth_contrib": 2,
"entity_importance": 1,
"importance": 2,
"entity_type": "account",
"type": "account",
"is_prioritized": true,
"severity": "Critical",
"urgency_score": 100,
"velocity_contrib": 2,
"detection_set": [
"http://server_url.com/api/v3.3/detections/1933",
"http://server_url.com/api/v3.3/detections/1934"
],
"last_detection_timestamp": "2023-05-15T09:39:24Z",
"last_modified_timestamp": "2023-07-27T08:56:09Z",
"notes": [],
"attack_rating": 10,
"attack_profile": "AWS Threat Actor",
"sensors": [
"test"
],
"state": "active",
"tags": [
"test"
],
"url": "http://server_url.com/api/v3.3/accounts/334",
"account_type": [
"o365"
]
},
{
"id": 335,
"name": "account_name_1",
"breadth_contrib": 2,
"entity_importance": 1,
"importance": 2,
"entity_type": "account",
"type": "account",
"is_prioritized": true,
"severity": "Critical",
"urgency_score": 80,
"velocity_contrib": 2,
"detection_set": [
"http://server_url.com/api/v3.3/detections/1935",
"http://server_url.com/api/v3.3/detections/1937"
],
"last_detection_timestamp": "2023-05-15T09:41:24Z",
"last_modified_timestamp": "2023-07-27T08:56:09Z",
"notes": [],
"attack_rating": 6,
"attack_profile": "attack1",
"sensors": [],
"state": "active",
"tags": [
"test",
"test1"
],
"url": "http://server_url.com/api/v3.3/accounts/335",
"account_type": [
"o365"
]
},
{
"id": 337,
"name": "account_name_2",
"breadth_contrib": 2,
"entity_importance": 1,
"importance": 1,
"entity_type": "account",
"type": "account",
"is_prioritized": true,
"severity": "Critical",
"urgency_score": 40,
"velocity_contrib": 2,
"detection_set": [
"http://server_url.com/api/v3.3/detections/1835",
"http://server_url.com/api/v3.3/detections/1837"
],
"last_detection_timestamp": "2023-05-15T09:40:24Z",
"last_modified_timestamp": "2023-07-27T08:56:09Z",
"notes": [],
"attack_rating": 9,
"attack_profile": "attack2",
"sensors": [],
"state": "active",
"tags": [
"test1"
],
"url": "http://server_url.com/api/v3.3/accounts/337",
"account_type": [
"aws"
]
},
{
"id": 339,
"name": "account_name_3",
"breadth_contrib": 2,
"entity_importance": 1,
"importance": 2,
"entity_type": "account",
"type": "account",
"is_prioritized": true,
"severity": "Critical",
"urgency_score": 21,
"velocity_contrib": 2,
"detection_set": [
"http://server_url.com/api/v3.3/detections/1735",
"http://server_url.com/api/v3.3/detections/1737"
],
"last_detection_timestamp": "2023-05-15T09:44:24Z",
"last_modified_timestamp": "2023-07-27T08:56:09Z",
"notes": [],
"attack_rating": 5,
"attack_profile": "attack3",
"sensors": [],
"state": "active",
"tags": [
"test"
],
"url": "http://server_url.com/api/v3.3/accounts/339",
"account_type": [
"o365"
]
}
]
}
Human Readable Output
Entities Table (Showing Page 1 out of 1)
ID Name Entity Type Urgency Score Entity Importance Last Detection Timestamp Last Modified Timestamp Detections IDs Prioritize State Tags 334 account_name account 100 High 2023-05-15T09:39:24Z 2023-07-18T09:44:24Z 1933, 1934 true active test 335 account_name_1 account 80 High 2023-05-15T09:41:24Z 2023-07-17T09:44:24Z 1935, 1937 true active test, test1 337 account_name_2 account 40 Medium 2023-05-15T09:40:24Z 2023-07-16T09:44:24Z 1835, 1837 true active test1 339 account_name_3 account 21 High 2023-05-15T09:44:24Z 2023-07-15T09:44:24Z 1735, 1737 true active test
vectra-entity-describe
Describes an entity by ID.
Base Command
vectra-entity-describe
Input
| Argument Name | Description | Required |
|---|---|---|
| entity_id | Specify the id of the entity. | Required |
| entity_type | Specify the type of the entity. Possible values are: host, account. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.Entity.id | Number | ID of the entity. |
| Vectra.Entity.name | String | Name of the entity. |
| Vectra.Entity.breadth_contrib | Number | Breadth contribution of the entity. |
| Vectra.Entity.importance | Number | Entity importance. |
| Vectra.Entity.type | String | Type of the entity. |
| Vectra.Entity.is_prioritized | Boolean | Entity is prioritized or not. |
| Vectra.Entity.severity | String | Severity of the entity. |
| Vectra.Entity.urgency_score | Number | Urgency score of the entity. |
| Vectra.Entity.velocity_contrib | Number | Velocity contribution of the entity. |
| Vectra.Entity.detection_set | String | Set of detections related to the entity. |
| Vectra.Entity.last_detection_timestamp | Date | Time of the last detection activity related to the entity. |
| Vectra.Entity.last_modified_timestamp | Date | Time of the last modification activity related to the entity. |
| Vectra.Entity.notes.id | String | Notes of the entity. |
| Vectra.Entity.notes.dateCreated | String | Created date of the Note. |
| Vectra.Entity.notes.dateModified | String | Modified date of the Note. |
| Vectra.Entity.notes.createdBy | String | Created user of the Note. |
| Vectra.Entity.notes.ModifiedBy | String | Modified user of the Note. |
| Vectra.Entity.notes.note | String | Note of the entity. |
| Vectra.Entity.attack_rating | Number | Attack Ratting of the entity. |
| Vectra.Entity.privilege_level | String | Privilege Level of the entity. |
| Vectra.Entity.privilege_category | String | Privilege Category of the entity. |
| Vectra.Entity.attack_profile | String | Attack Profile of the entity. |
| Vectra.Entity.sensors | Unknown | Sensors of the entity. |
| Vectra.Entity.state | String | State of the entity. |
| Vectra.Entity.tags | Unknown | Tags of the entity. |
| Vectra.Entity.url | String | Url link of the entity. |
| Vectra.Entity.host_type | Unknown | Host type of the entity. |
| Vectra.Entity.account_type | Unknown | Account type of the entity. |
Command example
!vectra-entity-describe entity_type=account entity_id=334
Context Example
{
"id": 334,
"name": "account_name",
"breadth_contrib": 2,
"entity_importance": 1,
"importance": 2,
"entity_type": "account",
"type": "account",
"is_prioritized": true,
"severity": "Critical",
"urgency_score": 100,
"velocity_contrib": 2,
"detection_set": [
"http://server_url.com/api/v3.3/detections/1933",
"http://server_url.com/api/v3.3/detections/1934"
],
"last_detection_timestamp": "2023-05-15T09:39:24Z",
"last_modified_timestamp": "2023-07-28T05:25:47Z",
"notes": [],
"attack_rating": 10,
"attack_profile": "test_attack",
"sensors": [
"test"
],
"state": "active",
"tags": [
"test"
],
"url": "http://server_url.com/api/v3.3/accounts/334",
"account_type": [
"o365"
]
}
}
Human Readable Output
Entity detail
Entity ID: 334
Name Entity Type Urgency Score Entity Importance Last Detection Timestamp Last Modified Timestamp Detections IDs Prioritize State Tags account_name account 100 High 2023-05-15T09:39:24Z 2023-07-28T05:25:47Z 1933, 1934 true active test
vectra-entity-detection-list
Returns a list of detections for a specified entity.
Base Command
vectra-entity-detection-list
Input
| Argument Name | Description | Required |
|---|---|---|
| entity_id | Specify the id of the entity. | Required |
| entity_type | Specify the type of the entity. Possible values are: account, host. | Required |
| page | Enables the caller to specify a particular page of results. Default is 1. | Optional |
| page_size | Specify the desired page size for the request. Maximum is 5000. Default is 50. | Optional |
| detection_category | The category of the detection. Possible values are: Command & Control, Botnet, Reconnaissance, Lateral Movement, Exfiltration, Info. | Optional |
| detection_type | Filter by detection type. | Optional |
| last_timestamp | Return only the detections which have a last timestamp equal to or after the given timestamp. Formats: YYYY-MM-ddTHH:mm:ssZ, YYYY-MM-dd, N days, N hours. Example: 2023-04-25T00:00:00Z, 2023-04-25, 2 days, 5 hours, 01 Mar 2023, 01 Feb 2023 04:45:33, 15 Jun. |
Optional |
| detection_name | Filter by detection name. | Optional |
| state | Filter by state. Default is active. | Optional |
| tags | Filter by a tag or a comma-separated list of tags. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.Entity.Detections.id | Number | Entity detection ID. |
| Vectra.Entity.Detections.assigned_date | Unknown | Date assigned to the detection. |
| Vectra.Entity.Detections.assigned_to | Unknown | User or entity assigned to the detection. |
| Vectra.Entity.Detections.category | String | Category of the detection. |
| Vectra.Entity.Detections.certainty | Number | Certainty level of the detection. |
| Vectra.Entity.Detections.c_score | Number | Confidence score of the detection. |
| Vectra.Entity.Detections.description | String | Description of the detection. |
| Vectra.Entity.Detections.detection | String | Detection information. |
| Vectra.Entity.Detections.detection_category | String | Category of the detection. |
| Vectra.Entity.Detections.detection_type | String | Type of the detection. |
| Vectra.Entity.Detections.grouped_details.external_target.ip | String | IP address of the external target in the detection group. |
| Vectra.Entity.Detections.grouped_details.external_target.name | String | Name of the external target in the detection group. |
| Vectra.Entity.Detections.grouped_details.num_sessions | Number | Number of sessions in the detection group. |
| Vectra.Entity.Detections.grouped_details.bytes_received | Number | Total bytes received in the detection group. |
| Vectra.Entity.Detections.grouped_details.bytes_sent | Number | Total bytes sent in the detection group. |
| Vectra.Entity.Detections.grouped_details.ja3_hashes | String | JA3 hashes in the detection group. |
| Vectra.Entity.Detections.grouped_details.ja3s_hashes | String | JA3S hashes in the detection group. |
| Vectra.Entity.Detections.grouped_details.sessions.tunnel_type | String | Tunnel type used in the sessions of the detection group. |
| Vectra.Entity.Detections.grouped_details.sessions.protocol | String | Protocol used in the sessions of the detection group. |
| Vectra.Entity.Detections.grouped_details.sessions.app_protocol | String | Application protocol used in the sessions of the detection group. |
| Vectra.Entity.Detections.grouped_details.sessions.dst_port | Number | Destination port in the sessions of the detection group. |
| Vectra.Entity.Detections.grouped_details.sessions.dst_ip | String | Destination IP address in the sessions of the detection group. |
| Vectra.Entity.Detections.grouped_details.sessions.bytes_received | Number | Total bytes received in the sessions of the detection group. |
| Vectra.Entity.Detections.grouped_details.sessions.bytes_sent | Number | Total bytes sent in the sessions of the detection group. |
| Vectra.Entity.Detections.grouped_details.sessions.first_timestamp | Date | First timestamp of the sessions in the detection group. |
| Vectra.Entity.Detections.grouped_details.sessions.last_timestamp | Date | Last timestamp of the sessions in the detection group. |
| Vectra.Entity.Detections.grouped_details.sessions.dst_geo | Unknown | Geolocation of the destination IP in the sessions of the detection group. |
| Vectra.Entity.Detections.grouped_details.sessions.dst_geo_lat | Unknown | Latitude of the destination IP in the sessions of the detection group. |
| Vectra.Entity.Detections.grouped_details.sessions.dst_geo_lon | Unknown | Longitude of the destination IP in the sessions of the detection group. |
| Vectra.Entity.Detections.grouped_details.first_timestamp | Date | First timestamp of the detection group. |
| Vectra.Entity.Detections.grouped_details.last_timestamp | Date | Last timestamp of the detection group. |
| Vectra.Entity.Detections.grouped_details.dst_ips | String | Destination IP addresses in the detection group. |
| Vectra.Entity.Detections.grouped_details.dst_ports | Number | Destination ports in the detection group. |
| Vectra.Entity.Detections.grouped_details.target_domains | String | Target domains in the detection group. |
| Vectra.Entity.Detections.is_targeting_key_asset | Boolean | Indicates if the detection is targeting a key asset. |
| Vectra.Entity.Detections.last_timestamp | Date | Last timestamp of the detection. |
| Vectra.Entity.Detections.note | Unknown | Note associated with the detection. |
| Vectra.Entity.Detections.note_modified_by | Unknown | User or entity who last modified the note. |
| Vectra.Entity.Detections.note_modified_timestamp | Unknown | Timestamp when the note was last modified. |
| Vectra.Entity.Detections.notes | Unknown | Additional notes related to the detection. |
| Vectra.Entity.Detections.sensor_name | String | Name of the sensor associated with the detection. |
| Vectra.Entity.Detections.src_account.id | Number | ID of the source account associated with the detection. |
| Vectra.Entity.Detections.src_account.name | String | Name of the source account associated with the detection. |
| Vectra.Entity.Detections.src_account.url | String | URL of the source account associated with the detection. |
| Vectra.Entity.Detections.src_account.threat | Number | Threat level of the source account associated with the detection. |
| Vectra.Entity.Detections.src_account.certainty | Number | Certainty level of the source account associated with the detection. |
| Vectra.Entity.Detections.src_account.privilege_level | Number | Privilege level of the source account associated with the detection. |
| Vectra.Entity.Detections.src_account.privilege_category | String | Privilege category of the source account associated with the detection. |
| Vectra.Entity.Detections.src_host.id | Number | ID of the source host in the detection. |
| Vectra.Entity.Detections.src_host.ip | String | IP address of the source host in the detection. |
| Vectra.Entity.Detections.src_host.name | String | Name of the source host in the detection. |
| Vectra.Entity.Detections.src_host.url | String | URL associated with the source host in the detection. |
| Vectra.Entity.Detections.src_host.is_key_asset | Boolean | Indicates if the source host is a key asset. |
| Vectra.Entity.Detections.src_host.groups | Unknown | Groups associated with the source host in the detection. |
| Vectra.Entity.Detections.src_host.threat | Number | Threat level associated with the source host in the detection. |
| Vectra.Entity.Detections.src_host.certainty | Number | Certainty level associated with the source host in the detection. |
| Vectra.Entity.Detections.src_ip | String | Source IP address in the detection. |
| Vectra.Entity.Detections.state | String | State of the detection. |
| Vectra.Entity.Detections.summary.bytes_received | Number | Total bytes received in the detection summary. |
| Vectra.Entity.Detections.summary.bytes_sent | Number | Total bytes sent in the detection summary. |
| Vectra.Entity.Detections.summary.cnc_server | String | CNC server associated with the detection summary. |
| Vectra.Entity.Detections.summary.num_events | Number | Total number of events related to the detection. |
| Vectra.Entity.Detections.summary.probable_owner | Unknown | Probable owner of the detection summary. |
| Vectra.Entity.Detections.summary.sessions | Number | Total sessions in the detection summary. |
| Vectra.Entity.Detections.tags | Unknown | Tags associated with the detection. |
| Vectra.Entity.Detections.threat | Number | Threat level of the detection. |
| Vectra.Entity.Detections.t_score | Number | T-score of the detection. |
| Vectra.Entity.Detections.type | String | Type of the detection. |
| Vectra.Entity.Detections.url | String | URL associated with the detection. |
Command example
!vectra-entity-detection-list entity_id=1
Context Example
{
[
{
"id": 132,
"category": "exfiltration",
"certainty": 70,
"c_score": 70,
"description": "",
"detection": "Data Smuggler",
"detection_category": "exfiltration",
"detection_type": "smuggler",
"grouped_details": [
{
"event_id": "ec2162c7-e526-4446-a549-71558743a1d7",
"event_name": "UpdateAssumeRolePolicy",
"aws_account_id": "aws_account_id",
"src_external_host": {
"ip": "0.0.0.0"
},
"aws_region": "us-east-1",
"access_key_id": [
"123456"
],
"identity_type": "Federated Account",
"assumed_role": "AWSReservedSSO_AdministratorAccess_a670eb90f07e2960",
"request_parameters": [
"{\"roleName\": \"stratus-red-team-backdoor-r-role\", \"policyDocument\": \"{\\\"Version\\\": \\\"2012-10-17\\\", \\\"Statement\\\": {\\\"Effect\\\": \\\"Allow\\\", \\\"Principal\\\": {\\\"AWS\\\": \\\"arn:aws:iam::123456789012:root\\\"}, \\\"Action\\\": \\\"sts:AssumeRole\\\"}}\"}"
],
"response_elements": [],
"role_sequence": [
"account_id",
"AWSReservedSSO_AdministratorAccess_a670eb90f07e2960"
],
"user_agent": [
"stratus-red-team_06e15b96-ee6b-482c-aff4-4f2f4a46a67c"
],
"last_timestamp": "2023-06-06T17:01:04Z"
},
{
"event_id": "89a098eb-1198-4e2a-9fa4-ef568ae39403",
"event_name": "UpdateAssumeRolePolicy",
"aws_account_id": "884414556547",
"src_external_host": {
"ip": "0.0.0.0"
},
"aws_region": "us-east-1",
"access_key_id": [
"123456"
],
"identity_type": "Federated Account",
"assumed_role": "AWSReservedSSO_AdministratorAccess_a670eb90f07e2960",
"request_parameters": [
"{\"policyDocument\": \"{\\\"Version\\\": \\\"2012-10-17\\\", \\\"Statement\\\": {\\\"Effect\\\": \\\"Allow\\\", \\\"Principal\\\": {\\\"AWS\\\": \\\"arn:aws:iam::123456789012:root\\\"}, \\\"Action\\\": \\\"sts:AssumeRole\\\"}}\", \"roleName\": \"stratus-red-team-backdoor-r-role\"}"
],
"response_elements": [],
"role_sequence": [
"account_name",
"AWSReservedSSO_AdministratorAccess_a670eb90f07e2960"
],
"user_agent": [
"stratus-red-team_a24eac3a-4fee-46ce-bc37-b4e675343fc9"
],
"last_timestamp": "2023-06-06T15:40:43Z"
}
],
"is_targeting_key_asset": false,
"last_timestamp": "2023-06-06T17:01:04Z",
"notes": [],
"sensor_name": "mafosb50",
"src_account": {
"id": 21,
"name": "account_name",
"url": "http://server_url.com/api/v3.3/accounts/21",
"threat": 76,
"certainty": 35
},
"src_ip": "0.0.0.0",
"state": "active",
"tags": [],
"threat": 80,
"t_score": 80,
"type": "account",
"url": "http://server_url.com/api/v3.3/detections/132"
},
{
"id": 135,
"category": "lateral_movement",
"certainty": 50,
"c_score": 50,
"description": "",
"detection": "AWS Suspect Admin Privilege Granting",
"detection_category": "lateral_movement",
"detection_type": "aws_admin_privilege_granted",
"grouped_details": [
{
"event_id": "85d88db5-cf2d-4b6e-9411-d3119d9920e0",
"event_name": "AttachRolePolicy",
"aws_account_id": "884414556547",
"src_external_host": {
"ip": "0.0.0.0"
},
"aws_region": "us-east-1",
"access_key_id": [
"123456"
],
"identity_type": "Federated Account",
"assumed_role": "AWSReservedSSO_AdministratorAccess_a670eb90f07e2960",
"request_parameters": [
"{\"roleName\":\"stratus-red-team-backdoor-r-role\",\"policyArn\":\"arn:aws:iam::aws:policy/AdministratorAccess\"}"
],
"response_elements": [],
"role_sequence": [
"account_name",
"AWSReservedSSO_AdministratorAccess_a670eb90f07e2960"
],
"user_agent": [
"APN/1.0 HashiCorp/1.0 Terraform/1.1.2 (+https://www.terraform.io) terraform-provider-aws/3.76.1 (+https://registry.terraform.io/providers/hashicorp/aws) aws-sdk-go/1.44.157 (go1.19.3; linux; amd64) stratus-red-team_5077134d-32ea-4403-996b-de30d7f278d7 HashiCorp-terraform-exec/0.17.3"
],
"last_timestamp": "2023-06-06T17:00:46Z"
},
{
"event_id": "ca157e7c-9a53-4012-9288-e6ac1c488fbc",
"event_name": "AttachRolePolicy",
"aws_account_id": "884414556547",
"src_external_host": {
"ip": "0.0.0.0"
},
"aws_region": "us-east-1",
"access_key_id": [
"123456"
],
"identity_type": "Federated Account",
"assumed_role": "AWSReservedSSO_AdministratorAccess_a670eb90f07e2960",
"request_parameters": [
"{\"roleName\":\"stratus-red-team-backdoor-r-role\",\"policyArn\":\"arn:aws:iam::aws:policy/AdministratorAccess\"}"
],
"response_elements": [],
"role_sequence": [
"account_name",
"AWSReservedSSO_AdministratorAccess_a670eb90f07e2960"
],
"user_agent": [
"APN/1.0 HashiCorp/1.0 Terraform/1.1.2 (+https://www.terraform.io) terraform-provider-aws/3.76.1 (+https://registry.terraform.io/providers/hashicorp/aws) aws-sdk-go/1.44.157 (go1.19.3; linux; amd64) stratus-red-team_01be8427-d1b5-4c18-8edb-0301c8e66c8e HashiCorp-terraform-exec/0.17.3"
],
"last_timestamp": "2023-06-06T15:40:07Z"
}
],
"is_targeting_key_asset": false,
"last_timestamp": "2023-06-06T17:00:46Z",
"notes": [],
"sensor_name": "mafosb50",
"src_account": {
"id": 21,
"name": "account_name",
"url": "http://server_url.com/api/v3.3/accounts/21",
"threat": 76,
"certainty": 35
},
"src_ip": "0.0.0.0",
"state": "fixed",
"summary": {
},
"tags": [],
"threat": 60,
"t_score": 60,
"type": "account",
"url": "http://server_url.com/api/v3.3/detections/135"
},
{
"id": 140,
"category": "reconnaissance",
"certainty": 40,
"c_score": 40,
"description": "",
"detection": "RPC Targeted Recon",
"detection_category": "reconnaissance",
"detection_type": "rpc_recon_1to1",
"grouped_details": [
{
"event_id": "cf9f469b-0a8e-47c6-85eb-5a0486292e58",
"event_name": "ModifySnapshotAttribute",
"aws_account_id": "884414556547",
"src_external_host": {
"ip": "0.0.0.0"
},
"aws_region": "us-west-2",
"access_key_id": [
"123456"
],
"identity_type": "Federated Account",
"assumed_role": "AWSReservedSSO_AdministratorAccess_a670eb90f07e2960",
"request_parameters": [
"{\"snapshotId\":\"snap-0f7d022a2f4f67e08\",\"createVolumePermission\":{\"add\":{\"items\":[{\"userId\":\"012345678912\"}]}},\"attributeType\":\"CREATE_VOLUME_PERMISSION\"}"
],
"response_elements": [
"{\"requestId\":\"350c1eb8-b696-4d94-88d4-a764a0eed08b\",\"_return\":true}"
],
"role_sequence": [
"account_name",
"AWSReservedSSO_AdministratorAccess_a670eb90f07e2960"
],
"user_agent": [
"stratus-red-team_a4dd596b-7a8d-4e77-a74d-13f19adf4403"
],
"last_timestamp": "2023-06-06T15:46:28Z"
}
],
"is_targeting_key_asset": false,
"last_timestamp": "2023-06-06T15:46:28Z",
"notes": [],
"sensor_name": "mafosb50",
"src_account": {
"id": 21,
"name": "account_name",
"url": "http://server_url.com/api/v3.3/accounts/21",
"threat": 76,
"certainty": 35
},
"src_ip": "0.0.0.0",
"state": "fixed",
"summary": {
},
"tags": [],
"threat": 60,
"t_score": 60,
"type": "account",
"url": "http://server_url.com/api/v3.3/detections/140"
}
]
}
Human Readable Output
Detections Table (Showing Page 1 out of 1)
ID Detection Name Detection Type Category Account Name Src IP Threat Score Certainty Score Number Of Events State Last Timestamp 132 Data Smuggler smuggler exfiltration account_name 0.0.0.0 80 70 0 active 2023-06-06T17:01:04Z 135 AWS Suspect Admin Privilege Granting aws_admin_privilege_granted lateral_movement account_name 0.0.0.0 60 50 0 fixed 2023-06-06T17:00:46Z 140 RPC Targeted Recon rpc_recon_1to1 reconnaissance account_name 0.0.0.0 60 40 0 fixed 2023-06-06T15:46:28Z
vectra-detection-describe
Returns a list of detections for the specified detection ID(s).
Base Command
vectra-detection-describe
Input
| Argument Name | Description | Required |
|---|---|---|
| detection_ids | Specify the ID(s) of the detections. | Required |
| page | Enables the caller to specify a particular page of results. Default is 1. | Optional |
| page_size | Specify the desired page size for the request. Maximum is 5000. Default is 50. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.Entity.Detections.id | Number | Entity detection ID. |
| Vectra.Entity.Detections.assigned_date | Unknown | Date assigned to the detection. |
| Vectra.Entity.Detections.assigned_to | Unknown | User or entity assigned to the detection. |
| Vectra.Entity.Detections.category | String | Category of the detection. |
| Vectra.Entity.Detections.certainty | Number | Certainty level of the detection. |
| Vectra.Entity.Detections.c_score | Number | Confidence score of the detection. |
| Vectra.Entity.Detections.description | String | Description of the detection. |
| Vectra.Entity.Detections.detection | String | Detection information. |
| Vectra.Entity.Detections.detection_category | String | Category of the detection. |
| Vectra.Entity.Detections.detection_type | String | Type of the detection. |
| Vectra.Entity.Detections.grouped_details.external_target.ip | String | IP address of the external target in the detection group. |
| Vectra.Entity.Detections.grouped_details.external_target.name | String | Name of the external target in the detection group. |
| Vectra.Entity.Detections.grouped_details.num_sessions | Number | Number of sessions in the detection group. |
| Vectra.Entity.Detections.grouped_details.bytes_received | Number | Total bytes received in the detection group. |
| Vectra.Entity.Detections.grouped_details.bytes_sent | Number | Total bytes sent in the detection group. |
| Vectra.Entity.Detections.grouped_details.ja3_hashes | String | JA3 hashes in the detection group. |
| Vectra.Entity.Detections.grouped_details.ja3s_hashes | String | JA3S hashes in the detection group. |
| Vectra.Entity.Detections.grouped_details.sessions.tunnel_type | String | Tunnel type used in the sessions of the detection group. |
| Vectra.Entity.Detections.grouped_details.sessions.protocol | String | Protocol used in the sessions of the detection group. |
| Vectra.Entity.Detections.grouped_details.sessions.app_protocol | String | Application protocol used in the sessions of the detection group. |
| Vectra.Entity.Detections.grouped_details.sessions.dst_port | Number | Destination port in the sessions of the detection group. |
| Vectra.Entity.Detections.grouped_details.sessions.dst_ip | String | Destination IP address in the sessions of the detection group. |
| Vectra.Entity.Detections.grouped_details.sessions.bytes_received | Number | Total bytes received in the sessions of the detection group. |
| Vectra.Entity.Detections.grouped_details.sessions.bytes_sent | Number | Total bytes sent in the sessions of the detection group. |
| Vectra.Entity.Detections.grouped_details.sessions.first_timestamp | Date | First timestamp of the sessions in the detection group. |
| Vectra.Entity.Detections.grouped_details.sessions.last_timestamp | Date | Last timestamp of the sessions in the detection group. |
| Vectra.Entity.Detections.grouped_details.sessions.dst_geo | Unknown | Geolocation of the destination IP in the sessions of the detection group. |
| Vectra.Entity.Detections.grouped_details.sessions.dst_geo_lat | Unknown | Latitude of the destination IP in the sessions of the detection group. |
| Vectra.Entity.Detections.grouped_details.sessions.dst_geo_lon | Unknown | Longitude of the destination IP in the sessions of the detection group. |
| Vectra.Entity.Detections.grouped_details.first_timestamp | Date | First timestamp of the detection group. |
| Vectra.Entity.Detections.grouped_details.last_timestamp | Date | Last timestamp of the detection group. |
| Vectra.Entity.Detections.grouped_details.dst_ips | String | Destination IP addresses in the detection group. |
| Vectra.Entity.Detections.grouped_details.dst_ports | Number | Destination ports in the detection group. |
| Vectra.Entity.Detections.grouped_details.target_domains | String | Target domains in the detection group. |
| Vectra.Entity.Detections.is_targeting_key_asset | Boolean | Indicates if the detection is targeting a key asset. |
| Vectra.Entity.Detections.last_timestamp | Date | Last timestamp of the detection. |
| Vectra.Entity.Detections.note | Unknown | Note associated with the detection. |
| Vectra.Entity.Detections.note_modified_by | Unknown | User or entity who last modified the note. |
| Vectra.Entity.Detections.note_modified_timestamp | Unknown | Timestamp when the note was last modified. |
| Vectra.Entity.Detections.notes | Unknown | Additional notes related to the detection. |
| Vectra.Entity.Detections.sensor_name | String | Name of the sensor associated with the detection. |
| Vectra.Entity.Detections.src_account.id | Number | ID of the source account associated with the detection. |
| Vectra.Entity.Detections.src_account.name | String | Name of the source account associated with the detection. |
| Vectra.Entity.Detections.src_account.url | String | URL of the source account associated with the detection. |
| Vectra.Entity.Detections.src_account.threat | Number | Threat level of the source account associated with the detection. |
| Vectra.Entity.Detections.src_account.certainty | Number | Certainty level of the source account associated with the detection. |
| Vectra.Entity.Detections.src_account.privilege_level | Number | Privilege level of the source account associated with the detection. |
| Vectra.Entity.Detections.src_account.privilege_category | String | Privilege category of the source account associated with the detection. |
| Vectra.Entity.Detections.src_host.id | Number | ID of the source host in the detection. |
| Vectra.Entity.Detections.src_host.ip | String | IP address of the source host in the detection. |
| Vectra.Entity.Detections.src_host.name | String | Name of the source host in the detection. |
| Vectra.Entity.Detections.src_host.url | String | URL associated with the source host in the detection. |
| Vectra.Entity.Detections.src_host.is_key_asset | Boolean | Indicates if the source host is a key asset. |
| Vectra.Entity.Detections.src_host.groups | Unknown | Groups associated with the source host in the detection. |
| Vectra.Entity.Detections.src_host.threat | Number | Threat level associated with the source host in the detection. |
| Vectra.Entity.Detections.src_host.certainty | Number | Certainty level associated with the source host in the detection. |
| Vectra.Entity.Detections.src_ip | String | Source IP address in the detection. |
| Vectra.Entity.Detections.state | String | State of the detection. |
| Vectra.Entity.Detections.summary.bytes_received | Number | Total bytes received in the detection summary. |
| Vectra.Entity.Detections.summary.bytes_sent | Number | Total bytes sent in the detection summary. |
| Vectra.Entity.Detections.summary.cnc_server | String | CNC server associated with the detection summary. |
| Vectra.Entity.Detections.summary.num_events | Number | Total number of events related to the detection. |
| Vectra.Entity.Detections.summary.probable_owner | Unknown | Probable owner of the detection summary. |
| Vectra.Entity.Detections.summary.sessions | Number | Total sessions in the detection summary. |
| Vectra.Entity.Detections.tags | Unknown | Tags associated with the detection. |
| Vectra.Entity.Detections.threat | Number | Threat level of the detection. |
| Vectra.Entity.Detections.t_score | Number | T-score of the detection. |
| Vectra.Entity.Detections.type | String | Type of the detection. |
| Vectra.Entity.Detections.url | String | URL associated with the detection. |
Command example
!vectra-detection-describe detection_ids=132,135,140
Context Example
{
[
{
"id": 132,
"category": "exfiltration",
"certainty": 70,
"c_score": 70,
"description": "",
"detection": "Data Smuggler",
"detection_category": "exfiltration",
"detection_type": "smuggler",
"grouped_details": [
{
"event_id": "ec2162c7-e526-4446-a549-71558743a1d7",
"event_name": "UpdateAssumeRolePolicy",
"aws_account_id": "aws_account_id",
"src_external_host": {
"ip": "0.0.0.0"
},
"aws_region": "us-east-1",
"access_key_id": [
"123456"
],
"identity_type": "Federated Account",
"assumed_role": "AWSReservedSSO_AdministratorAccess_a670eb90f07e2960",
"request_parameters": [
"{\"roleName\": \"stratus-red-team-backdoor-r-role\", \"policyDocument\": \"{\\\"Version\\\": \\\"2012-10-17\\\", \\\"Statement\\\": {\\\"Effect\\\": \\\"Allow\\\", \\\"Principal\\\": {\\\"AWS\\\": \\\"arn:aws:iam::123456789012:root\\\"}, \\\"Action\\\": \\\"sts:AssumeRole\\\"}}\"}"
],
"response_elements": [],
"role_sequence": [
"account_id",
"AWSReservedSSO_AdministratorAccess_a670eb90f07e2960"
],
"user_agent": [
"stratus-red-team_06e15b96-ee6b-482c-aff4-4f2f4a46a67c"
],
"last_timestamp": "2023-06-06T17:01:04Z"
},
{
"event_id": "89a098eb-1198-4e2a-9fa4-ef568ae39403",
"event_name": "UpdateAssumeRolePolicy",
"aws_account_id": "884414556547",
"src_external_host": {
"ip": "0.0.0.0"
},
"aws_region": "us-east-1",
"access_key_id": [
"123456"
],
"identity_type": "Federated Account",
"assumed_role": "AWSReservedSSO_AdministratorAccess_a670eb90f07e2960",
"request_parameters": [
"{\"policyDocument\": \"{\\\"Version\\\": \\\"2012-10-17\\\", \\\"Statement\\\": {\\\"Effect\\\": \\\"Allow\\\", \\\"Principal\\\": {\\\"AWS\\\": \\\"arn:aws:iam::123456789012:root\\\"}, \\\"Action\\\": \\\"sts:AssumeRole\\\"}}\", \"roleName\": \"stratus-red-team-backdoor-r-role\"}"
],
"response_elements": [],
"role_sequence": [
"account_name",
"AWSReservedSSO_AdministratorAccess_a670eb90f07e2960"
],
"user_agent": [
"stratus-red-team_a24eac3a-4fee-46ce-bc37-b4e675343fc9"
],
"last_timestamp": "2023-06-06T15:40:43Z"
}
],
"is_targeting_key_asset": false,
"last_timestamp": "2023-06-06T17:01:04Z",
"notes": [],
"sensor_name": "mafosb50",
"src_account": {
"id": 21,
"name": "account_name",
"url": "http://server_url.com/api/v3.3/accounts/21",
"threat": 76,
"certainty": 35
},
"src_ip": "0.0.0.0",
"state": "active",
"summary": {
},
"tags": [],
"threat": 80,
"t_score": 80,
"type": "account",
"url": "http://server_url.com/api/v3.3/detections/132"
},
{
"id": 135,
"category": "lateral_movement",
"certainty": 50,
"c_score": 50,
"description": "",
"detection": "AWS Suspect Admin Privilege Granting",
"detection_category": "lateral_movement",
"detection_type": "aws_admin_privilege_granted",
"grouped_details": [
{
"event_id": "85d88db5-cf2d-4b6e-9411-d3119d9920e0",
"event_name": "AttachRolePolicy",
"aws_account_id": "884414556547",
"src_external_host": {
"ip": "0.0.0.0"
},
"aws_region": "us-east-1",
"access_key_id": [
"123456"
],
"identity_type": "Federated Account",
"assumed_role": "AWSReservedSSO_AdministratorAccess_a670eb90f07e2960",
"request_parameters": [
"{\"roleName\":\"stratus-red-team-backdoor-r-role\",\"policyArn\":\"arn:aws:iam::aws:policy/AdministratorAccess\"}"
],
"response_elements": [],
"role_sequence": [
"account_name",
"AWSReservedSSO_AdministratorAccess_a670eb90f07e2960"
],
"user_agent": [
"APN/1.0 HashiCorp/1.0 Terraform/1.1.2 (+https://www.terraform.io) terraform-provider-aws/3.76.1 (+https://registry.terraform.io/providers/hashicorp/aws) aws-sdk-go/1.44.157 (go1.19.3; linux; amd64) stratus-red-team_5077134d-32ea-4403-996b-de30d7f278d7 HashiCorp-terraform-exec/0.17.3"
],
"last_timestamp": "2023-06-06T17:00:46Z"
},
{
"event_id": "ca157e7c-9a53-4012-9288-e6ac1c488fbc",
"event_name": "AttachRolePolicy",
"aws_account_id": "884414556547",
"src_external_host": {
"ip": "0.0.0.0"
},
"aws_region": "us-east-1",
"access_key_id": [
"123456"
],
"identity_type": "Federated Account",
"assumed_role": "AWSReservedSSO_AdministratorAccess_a670eb90f07e2960",
"request_parameters": [
"{\"roleName\":\"stratus-red-team-backdoor-r-role\",\"policyArn\":\"arn:aws:iam::aws:policy/AdministratorAccess\"}"
],
"response_elements": [],
"role_sequence": [
"account_name",
"AWSReservedSSO_AdministratorAccess_a670eb90f07e2960"
],
"user_agent": [
"APN/1.0 HashiCorp/1.0 Terraform/1.1.2 (+https://www.terraform.io) terraform-provider-aws/3.76.1 (+https://registry.terraform.io/providers/hashicorp/aws) aws-sdk-go/1.44.157 (go1.19.3; linux; amd64) stratus-red-team_01be8427-d1b5-4c18-8edb-0301c8e66c8e HashiCorp-terraform-exec/0.17.3"
],
"last_timestamp": "2023-06-06T15:40:07Z"
}
],
"is_targeting_key_asset": false,
"last_timestamp": "2023-06-06T17:00:46Z",
"notes": [],
"sensor_name": "mafosb50",
"src_account": {
"id": 21,
"name": "account_name",
"url": "http://server_url.com/api/v3.3/accounts/21",
"threat": 76,
"certainty": 35
},
"src_ip": "0.0.0.0",
"state": "fixed",
"summary": {
},
"tags": [],
"threat": 60,
"t_score": 60,
"type": "account",
"url": "http://server_url.com/api/v3.3/detections/135"
},
{
"id": 140,
"category": "reconnaissance",
"certainty": 40,
"c_score": 40,
"description": "",
"detection": "RPC Targeted Recon",
"detection_category": "reconnaissance",
"detection_type": "rpc_recon_1to1",
"grouped_details": [
{
"event_id": "cf9f469b-0a8e-47c6-85eb-5a0486292e58",
"event_name": "ModifySnapshotAttribute",
"aws_account_id": "884414556547",
"src_external_host": {
"ip": "0.0.0.0"
},
"aws_region": "us-west-2",
"access_key_id": [
"123456"
],
"identity_type": "Federated Account",
"assumed_role": "AWSReservedSSO_AdministratorAccess_a670eb90f07e2960",
"request_parameters": [
"{\"snapshotId\":\"snap-0f7d022a2f4f67e08\",\"createVolumePermission\":{\"add\":{\"items\":[{\"userId\":\"012345678912\"}]}},\"attributeType\":\"CREATE_VOLUME_PERMISSION\"}"
],
"response_elements": [
"{\"requestId\":\"350c1eb8-b696-4d94-88d4-a764a0eed08b\",\"_return\":true}"
],
"role_sequence": [
"account_name",
"AWSReservedSSO_AdministratorAccess_a670eb90f07e2960"
],
"user_agent": [
"stratus-red-team_a4dd596b-7a8d-4e77-a74d-13f19adf4403"
],
"last_timestamp": "2023-06-06T15:46:28Z"
}
],
"is_targeting_key_asset": false,
"last_timestamp": "2023-06-06T15:46:28Z",
"notes": [],
"sensor_name": "mafosb50",
"src_account": {
"id": 21,
"name": "account_name",
"url": "http://server_url.com/api/v3.3/accounts/21",
"threat": 76,
"certainty": 35
},
"src_ip": "0.0.0.0",
"state": "fixed",
"summary": {
},
"tags": [],
"threat": 60,
"t_score": 60,
"type": "account",
"url": "http://server_url.com/api/v3.3/detections/140"
}
]
}
Human Readable Output
Detections Table (Showing Page 1 out of 1)
ID Detection Name Detection Type Category Account Name Src IP Threat Score Certainty Score Number Of Events State Last Timestamp 132 Data Smuggler smuggler exfiltration account_name 0.0.0.0 80 70 0 active 2023-06-06T17:01:04Z 135 AWS Suspect Admin Privilege Granting aws_admin_privilege_granted lateral_movement account_name 0.0.0.0 60 50 0 fixed 2023-06-06T17:00:46Z 140 RPC Targeted Recon rpc_recon_1to1 reconnaissance account_name 0.0.0.0 60 40 0 fixed 2023-06-06T15:46:28Z
vectra-entity-note-add
Add a note to the entity.
Base Command
vectra-entity-note-add
Input
| Argument Name | Description | Required |
|---|---|---|
| entity_id | Specify the id of the entity. | Required |
| entity_type | Specify the type of the entity. Possible values are: account, host. | Required |
| note | Note to be added in the specified entity_id. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.Entity.Notes.entity_id | String | ID of the entity associated with the note. |
| Vectra.Entity.Notes.note_id | Number | ID of the note. |
| Vectra.Entity.Notes.date_created | Date | Date when the note was created. |
| Vectra.Entity.Notes.date_modified | Unknown | Date when the note was last modified. |
| Vectra.Entity.Notes.created_by | String | User who created the note. |
| Vectra.Entity.Notes.modified_by | Unknown | User who last modified the note. |
| Vectra.Entity.Notes.note | String | Content of the note. |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.Entity.Notes.entity_id | String | The ID of the entity associated with the note. |
| Vectra.Entity.Notes.note_id | Number | The ID of the note. |
| Vectra.Entity.Notes.date_created | Date | The date when the note was created. |
| Vectra.Entity.Notes.date_modified | Unknown | The date when the note was last modified. |
| Vectra.Entity.Notes.created_by | String | The user who created the note. |
| Vectra.Entity.Notes.modified_by | Unknown | The user who last modified the note. |
| Vectra.Entity.Notes.note | String | The content of the note. |
Command example
!vectra-entity-note-add entity_id=1 entity_type=account note="test note"
Context Example
{
{
"date_created": "2023-06-21T06:19:15.224449Z",
"created_by": "test_user",
"note": "test_note",
"note_id": 19,
"entity_id": 1
}
}
Human Readable Output
The note has been successfully added to the entity
Returned Note ID: 19
vectra-entity-note-update
Update a note in the entity.
Base Command
vectra-entity-note-update
Input
| Argument Name | Description | Required |
|---|---|---|
| entity_id | Specify the id of the entity. | Required |
| entity_type | Specify the type of the entity. Possible values are: account, host. | Required |
| note_id | Specify the ID of the note. | Required |
| note | Note to be updated for the specified note_id. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.Entity.Notes.entity_id | String | ID of the entity associated with the note. |
| Vectra.Entity.Notes.note_id | Number | ID of the note. |
| Vectra.Entity.Notes.date_created | Date | Date when the note was created. |
| Vectra.Entity.Notes.date_modified | Unknown | Date when the note was last modified. |
| Vectra.Entity.Notes.created_by | String | User who created the note. |
| Vectra.Entity.Notes.modified_by | Unknown | User who last modified the note. |
| Vectra.Entity.Notes.note | String | Content of the note. |
Command example
!vectra-entity-note-update entity_id=1 entity_type=account note_id=1 note="note modified"
Context Example
{
{
"date_created": "2023-06-16T04:55:58Z",
"date_modified": "2023-06-22T04:57:09Z",
"created_by": "test_user",
"modified_by": "test_user",
"note": "note modified",
"note_id": 8,
"entity_id": 1
}
}
Human Readable Output
The note has been successfully updated in the entity
vectra-entity-note-remove
Remove a note from the entity.
Base Command
vectra-entity-note-remove
Input
| Argument Name | Description | Required |
|---|---|---|
| entity_id | Specify the ID of the entity. | Required |
| entity_type | Specify the type of the entity. Possible values are: account, host. | Required |
| note_id | Specify the ID of the note. | Required |
Context Output
There is no context output for this command.
Command Example
!vectra-entity-note-remove entity_id=1 entity_type=account note_id=1"
Context Example
{}
Human Readable Output
The note has been successfully removed from the entity
vectra-entity-tag-add
Add tags in the entity.
Base Command
vectra-entity-tag-add
Input
| Argument Name | Description | Required |
|---|---|---|
| entity_id | Specify the id of the entity. | Required |
| entity_type | Specify the type of the entity. Possible values are: account, host. | Required |
| tags | Comma-separated values of tags to be included in the entity. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.Entity.Tags.tag_id | String | ID of the tag. |
| Vectra.Entity.Tags.entity_id | String | ID of the entity associated with the tag. |
| Vectra.Entity.Tags.entity_type | String | Type of the entity. |
| Vectra.Entity.Tags.tags | Unknown | A list of tags linked to an entity. |
Command example
!vectra-entity-tag-add entity_id=1 entity_type=host tags="tag1, tag2"
Context Example
{
{
"tag_id": "1",
"tags": [
"tag1",
"tag2"
],
"entity_type": "host",
"entity_id": 1
}
}
Human Readable Output
Tags have been successfully added to the entity
Updated list of tags: tag1, tag2
vectra-entity-tag-remove
Remove tags from the entity.
Base Command
vectra-entity-tag-remove
Input
| Argument Name | Description | Required |
|---|---|---|
| entity_id | Specify the id of the entity. | Required |
| entity_type | Specify the type of the entity. Possible values are: account, host. | Required |
| tags | Comma-separated values of tags to be removed from the entity. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.Entity.Tags.tag_id | String | ID of the tag. |
| Vectra.Entity.Tags.entity_id | String | ID of the entity associated with the tag. |
| Vectra.Entity.Tags.entity_type | String | Type of the entity. |
| Vectra.Entity.Tags.tags | Unknown | A list of tags linked to an entity. |
Command example
!vectra-entity-tag-remove entity_id=1 entity_type=host tags="tag2"
Context Example
{
{
"tag_id": "1",
"tags": ["tag1"],
"entity_type": "host",
"entity_id": 1
}
}
Human Readable Output
Specified tags have been successfully removed for the entity
Updated list of tags: tag1
vectra-entity-tag-list
Returns a list of tags for a specified entity.
Base Command
vectra-entity-tag-list
Input
| Argument Name | Description | Required |
|---|---|---|
| entity_id | Specify the id of the entity. | Required |
| entity_type | Specify the type of the entity. Possible values are: account, host. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.Entity.Tags.tag_id | String | ID of the tag. |
| Vectra.Entity.Tags.entity_id | String | ID of the entity associated with the tag. |
| Vectra.Entity.Tags.entity_type | String | Type of the entity. |
| Vectra.Entity.Tags.tags | Unknown | A list of tags linked to an entity. |
Command example
!vectra-entity-tag-list entity_id=1 entity_type=host
Context Example
{
"Vectra": {
"Entity": {
"Tags": {
"tag_id": "1",
"tags": [
"tag1",
"tag2"
],
"entity_type": "host",
"entity_id": 1
}
}
}
}
Human Readable Output
List of tags: tag1, tag2
vectra-entity-assignment-add
Add an assignment for the entity.
Base Command
vectra-entity-assignment-add
Input
| Argument Name | Description | Required |
|---|---|---|
| entity_id | Specify the ID of the entity. | Required |
| entity_type | Specify the type of the entity. Possible values are: account, host. | Required |
| user_id | Specify the ID of the user. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.Entity.Assignments.id | Number | ID of the assignment. |
| Vectra.Entity.Assignments.assignment_id | Number | ID of the assignment. |
| Vectra.Entity.Assignments.assigned_by.id | Number | ID of the user who assigned the entity. |
| Vectra.Entity.Assignments.assigned_by.username | String | Username of the user who assigned the entity. |
| Vectra.Entity.Assignments.date_assigned | Date | Date when the entity was assigned. |
| Vectra.Entity.Assignments.date_resolved | Date | Date when the entity was resolved. |
| Vectra.Entity.Assignments.events.assignment_id | Number | ID of the assignment event. |
| Vectra.Entity.Assignments.events.actor | Number | ID of the actor who performed the assignment event. |
| Vectra.Entity.Assignments.events.event_type | String | Type of assignment event. |
| Vectra.Entity.Assignments.events.datetime | Date | Date of the assignment event. |
| Vectra.Entity.Assignments.events.context.to | Number | ID of the entity that was assigned to. |
| Vectra.Entity.Assignments.events.context.entity_t_score | Number | Threat score of the entity that was assigned to. |
| Vectra.Entity.Assignments.events.context.entity_c_score | Number | Certainty score of the entity that was assigned to. |
| Vectra.Entity.Assignments.outcome.id | String | ID of the assignment outcome. |
| Vectra.Entity.Assignments.outcome.builtin | String | Whether the assignment outcome is builtin or not. |
| Vectra.Entity.Assignments.outcome.user_selectable | String | Whether the assignment outcome is user selectable or not. |
| Vectra.Entity.Assignments.outcome.title | String | Title of the assignment outcome. |
| Vectra.Entity.Assignments.outcome.category | String | Category of the assignment outcome. |
| Vectra.Entity.Assignments.resolved_by.id | Number | ID of the user who resolved the entity. |
| Vectra.Entity.Assignments.resolved_by.username | String | Username of the user who resolved the entity. |
| Vectra.Entity.Assignments.triaged_detections | Unknown | Number of detections that have been triaged for the entity. |
| Vectra.Entity.Assignments.host_id | Number | ID of the host that the entity is associated with. |
| Vectra.Entity.Assignments.account_id | Unknown | ID of the account that the entity is associated with. |
| Vectra.Entity.Assignments.assigned_to.id | Number | ID of the user who is currently assigned to the entity. |
| Vectra.Entity.Assignments.assigned_to.username | String | Username of the user who is currently assigned to the entity. |
Command Example
!vectra-entity-assignment-add entity_id=1 entity_type=account user_id=1
Context Example
{
{
"assigned_by": {
"id": 2,
"username": "test_user_2"
},
"date_assigned": "2023-07-24T08:52:59.367115Z",
"events": [
{
"assignment_id": 74,
"actor": 65,
"event_type": "created",
"datetime": "2023-07-24T08:52:59Z",
"context": {
"to": 60,
"entity_t_score": 0,
"entity_c_score": 0
}
}
],
"host_id": 10,
"assigned_to": {
"id": 1,
"username": "test.user@example.com"
},
"assignment_id": 1,
"id":1
}
}
Human Readable Output
The assignment has been successfully created
Assignment detail
Assignment ID Assigned By Assigned Date Assigned To Event Type 1 test_user_2 2023-07-24T08:52:59.367115Z test.user@example.com created
vectra-entity-assignment-update
Update an assignment in the entity.
Base Command
vectra-entity-assignment-update
Input
| Argument Name | Description | Required |
|---|---|---|
| assignment_id | Specify the ID of the assignment. | Required |
| user_id | Specify the ID of the user. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.Entity.Assignments.id | Number | ID of the assignment. |
| Vectra.Entity.Assignments.assignment_id | Number | ID of the assignment. |
| Vectra.Entity.Assignments.assigned_by.id | Number | ID of the user who assigned the entity. |
| Vectra.Entity.Assignments.assigned_by.username | String | Username of the user who assigned the entity. |
| Vectra.Entity.Assignments.date_assigned | Date | Date when the entity was assigned. |
| Vectra.Entity.Assignments.date_resolved | Date | Date when the entity was resolved. |
| Vectra.Entity.Assignments.events.assignment_id | Number | ID of the assignment event. |
| Vectra.Entity.Assignments.events.actor | Number | ID of the actor who performed the assignment event. |
| Vectra.Entity.Assignments.events.event_type | String | Type of assignment event. |
| Vectra.Entity.Assignments.events.datetime | Date | Date of the assignment event. |
| Vectra.Entity.Assignments.events.context.to | Number | ID of the entity that was assigned to. |
| Vectra.Entity.Assignments.events.context.from | Number | ID of the entity that was assigned. |
| Vectra.Entity.Assignments.events.context.entity_t_score | Number | Threat score of the entity that was assigned to. |
| Vectra.Entity.Assignments.events.context.entity_c_score | Number | Certainty score of the entity that was assigned to. |
| Vectra.Entity.Assignments.outcome.id | String | ID of the assignment outcome. |
| Vectra.Entity.Assignments.outcome.builtin | String | Whether the assignment outcome is builtin or not. |
| Vectra.Entity.Assignments.outcome.user_selectable | String | Whether the assignment outcome is user selectable or not. |
| Vectra.Entity.Assignments.outcome.title | String | Title of the assignment outcome. |
| Vectra.Entity.Assignments.outcome.category | String | Category of the assignment outcome. |
| Vectra.Entity.Assignments.resolved_by.id | Number | ID of the user who resolved the entity. |
| Vectra.Entity.Assignments.resolved_by.username | String | Username of the user who resolved the entity. |
| Vectra.Entity.Assignments.triaged_detections | Unknown | Number of detections that have been triaged for the entity. |
| Vectra.Entity.Assignments.host_id | Number | ID of the host that the entity is associated with. |
| Vectra.Entity.Assignments.account_id | Unknown | ID of the account that the entity is associated with. |
| Vectra.Entity.Assignments.assigned_to.id | Number | ID of the user who is currently assigned to the entity. |
| Vectra.Entity.Assignments.assigned_to.username | String | Username of the user who is currently assigned to the entity. |
Command Example
!vectra-entity-assignment-update assignment_id=1 user_id=2
Context Example
{
{
"assigned_by": {
"id": 65,
"username": "api_client"
},
"date_assigned": "2023-07-21T12:44:10Z",
"events": [
{
"assignment_id": 1,
"actor": 65,
"event_type": "reassigned",
"datetime": "2023-07-25T06:26:10Z",
"context": {
"from": 1,
"to": 2,
"entity_t_score": 68,
"entity_c_score": 90
}
},
{
"assignment_id": 1,
"actor": 65,
"event_type": "created",
"datetime": "2023-07-21T12:44:10Z",
"context": {
"to": 1,
"entity_t_score": 68,
"entity_c_score": 90
}
}
],
"host_id": 97,
"assigned_to": {
"id": 2,
"username": "test_user_2"
},
"assignment_id": 1,
"id": 1
}
}
Human Readable Output
The assignment has been successfully updated
Assignment detail
Assignment ID Assigned By Assigned Date Assigned To Event Type 1 api_client 2023-07-21T12:44:10Z test_user_2 reassigned
vectra-detection-pcap-download
Download pcap of the detection.
Base Command
vectra-detection-pcap-download
Input
| Argument Name | Description | Required |
|---|---|---|
| detection_id | Specify the ID of the detection. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| File.Size | Number | The size of the file. |
| File.SHA1 | String | The SHA1 hash of the file. |
| File.SHA256 | String | The SHA256 hash of the file. |
| File.SHA512 | String | The SHA512 hash of the file. |
| File.Name | String | The name of the file. |
| File.SSDeep | String | The SSDeep hash of the file. |
| File.EntryID | String | The entry ID of the file. |
| File.Info | String | File information. |
| File.Type | String | The file type. |
| File.MD5 | String | The MD5 hash of the file. |
| File.Extension | String | The file extension. |
Command Example
!vectra-detection-pcap-download detection_id="116"
Context Example
{
"File": {
"EntryID": "1703@7e0f6637-f0a4-46b3-8c61-2f94b3432428",
"Extension": "pcap",
"Info": "pcap-ng capture file - version 1.0",
"MD5": "709db6e1f8f5054ca57caf43ba248ed6",
"Name": "IP-192.168.55.10_hidden_dns_tunnel_1382.pcap",
"SHA1": "49fe55c6aef85549261b46dd2e54f8d485306ee5",
"SHA256": "8615bde9332584b4fd4fe4dc2cc6fc4c75504f6d44667814456c089fd413aa4d",
"SHA512": "3fa29be0e20884c850b62d2a99aa09b24488289ba0bc9aff37ebe982c21d3a78fb26d9c9ac7fbf2a0839ba649dc0a845f30e7f13de3a0c6284c3c2ac54102143",
"SSDeep": "384:dN+Pm11R0XPmts64kZog9ZaikYngk+SnRxFyeyCEyuAOasucOcakca0/rHfcjOUI:dI+t25caEPjRSnmuNasxRana4DgOUDcX",
"Size": 23988,
"Type": "application/vnd.tcpdump.pcap"
}
}
Human Readable Output
Uploaded file: IP-192.168.55.10_hidden_dns_tunnel_1382.pcap
Property Type Size Info MD5 SHA1 SHA256 SHA512 SSDeep Value application/vnd.tcpdump.pcap 23,988 bytes pcap-ng capture file - version 1.0 709db6e1f8f5054ca57caf43ba248ed6 49fe55c6aef85549261b46dd2e54f8d485306ee5 8615bde9332584b4fd4fe4dc2cc6fc4c75504f6d44667814456c089fd413aa4d 3fa29be0e20884c850b62d2a99aa09b24488289ba0bc9aff37ebe982c21d3a78fb26d9c9ac7fbf2a0839ba649dc0a845f30e7f13de3a0c6284c3c2ac54102143 384:dN+Pm11R0XPmts64kZog9ZaikYngk+SnRxFyeyCEyuAOasucOcakca0/rHfcjOUI:dI+t25caEPjRSnmuNasxRana4DgOUDcX
vectra-assignment-list
Returns a list of all assignments.
Base Command
vectra-assignment-list
Input
| Argument Name | Description | Required |
|---|---|---|
| entity_ids | Specify the IDs of the entities. Comma-separated values supported. | Optional |
| entity_type | Specify the type of the entity. Possible values are: account, host. | Optional |
| resolved | Filter by resolved status. Possible values are: True, False. | Optional |
| assignees | Filter by user ids of the assignment. Comma-separated values supported. | Optional |
| resolution | Filter by outcome ids of the resolution. Comma-separated values supported. | Optional |
| created_after | Filter by created after the timestamp. Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ. For example: 01 May 2023, 01 Mar 2021 04:45:33, 2022-04-17T14:05:44Z. |
Optional |
| page | Enables the caller to specify a particular page of results. Default is 1. | Optional |
| page_size | Specify the desired page size for the request. Default is 50. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.Entity.Assignments.id | Number | ID of the assignment. |
| Vectra.Entity.Assignments.assignment_id | Number | ID of the assignment. |
| Vectra.Entity.Assignments.assigned_by.id | Number | ID of the user who assigned the entity. |
| Vectra.Entity.Assignments.assigned_by.username | String | Username of the user who assigned the entity. |
| Vectra.Entity.Assignments.date_assigned | Date | Date when the entity was assigned. |
| Vectra.Entity.Assignments.date_resolved | Date | Date when the entity was resolved. |
| Vectra.Entity.Assignments.events.assignment_id | Number | ID of the assignment event. |
| Vectra.Entity.Assignments.events.actor | Number | ID of the actor who performed the assignment event. |
| Vectra.Entity.Assignments.events.event_type | String | Type of the assignment event. |
| Vectra.Entity.Assignments.events.datetime | Date | Date of the assignment event. |
| Vectra.Entity.Assignments.events.context.to | Number | ID of the entity that was assigned to. |
| Vectra.Entity.Assignments.events.context.entity_t_score | Number | Threat score of the entity that was assigned to. |
| Vectra.Entity.Assignments.events.context.entity_c_score | Number | Certainty score of the entity that was assigned to. |
| Vectra.Entity.Assignments.events.context.triage_as | String | Triage status of the entity. |
| Vectra.Entity.Assignments.events.context.triaged_detection_ids | Array | IDs of the detections that have been triaged for the entity. |
| Vectra.Entity.Assignments.events.context.fixed_detection_ids | Array | IDs of the detections that have been fixed. |
| Vectra.Entity.Assignments.events.context.created_rule_ids | Array | IDs of the rules that have been created for the entity. |
| Vectra.Entity.Assignments.outcome.id | Number | ID of the assignment outcome. |
| Vectra.Entity.Assignments.outcome.builtin | Boolean | Whether the assignment outcome is builtin or not. |
| Vectra.Entity.Assignments.outcome.user_selectable | Boolean | Whether the assignment outcome is user selectable or not. |
| Vectra.Entity.Assignments.outcome.title | String | Title of the assignment outcome. |
| Vectra.Entity.Assignments.outcome.category | String | Category of the assignment outcome. |
| Vectra.Entity.Assignments.resolved_by.id | Number | ID of the user who resolved the entity. |
| Vectra.Entity.Assignments.resolved_by.username | String | Username of the user who resolved the entity. |
| Vectra.Entity.Assignments.triaged_detections | Array | Number of detections that have been triaged for the entity. |
| Vectra.Entity.Assignments.host_id | Number | ID of the host that the entity is associated with. |
| Vectra.Entity.Assignments.account_id | Number | ID of the account that the entity is associated with. |
| Vectra.Entity.Assignments.assigned_to.id | Number | ID of the user who is currently assigned to the entity. |
| Vectra.Entity.Assignments.assigned_to.username | String | Username of the user who is currently assigned to the entity. |
Command Example
#### Context Example
```json
{
"Vectra": {
"Entity": {
"Assignments": [
{
"id": 214,
"assigned_by": {
"id": 64,
"username": "test.user4@example.com"
},
"date_assigned": "2023-08-18T10:55:29Z",
"events": [
{
"assignment_id": 214,
"actor": 64,
"event_type": "reassigned",
"datetime": "2023-08-18T10:56:11Z",
"context": {
"from": 39,
"to": 59,
"entity_t_score": 0,
"entity_c_score": 0
}
},
{
"assignment_id": 214,
"actor": 64,
"event_type": "created",
"datetime": "2023-08-18T10:55:29Z",
"context": {
"to": 39,
"entity_t_score": 0,
"entity_c_score": 0
}
}
],
"host_id": 220,
"assigned_to": {
"id": 59,
"username": "test.user2@example.com"
},
"assignment_id": 214
},
{
"id": 212,
"assigned_by": {
"id": 65,
"username": "test.user4@example.com"
},
"date_assigned": "2023-08-18T06:29:56Z",
"date_resolved": "2023-08-18T06:32:09Z",
"events": [
{
"assignment_id": 212,
"actor": 65,
"event_type": "resolved",
"datetime": "2023-08-18T06:32:09Z",
"context": {
"entity_t_score": 77,
"entity_c_score": 53
}
},
{
"assignment_id": 212,
"actor": 65,
"event_type": "reassigned",
"datetime": "2023-08-18T06:31:02Z",
"context": {
"from": 59,
"to": 60,
"entity_t_score": 77,
"entity_c_score": 53
}
},
{
"assignment_id": 212,
"actor": 65,
"event_type": "created",
"datetime": "2023-08-18T06:29:56Z",
"context": {
"to": 59,
"entity_t_score": 77,
"entity_c_score": 53
}
}
],
"outcome": {
"id": 1,
"builtin": true,
"user_selectable": true,
"title": "Benign True Positive",
"category": "benign_true_positive"
},
"resolved_by": {
"id": 65,
"username": "test.user4@example.com"
},
"account_id": 108,
"assigned_to": {
"id": 60,
"username": "test.user1@example.com"
},
"assignment_id": 212
}
]
}
}
}
Human Readable Output
Assignments Table (Showing Page 1 out of 1)
Account ID Host ID Assignment ID Assigned By Assigned To Date Assigned Resolved By Date Resolved Outcome ID Outcome 220 214 test.user4@example.com test.user2@example.com 2023-08-18T10:55:29Z 108 212 test.user4@example.com test.user1@example.com 2023-08-18T06:29:56Z test.user4@example.com 2023-08-18T06:32:09Z 1 Benign True Positive
vectra-entity-note-list
Returns a list of notes for a specified entity.
Base Command
vectra-entity-note-list
Input
| Argument Name | Description | Required |
|---|---|---|
| entity_id | Specify the ID of the entity. | Required |
| entity_type | Specify the type of the entity. Possible values are: host, account. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.Entity.Notes.note_id | Number | ID of the note. |
| Vectra.Entity.Notes.id | Number | ID of the note. |
| Vectra.Entity.Notes.date_created | Date | Date when the note was created. |
| Vectra.Entity.Notes.date_modified | Unknown | Date when the note was last modified. |
| Vectra.Entity.Notes.created_by | String | User who created the note. |
| Vectra.Entity.Notes.modified_by | Unknown | User who last modified the note. |
| Vectra.Entity.Notes.note | String | Content of the note. |
| Vectra.Entity.Notes.entity_id | String | ID of the entity associated with the note. |
| Vectra.Entity.Notes.entity_type | String | Type of the entity associated with the note. |
Command Example
!vectra-entity-note-list entity_id="107" entity_type="account"
Context Example
{
"Vectra": {
"Entity": {
"Notes": [
{
"created_by": "test_user@example.com",
"date_created": "2023-08-25T07:09:08Z",
"entity_id": 107,
"entity_type": "account",
"id": 1070,
"modified_by": "test_user@example.com",
"note": "From XSOAR",
"note_id": 1070
},
{
"created_by": "test_user@example.com",
"date_created": "2023-08-25T07:08:58Z",
"entity_id": 107,
"entity_type": "account",
"id": 1069,
"modified_by": "test_user@example.com",
"note": "Test note",
"note_id": 1069
},
{
"created_by": "api_client",
"date_created": "2023-08-16T05:23:33Z",
"entity_id": 107,
"entity_type": "account",
"id": 922,
"note": "[Mirrored From XSOAR] XSOAR Incident ID: 14228\n\nNote: **bold**\n\n_Italic_\n\n+Underline+\n\n~~strikethrough~~\n\nAdded By: admin",
"note_id": 922
}
]
}
}
}
Human Readable Output
Entity Notes Table
Note ID Note Created By Created Date Modified By Modified Date 1070 From XSOAR test_user@example.com 2023-08-25T07:09:08Z test_user@example.com 2023-08-25T08:10:08Z 1069 Test note test_user@example.com 2023-08-25T07:08:58Z test_user@example.com 2023-08-25T08:10:08Z 922 [Mirrored From XSOAR] XSOAR Incident ID: 14228
Note:XSOAR note
Added By: adminapi_client 2023-08-16T05:23:33Z
vectra-group-list
Returns a list of all groups.
Base Command
vectra-group-list
Input
| Argument Name | Description | Required |
|---|---|---|
| group_type | Filter by group type. Possible values are: account, host, ip, domain. | Optional |
| account_names | Filter by Account Names. Supports comma-separated values. Note: Only valid when the group_type parameter is set to “account”. |
Optional |
| domains | Filter by Domains. Supports comma-separated values. Note: Only valid when the group_type parameter is set to “domain”. |
Optional |
| host_ids | Filter by Host IDs. Supports comma-separated values. Note: Only valid when the group_type parameter is set to “host”. |
Optional |
| host_names | Filter by Host Names. Supports comma-separated values. Note: Only valid when the group_type parameter is set to “host”. |
Optional |
| importance | Filter by group importance. Possible values are: high, medium, low, never_prioritize. | Optional |
| ips | Filter by IPs. Supports comma-separated values. Note: Only valid when the group_type parameter is set to “ip”. |
Optional |
| description | Filter by group description. | Optional |
| last_modified_timestamp | Return only the groups which have a last modification timestamp equal to or after the given timestamp. Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ. For example: 01 May 2023, 01 Mar 2023 04:45:33, 2023-04-17T14:05:44Z. |
Optional |
| last_modified_by | Filters by the user id who made the most recent modification to the group. | Optional |
| group_name | Filters by group name. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.Group.group_id | Number | ID of the group. |
| Vectra.Group.id | Number | ID of the group. |
| Vectra.Group.name | String | Name of the group. |
| Vectra.Group.description | String | Description of the group. |
| Vectra.Group.last_modified | Date | Date when the group was last modified. |
| Vectra.Group.last_modified_by | String | Name of the user who last modified the group. |
| Vectra.Group.type | String | Type of the group. |
| Vectra.Group.members | Unknown | Members of the group. |
| Vectra.Group.members.id | Number | Entity ID of member. |
| Vectra.Group.members.name | String | Entity name of member. |
| Vectra.Group.members.is_key_asset | Boolean | Indicates key asset. |
| Vectra.Group.members.url | String | Entity URL of member. |
| Vectra.Group.members.uid | String | Entity UID of member. |
| Vectra.Group.rules.triage_category | String | Triage category of rule. |
| Vectra.Group.rules.id | Number | Id of the rule. |
| Vectra.Group.rules.description | String | Description of the rule. |
| Vectra.Group.importance | String | Importance level of the group. |
| Vectra.Group.cognito_managed | Boolean | Whether the group is managed by Cognito or not. |
Command Example
#### Context Example
```json
{
"Vectra": {
"Group": [
{
"id": 1,
"group_id": 1,
"name": "Cognito - Box",
"description": "Domains used by the Box service",
"last_modified": "2023-05-31T13:57:53Z",
"last_modified_by": "cognito",
"type": "domain",
"members": [
"*.abc.com",
"*.xyz.net"
],
"rules": [
{
"triage_category": "Box",
"id": 175,
"description": "data storage to Box service"
}
],
"importance": "medium",
"cognito_managed": true
},
{
"id": 8,
"group_id": 8,
"name": "Cognito - IPAM",
"description": "IPAM, created by Cognito",
"last_modified": "2023-08-18T09:16:54Z",
"last_modified_by": "cognito",
"type": "host",
"members": [
{
"is_key_asset": false,
"id": 97,
"name": "IP-0.0.0.0",
"url": "https://server_url.com/api/v3.3/hosts/97"
},
{
"is_key_asset": false,
"id": 212,
"name": "IP-0.0.0.1",
"url": "https://server_url.com/api/v3.3/hosts/212"
}
],
"rules": [
{
"triage_category": "Expected IPAM Behavior",
"id": 189,
"description": "Expected behavior from these devices"
},
{
"triage_category": "Expected IPAM Behavior",
"id": 193,
"description": "Expected behavior from these devices"
}
],
"importance": "medium"
},
{
"id": 16,
"group_id": 16,
"name": "Cognito - Guest Wifi",
"description": "IP space used by Guest Wifi",
"last_modified": "2023-08-18T08:55:54Z",
"last_modified_by": "cognito",
"type": "ip",
"members": [
"0.0.0.0",
"0.0.0.1"
],
"importance": "medium",
"cognito_managed": false
},
{
"id": 22,
"group_id": 22,
"name": "Dev-Group-Account-High",
"description": "",
"last_modified": "2023-08-25T10:17:37Z",
"last_modified_by": "cognito",
"type": "account",
"members": [
{
"uid": "O300:service-principal_00000000-0000-0000-0000-000000000001"
},
{
"uid": "administrator@fictotech.com"
}
],
"importance": "high"
}
]
}
}
Human Readable Output
Groups Table
Group ID Name Group Type Description Importance Members Last Modified Timestamp 1 Cognito - Box domain Domains used by the Box service medium *.abc.com, *.xyz.net 2023-05-31T13:57:53Z 8 Cognito - IPAM host IPAM, created by Cognito medium 97, 212 2023-08-18T09:16:54Z 16 Cognito - Guest Wifi ip IP space used by Guest Wifi medium 0.0.0.0, 0.0.0.1 2023-08-18T08:55:54Z 22 Dev-Group-Account-High account high O300:service-principal_00000000-0000-0000-0000-000000000001, administrator@fictotech.com 2023-08-25T10:17:37Z
vectra-group-unassign
Unassign members from the specified group.
Base Command
vectra-group-unassign
Input
| Argument Name | Description | Required |
|---|---|---|
| group_id | Specify Group ID to unassign members. | Required |
| members | Member values based on the group type. Supports comma-separated values. Note: If the group type is host, then the “Host IDs”. If the group type is account, then “Account Names”. If the group type is ip, then the list of “IPs”. If the group type is domain, then the list of “Domains” . |
Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.Group.group_id | Number | ID of the group. |
| Vectra.Group.id | Number | ID of the group. |
| Vectra.Group.name | String | Name of the group. |
| Vectra.Group.description | String | Description of the group. |
| Vectra.Group.last_modified | Date | Date when the group was last modified. |
| Vectra.Group.last_modified_by | String | Name of the user who last modified the group. |
| Vectra.Group.type | String | Type of the group. |
| Vectra.Group.members | Unknown | Members of the group. |
| Vectra.Group.members.id | Number | Entity ID of member. |
| Vectra.Group.members.name | String | Entity name of member. |
| Vectra.Group.members.is_key_asset | Boolean | Indicates key asset. |
| Vectra.Group.members.url | String | Entity URL of member. |
| Vectra.Group.members.uid | String | Entity UID of member. |
| Vectra.Group.rules.triage_category | String | Triage category of rule. |
| Vectra.Group.rules.id | Number | Id of the rule. |
| Vectra.Group.rules.description | String | Description of the rule. |
Command Example
!vectra-group-unassign group_id=23 members="*.domain4.com,*.domain5.com"
Context Example
{
"Vectra": {
"Group": {
"cognito_managed": false,
"description": "xsoar-group-account-test",
"group_id": 23,
"id": 23,
"last_modified": "2023-09-04T12:03:02Z",
"last_modified_by": "API Client a7f5be37",
"members": ["*.domain1.net", "*.domain2.com", "*.domain3.com"],
"name": "xsoar-group-account-test",
"type": "domain"
}
}
}
Human Readable Output
Member(s) *.domain4.com, *.domain5.com have been unassigned from the group
Updated group details
Group ID Name Group Type Description Members Last Modified Timestamp 1 xsoar-group-account-test domain xsoar-group-account-test *.domain1.net, *.domain2.com, *.domain3.com 2023-09-04T07:30:01Z
vectra-group-assign
Assign members to the specified group.
Base Command
vectra-group-assign
Input
| Argument Name | Description | Required |
|---|---|---|
| group_id | Specify Group ID to assign members. | Required |
| members | Member values based on the group type. Supports comma-separated values. Note: If the group type is host, then the “Host IDs”. If the group type is account, then “Account Names”. If the group type is ip, then the list of “IPs”. If the group type is domain, then the list of “Domains” . |
Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.Group.group_id | Number | ID of the group. |
| Vectra.Group.id | Number | ID of the group. |
| Vectra.Group.name | String | Name of the group. |
| Vectra.Group.description | String | Description of the group. |
| Vectra.Group.last_modified | Date | Date when the group was last modified. |
| Vectra.Group.last_modified_by | String | Name of the user who last modified the group. |
| Vectra.Group.type | String | Type of the group. |
| Vectra.Group.members | Unknown | Members of the group. |
| Vectra.Group.members.id | Number | Entity ID of member. |
| Vectra.Group.members.name | String | Entity name of member. |
| Vectra.Group.members.is_key_asset | Boolean | Indicates key asset. |
| Vectra.Group.members.url | String | Entity URL of member. |
| Vectra.Group.members.uid | String | Entity UID of member. |
| Vectra.Group.rules.triage_category | String | Triage category of rule. |
| Vectra.Group.rules.id | Number | Id of the rule. |
| Vectra.Group.rules.description | String | Description of the rule. |
Command Example
!vectra-group-assign group_id=23 members="*.domain4.com,*.domain5.com"
Context Example
{
"Vectra": {
"Group": {
"cognito_managed": false,
"description": "xsoar-group-account-test",
"group_id": 23,
"id": 23,
"last_modified": "2023-09-04T11:59:15Z",
"last_modified_by": "API Client a7f5be37",
"members": [
"*.domain1.net",
"*.domain2.com",
"*.domain3.com",
"*.domain4.com",
"*.domain5.com"
],
"name": "xsoar-group-account-test",
"type": "domain"
}
}
}
Human Readable Output
Member(s) *.domain4.com, *.domain5.com have been assigned to the group
Updated group details
Group ID Name Group Type Description Members Last Modified Timestamp 1 xsoar-group-account-test domain xsoar-group-account-test *.domain1.net, *.domain2.com, *.domain3.com, *.domain4.com, *.domain5.com 2023-09-04T06:30:01Z
vectra-entity-detections-mark-asclosed
Mark the detections of the entity as closed with the provided entity ID in the argument.
Base Command
vectra-entity-detections-mark-asclosed
Input
| Argument Name | Description | Required |
|---|---|---|
| entity_id | Specify the ID of the entity. | Required |
| entity_type | Specify the type of the entity. Possible values are: account, host. | Required |
| close_reason | Specify the close reason. Possible values are: benign, remediated. | Required |
Context Output
There is no context output for this command.
Command example
!vectra-entity-detections-mark-asclosed entity_id=1 entity_type=account close_reason=benign
Human Readable Output
The detections (34122, 35097) of the provided entity ID have been successfully closed as benign
vectra-detections-mark-asopen
Open detections with provided detection IDs in the argument.
Base Command
vectra-detections-mark-asopen
Input
| Argument Name | Description | Required |
|---|---|---|
| detection_ids | Provide a list of detection IDs separated by commas or a single detection ID. | Required |
Context Output
There is no context output for this command.
Command example
!vectra-detections-mark-asopen detection_ids=1,2,3
Human Readable Output
The provided detection IDs have been successfully re-opened
vectra-detection-tag-list
Returns a list of tags for a specified detection.
Base Command
vectra-detection-tag-list
Input
| Argument Name | Description | Required |
|---|---|---|
| detection_id | Specify the ID of the detection. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.Detection.Tags.tag_id | String | The ID of the tag. |
| Vectra.Detection.Tags.detection_id | String | The ID of the Detection associated with the tag. |
| Vectra.Detection.Tags.tags | Unknown | A list of tags linked to a detection. |
Command example
!vectra-detection-tag-list detection_id=123
Context Example
{
"Vectra": {
"Detection": {
"Tags": {
"detection_id": 123,
"tag_id": "123",
"tags": [
"tag1",
"tag2"
]
}
}
}
}
Human Readable Output
List of tags: tag1, tag2
vectra-detection-tag-add
Add tags to a detection.
Base Command
vectra-detection-tag-add
Input
| Argument Name | Description | Required |
|---|---|---|
| detection_id | Specify the ID of the detection. | Required |
| tags | Comma-separated values of tags to be added to the detection. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.Detection.Tags.tag_id | String | The ID of the tag. |
| Vectra.Detection.Tags.detection_id | String | The ID of the detection associated with the tag. |
| Vectra.Detection.Tags.tags | Unknown | A list of tags linked to a detection. |
Command example
!vectra-detection-tag-add detection_id=1 tags="tag1,tag2"
Context Example
{
"Vectra": {
"Detection": {
"Tags": {
"detection_id": 1,
"tag_id": 1,
"tags": [
"tag",
"tag1",
"tag2"
]
}
}
}
}
Human Readable Output
Tags have been successfully added to the detection
Updated list of tags: tag, tag1, tag2
vectra-detection-tag-remove
Remove tags from the detection.
Base Command
vectra-detection-tag-remove
Input
| Argument Name | Description | Required |
|---|---|---|
| detection_id | Specify the ID of the detection. | Required |
| tags | Comma-separated values of tags to be removed from the detection. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.Detection.Tags.tag_id | String | The ID of the tag. |
| Vectra.Detection.Tags.detection_id | String | The ID of the detection associated with the tag. |
| Vectra.Detection.Tags.tags | Unknown | A list of tags linked to a detection. |
Command example
!vectra-detection-tag-remove detection_id="2" tags="tag3,tag4"
Context Example
{
"Vectra": {
"Detection": {
"Tags": {
"detection_id": 2,
"tag_id": "2",
"tags": [
"tag",
"tag1",
"tag2"
]
}
}
}
}
Human Readable Output
Specified tags have been successfully removed for the detection
Updated list of tags: tag, tag1, tag2
vectra-detection-note-list
Returns a list of notes for a specified detection.
Base Command
vectra-detection-note-list
Input
| Argument Name | Description | Required |
|---|---|---|
| detection_id | Specify the ID of the detection. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.Detection.Notes.note_id | Number | ID of the note. |
| Vectra.Detection.Notes.id | Number | ID of the note. |
| Vectra.Detection.Notes.date_created | Date | Date when the note was created (ISO8601). |
| Vectra.Detection.Notes.date_modified | Date | Date when the note was last modified (ISO8601). |
| Vectra.Detection.Notes.created_by | String | User who created the note. |
| Vectra.Detection.Notes.modified_by | String | User who last modified the note. |
| Vectra.Detection.Notes.note | String | Content of the note. |
| Vectra.Detection.Notes.detection_id | String | ID of the detection associated with the note. |
Command example
!vectra-detection-note-list detection_id=1
Context Example
{
"Vectra": {
"Detection": {
"Notes": [
{
"created_by": "test_user@example.com",
"date_created": "2023-08-25T07:09:08Z",
"detection_id": 1,
"id": 1070,
"modified_by": "test_user@example.com",
"note": "From XSOAR",
"note_id": 1070
},
{
"created_by": "test_user@example.com",
"date_created": "2023-08-25T07:08:58Z",
"detection_id": 1,
"id": 1069,
"modified_by": "test_user@example.com",
"note": "Test note",
"note_id": 1069
},
{
"created_by": "api_client",
"date_created": "2023-08-16T05:23:33Z",
"detection_id": 1,
"id": 922,
"note": "[Mirrored From XSOAR] XSOAR Incident ID: 14228\n\nNote: **bold**\n\n_Italic_\n\n+Underline+\n\n~~strikethrough~~\n\nAdded By: admin",
"note_id": 922
}
]
}
}
}
Human Readable Output
Detection Notes Table
Note ID Note Created By Created Date Modified By Modified Date 1070 From XSOAR test_user@example.com 2023-08-25T07:08:58Z test_user@example.com 2023-08-25T07:08:58Z 1069 Test note test_user@example.com 2023-08-25T07:08:58Z test_user@example.com 2023-08-25T07:08:58Z 922 [Mirrored From XSOAR] XSOAR Incident ID: 14228\n\nNote: bold\n\n_Italic_\n\n+Underline+\n\nstrikethrough\n\nAdded By: admin api_client 2023-08-16T05:23:33Z
vectra-detection-note-add
Add a note to the detection.
Base Command
vectra-detection-note-add
Input
| Argument Name | Description | Required |
|---|---|---|
| detection_id | Specify the ID of the detection. | Required |
| note | Note to be added in the specified detection_id. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.Detection.Notes.detection_id | String | ID of the detection associated with the note. |
| Vectra.Detection.Notes.note_id | Number | ID of the note. |
| Vectra.Detection.Notes.id | Number | ID of the note. |
| Vectra.Detection.Notes.date_created | Date | Date when the note was created (ISO8601). |
| Vectra.Detection.Notes.created_by | String | User who created the note. |
| Vectra.Detection.Notes.note | String | Content of the note. |
Command example
!vectra-detection-note-add detection_id=1 note="test note"
Context Example
{
{
"date_created": "2023-06-21T06:19:15.224449Z",
"created_by": "test_user",
"note": "test note",
"note_id": 19,
"id": 19,
"detection_id": 1
}
}
Human Readable Output
The note has been successfully added to the detection
Returned Note ID: 19
vectra-detection-note-update
Update a note in the detection.
Base Command
vectra-detection-note-update
Input
| Argument Name | Description | Required |
|---|---|---|
| detection_id | Specify the ID of the detection. | Required |
| note_id | Specify the ID of the note. | Required |
| note | Note to be updated for the specified note_id. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.Detection.Notes.detection_id | String | ID of the detection associated with the note. |
| Vectra.Detection.Notes.note_id | Number | ID of the note. |
| Vectra.Detection.Notes.id | Number | ID of the note. |
| Vectra.Detection.Notes.date_created | Date | Date when the note was created (ISO8601). |
| Vectra.Detection.Notes.date_modified | Date | Date when the note was last modified (ISO8601). |
| Vectra.Detection.Notes.created_by | String | User who created the note. |
| Vectra.Detection.Notes.modified_by | String | User who last modified the note. |
| Vectra.Detection.Notes.note | String | Content of the note. |
Command example
!vectra-detection-note-update detection_id=1 note_id=1 note="note modified"
Context Example
{
{
"date_created": "2023-06-16T04:55:58Z",
"date_modified": "2023-06-22T04:57:09Z",
"created_by": "test_user",
"modified_by": "test_user",
"note": "note modified",
"note_id": 8,
"id": 8,
"detection_id": 1
}
}
Human Readable Output
The note has been successfully updated in the detection
vectra-detection-note-remove
Remove a note from the detection.
Base Command
vectra-detection-note-remove
Input
| Argument Name | Description | Required |
|---|---|---|
| detection_id | Specify the ID of the detection. | Required |
| note_id | Specify the ID of the note. | Required |
Context Output
There is no context output for this command.
Command Example
!vectra-detection-note-remove detection_id=1 note_id=1
Context Example
{}
Human Readable Output
The note has been successfully removed from the detection
vectra-entity-unresolved-priority-reset
Update the unresolved priority of an entity to false.
Base Command
vectra-entity-unresolved-priority-reset
Input
| Argument Name | Description | Required |
|---|---|---|
| entity_id | Specify the ID of the entity. Note: Users can get the entity ID by executing the “vectra-entity-list” command. |
Required |
| entity_type | Specify the type of the entity. Possible values are: account, host. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.Entity.id | String | An ID of the entity. |
| Vectra.Entity.type | String | The type of the entity. |
| Vectra.Entity.unresolved_priority | Boolean | An entity unresolved priority status. |
Command Example
!vectra-entity-unresolved-priority-reset entity_id=1 entity_type=account
Context Example
{
"Vectra": {
"Entity": [
{
"id": "1",
"type": "account",
"unresolved_priority": false
}
]
}
}
Human Readable Output
The unresolved priority of the provided entity has been successfully changed as ‘false’
vectra-detection-investigation-status-update
Update the investigation status of the detection by detection ID(s).
Base Command
vectra-detection-investigation-status-update
Input
| Argument Name | Description | Required |
|---|---|---|
| detection_ids | Provide a list of detection IDs separated by comma or a single detection ID. Note: Users can get the detection ID by executing the “vectra-detection-list” command. |
Required |
| investigation_status | Specify the investigation status. Possible values are: open, acknowledged, escalated, paused, closed, expired. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.Detection.id | String | The detection ID. |
| Vectra.Detection.investigation_status | String | The detection investigation status. |
Command Example
!vectra-detection-investigation-status-update detection_ids=1 investigation_status=escalated
Context Example
{
"Vectra": {
"Detection": [
{
"id": "1",
"investigation_status": "escalated"
}
]
}
}
Human Readable Output
The investigation Status for provided Detection ID(s) [‘1’] have been updated as escalated
vectra-detection-external-id-update
Update the external reference ID for the provided detection ID(s).
Base Command
vectra-detection-external-id-update
Input
| Argument Name | Description | Required |
|---|---|---|
| detection_ids | Provide a list of detection IDs separated by comma or a single detection ID. Note: Users can get the detection ID by executing the “vectra-detection-list” command. |
Required |
| external_reference_id | Provide the external reference ID. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.Detection.id | String | The detection ID. |
| Vectra.Detection.external_reference_id | String | The external reference ID of the detection. |
Command Example
!vectra-detection-external-id-update detection_ids=1 external_reference_id=12345
Context Example
{
"Vectra": {
"Detection": [
{
"id": "1",
"external_reference_id": "12345"
}
]
}
}
Human Readable Output
The external reference ID for provided Detection ID(s) [‘1’] have been updated as 12345
vectra-entity-external-id-update
Update the external reference ID for the provided entity.
Base Command
vectra-entity-external-id-update
Input
| Argument Name | Description | Required |
|---|---|---|
| entity_id | Specify the ID of the entity. Note: Users can get the entity ID by executing the “vectra-entity-list” command. |
Required |
| entity_type | Specify the type of the entity. Possible values are: account, host. | Required |
| external_reference_id | Provide the external reference ID. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.Entity.id | String | An ID of the entity. |
| Vectra.Entity.type | String | The type of the entity. |
| Vectra.Entity.external_reference_id | String | The external reference ID of the entity. |
Command Example
!vectra-entity-external-id-update entity_id=1 entity_type=account external_reference_id=12345
Context Example
{
"Vectra": {
"Entity": [
{
"id": "1",
"type": "account",
"external_reference_id": "12345"
}
]
}
}
Human Readable Output
The external reference ID for provided Entity have been updated as 12345
vectra-detection-list
Returns a list of detections based on the specified filters.
Base Command
vectra-detection-list
Input
| Argument Name | Description | Required |
|---|---|---|
| created_after | Filter the detections by created on or after the specified time. Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ. For example: 01 March 2026, 01 Mar 2026 04:45:33, 2026-04-17T14:05:44Z. |
Optional |
| created_before | Filter the detections by created on or before the specified time. Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ. For example: 01 March 2026, 01 Mar 2026 04:45:33, 2026-04-17T14:05:44Z. |
Optional |
| last_detected_after | Filter the detections by last detected on or after the specified time. Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ. For example: 01 March 2026, 01 Mar 2026 04:45:33, 2026-04-17T14:05:44Z. |
Optional |
| last_detected_before | Filter the detections by last detected on or before the specified time. Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ. For example: 01 March 2026, 01 Mar 2026 04:45:33, 2026-04-17T14:05:44Z. |
Optional |
| description | Filter by description containing specified value. | Optional |
| detection_name | Filter by detection name. | Optional |
| detection_type | Filter by detection type. | Optional |
| detection_category | Filter by detections category. Possible values are: Command & Control, Botnet, Reconnaissance, Lateral Movement, Exfiltration, Info. | Optional |
| include_info_category_detections | Include the info category detections which are excluded by default. Possible values are: true, false. Default is true. | Optional |
| close_reason | Filter by close reason of the detection. Possible values are: benign, remediated. | Optional |
| detection_state | Filter by detection state. Possible values are: active, inactive, fixed. | Optional |
| entity_type | Filter by Entity type. Possible values are: account, host. | Optional |
| tags | Filter by detection tags. Comma-separated values supported. | Optional |
| is_triaged | Filter by detection triage status. Possible values are: true, false. Default is false. | Optional |
| page | Provide page number to retrieve. Default is 1. | Optional |
| page_size | Provide a number of results per page. Default is 50. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.Detection.id | Number | A unique identifier for the detection. |
| Vectra.Detection.assigned_date | Date | The date when the detection was assigned. |
| Vectra.Detection.assigned_to | String | The email or user to whom the detection is assigned. |
| Vectra.Detection.certainty | Number | The certainty level associated with the detection. |
| Vectra.Detection.created_timestamp | Date | The timestamp when the detection was created. |
| Vectra.Detection.custom_detection | Unknown | The custom detection configuration or settings. |
| Vectra.Detection.data_source.type | String | The type of data source for the detection. |
| Vectra.Detection.data_source.connection_name | String | The name of the connection used for data ingestion. |
| Vectra.Detection.data_source.connection_id | String | A unique identifier for the data source connection. |
| Vectra.Detection.description | String | The description of the detection. |
| Vectra.Detection.detection | String | The name of the detection. |
| Vectra.Detection.detection_category | String | The category of the detection. |
| Vectra.Detection.detection_type | String | The type of the detection. |
| Vectra.Detection.detection_url | String | The URL to access the detection details. |
| Vectra.Detection.filtered_by_ai | Boolean | Indicates if the detection was filtered by AI. |
| Vectra.Detection.filtered_by_rule | Boolean | Indicates if the detection was filtered by a rule. |
| Vectra.Detection.filtered_by_user | Boolean | Indicates if the detection was filtered by a user. |
| Vectra.Detection.first_timestamp | Date | The first timestamp when the detection was observed. |
| Vectra.Detection.grouped_details.role | String | The role associated with the detection group. |
| Vectra.Detection.grouped_details.last_timestamp | Date | The last timestamp of the detection group. |
| Vectra.Detection.groups.id | Number | A unique identifier for the group. |
| Vectra.Detection.groups.name | String | The name of the group. |
| Vectra.Detection.groups.description | String | The description of the group. |
| Vectra.Detection.groups.type | String | The type of the group. |
| Vectra.Detection.groups.last_modified | Date | The timestamp when the group was last modified. |
| Vectra.Detection.groups.last_modified_by | String | The email or user who last modified the group. |
| Vectra.Detection.is_custom_model | Boolean | Indicates if the detection uses a custom model. |
| Vectra.Detection.is_marked_custom | Boolean | Indicates if the detection is marked as custom. |
| Vectra.Detection.is_triaged | Boolean | Indicates if the detection has been triaged. |
| Vectra.Detection.last_timestamp | Date | The last timestamp when the detection was observed. |
| Vectra.Detection.note | String | A note associated with the detection. |
| Vectra.Detection.note_modified_by | String | The email or user who modified the note. |
| Vectra.Detection.note_modified_timestamp | Date | The timestamp when the note was last modified. |
| Vectra.Detection.notes.created_by | String | The email or user who created the note. |
| Vectra.Detection.notes.date_created | Date | The date when the note was created. |
| Vectra.Detection.notes.date_modified | Date | The date when the note was modified. |
| Vectra.Detection.notes.id | Number | A unique identifier for the note. |
| Vectra.Detection.notes.modified_by | String | The email or user who modified the note. |
| Vectra.Detection.notes.note | String | The content of the note. |
| Vectra.Detection.reason | String | The reason for the detection state or triage action. |
| Vectra.Detection.sensor | String | The sensor identifier that detected the activity. |
| Vectra.Detection.sensor_name | String | The name of the sensor that detected the activity. |
| Vectra.Detection.src_account.id | Number | A unique identifier for the source account. |
| Vectra.Detection.src_account.name | String | The name of the source account. |
| Vectra.Detection.src_account.url | String | The URL to access the source account details. |
| Vectra.Detection.src_account.threat | Number | The threat level associated with the source account. |
| Vectra.Detection.src_account.certainty | Number | The certainty level associated with the source account. |
| Vectra.Detection.src_account.privilege_level | Number | The privilege level associated with the source account. |
| Vectra.Detection.src_account.privilege_category | String | The privilege category associated with the source account. |
| Vectra.Detection.src_host.id | Number | A unique identifier for the source host. |
| Vectra.Detection.src_host.name | String | The name of the source host. |
| Vectra.Detection.src_host.ip | String | The IP address of the source host. |
| Vectra.Detection.src_host.url | String | The URL to access the source host details. |
| Vectra.Detection.src_host.is_key_asset | Boolean | Indicates if the source host is a key asset. |
| Vectra.Detection.src_host.group.id | Number | A unique identifier for the source host group. |
| Vectra.Detection.src_host.group.name | String | The name of the source host group. |
| Vectra.Detection.src_host.group.description | String | The description of the source host group. |
| Vectra.Detection.src_host.group.type | String | The type of the source host group. |
| Vectra.Detection.src_host.group.last_modified | Date | The timestamp when the source host group was last modified. |
| Vectra.Detection.src_host.group.last_modified_by | String | The email or user who last modified the source host group. |
| Vectra.Detection.src_host.threat | Number | The threat level associated with the source host. |
| Vectra.Detection.src_host.certainty | Number | The certainty level associated with the source host. |
| Vectra.Detection.src_ip | String | The source IP address in the detection. |
| Vectra.Detection.src_groups.id | Number | A unique identifier for the source group. |
| Vectra.Detection.src_groups.name | String | The name of the source group. |
| Vectra.Detection.src_groups.description | String | The description of the source group. |
| Vectra.Detection.src_groups.type | String | The type of the source group. |
| Vectra.Detection.src_groups.last_modified | Date | The timestamp when the source group was last modified. |
| Vectra.Detection.src_groups.last_modified_by | String | The email or user who last modified the source group. |
| Vectra.Detection.dst_groups.id | Number | A unique identifier for the destination group. |
| Vectra.Detection.dst_groups.name | String | The name of the destination group. |
| Vectra.Detection.dst_groups.description | String | The description of the destination group. |
| Vectra.Detection.dst_groups.type | String | The type of the destination group. |
| Vectra.Detection.dst_groups.last_modified | Date | The timestamp when the destination group was last modified. |
| Vectra.Detection.dst_groups.last_modified_by | String | The email or user who last modified the destination group. |
| Vectra.Detection.state | String | The current state of the detection. |
| Vectra.Detection.summary.artifact | Array | The artifacts associated with the detection summary. |
| Vectra.Detection.summary.last_timestamp | Date | The last timestamp in the detection summary. |
| Vectra.Detection.summary.description | String | The description in the detection summary. |
| Vectra.Detection.summary.roles | Array | The roles associated with the detection summary. |
| Vectra.Detection.tags | Array | The tags associated with the detection. |
| Vectra.Detection.is_targeting_key_asset | Boolean | Indicates if the detection is targeting a key asset. |
| Vectra.Detection.threat | Number | The threat level of the detection. |
| Vectra.Detection.triage_rule_id | Unknown | A unique identifier for the triage rule applied to the detection. |
| Vectra.Detection.type | String | The type of the detection. |
| Vectra.Detection.url | String | The URL to access the detection details. |
Command Example
!vectra-detection-list page=1 page_size=2
Context Example
{
"Vectra": {
"Detection": [
{
"summary": {
"app_name": "Exchange",
"operations": [
"Add-MailboxPermission"
],
"src_ips": [
"10.0.0.1"
],
"description": "This account performed Exchange operations that were unusual for the account."
},
"src_account": {
"id": 1001,
"name": "user@example.com",
"url": "https://example.vectra.ai/api/v3.5/accounts/1001",
"threat": 45,
"certainty": 60
},
"state": "active",
"created_timestamp": "2026-01-15T10:30:00Z",
"filtered_by_user": false,
"type": "account",
"detection_type": "M365 Risky Exchange Operation",
"data_source": {
"type": "o365",
"connection_name": "M365-Production",
"connection_id": "abc123"
},
"filtered_by_rule": false,
"detection": "M365 Risky Exchange Operation",
"url": "https://example.vectra.ai/api/v3.5/detections/5001",
"sensor": "abc123",
"threat": 50,
"is_custom_model": false,
"is_triaged": false,
"detection_category": "lateral_movement",
"filtered_by_ai": false,
"detection_url": "https://example.vectra.ai/api/v3.5/detections/5001",
"last_timestamp": "2026-01-15T12:00:00Z",
"first_timestamp": "2026-01-15T10:00:00Z",
"certainty": 50,
"is_marked_custom": false,
"id": 5001,
"sensor_name": "Vectra NDR",
"is_targeting_key_asset": false,
"grouped_details": [
{
"parameters": [
{
"data": [
{
"name": "Identity",
"value": "mailbox@example.com"
}
],
"timestamp": "2026-01-15T11:30:00Z"
}
],
"operation": "Add-MailboxPermission",
"behavior": "Mailbox management",
"user_type": "Admin",
"last_timestamp": "2026-01-15T12:00:00Z",
"src_ip": "10.0.0.1",
"app_name": "Exchange"
}
]
},
{
"state": "active",
"created_timestamp": "2026-01-15T09:00:00Z",
"filtered_by_user": false,
"type": "host",
"detection_type": "Suspicious Domain",
"groups": [
{
"id": 10,
"name": "Production Servers",
"description": "Production server subnet",
"type": "ip",
"last_modified": "2026-01-10T08:00:00Z",
"last_modified_by": "admin@example.com"
}
],
"data_source": {
"type": "sensor",
"connection_name": "Network Sensor 1",
"connection_id": "xyz789"
},
"filtered_by_rule": false,
"detection": "Suspicious Domain",
"url": "https://example.vectra.ai/api/v3.5/detections/5002",
"sensor": "xyz789",
"threat": 30,
"is_custom_model": false,
"is_triaged": false,
"detection_category": "command_and_control",
"filtered_by_ai": false,
"detection_url": "https://example.vectra.ai/api/v3.5/detections/5002",
"src_ip": "10.0.1.50",
"last_timestamp": "2026-01-15T09:45:00Z",
"first_timestamp": "2026-01-15T09:00:00Z",
"src_host": {
"id": 2001,
"ip": "10.0.1.50",
"name": "workstation-01",
"url": "https://example.vectra.ai/api/v3.5/hosts/2001",
"is_key_asset": false,
"groups": [
{
"id": 10,
"name": "Production Servers",
"description": "Production server subnet",
"last_modified": "2026-01-10T08:00:00Z",
"last_modified_by": "admin@example.com",
"type": "ip"
}
],
"threat": 35,
"certainty": 40
},
"certainty": 25,
"is_marked_custom": false,
"id": 5002,
"sensor_name": "Network Sensor 1",
"is_targeting_key_asset": false,
"grouped_details": [
{
"protocol": "dns",
"last_timestamp": "2026-01-15T09:45:00Z",
"grouping_field": "last_timestamp",
"response_code": "NXDomain",
"target_domains": [
"suspicious-domain.example"
],
"dst_ips": [
"8.8.8.8"
]
}
],
"summary": {
"num_failures": 5,
"num_successes": 0,
"num_sessions": 10
}
}
]
}
}
Human Readable Output
Detections Table (Showing Page 1 out of 100)
ID Detection Name Detection Type Account Name Host Name Src IP Threat Score Certainty Score Number Of Events State Last Timestamp 5001 M365 Risky Exchange Operation M365 Risky Exchange Operation user@example.com 50 50 0 active 2026-01-15T12:00:00Z 5002 Suspicious Domain Suspicious Domain workstation-01 10.0.1.50 30 25 0 active 2026-01-15T09:45:00Z
vectra-investigation-query-send
Submit an investigation query and receive a request ID for retrieving results.
Base Command
vectra-investigation-query-send
Input
| Argument Name | Description | Required |
|---|---|---|
| query | Provide an investigation query in the supported query language. | Required |
| version | Specify the version of the query language. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.Investigation.request_id | String | The unique identifier for the query request. Use this to retrieve results. |
| Vectra.Investigation.searchable_range.searchable_days_allowed | Number | A Maximum number of days of data that can be searched. |
Command Example
!vectra-investigation-query-send query="SELECT * FROM detections" version=v1
Context Example
{
"Vectra": {
"Investigation": {
"request_id": "b57d7a27-28ad-4c0c-b28a-0e7b3",
"searchable_range": {
"searchable_days_allowed": 14
}
}
}
}
Human Readable Output
The Vectra investigation has started. You can view the results by executing the below command
!vectra-investigation-result-get id=b57d7a27-28ad-4c0c-b28a-0e7b3
vectra-investigation-result-get
Retrieve the results of a previously submitted investigation query using the request ID.
Base Command
vectra-investigation-result-get
Input
| Argument Name | Description | Required |
|---|---|---|
| id | Provide the unique request ID of investigation. | Required |
| page | Provide page number to retrieve. Default is 1. | Optional |
| page_size | Provide a number of results per page to retrieve. Default is 50. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Vectra.Investigation.request_id | String | The unique identifier for the query request. |
| Vectra.Investigation.data | Unknown | An array of query results. |
| Vectra.Investigation.meta.query_status | String | The status of the query. |
| Vectra.Investigation.meta.num_rows_available | Number | Total rows returned by the query. |
| Vectra.Investigation.meta.page | Number | The current page number. |
| Vectra.Investigation.meta.page_size | Number | The rows returned on this page. |
| Vectra.Investigation.meta.estimated_file_size_bytes | Number | The estimated size of the full result set in bytes. |
| Vectra.Investigation.meta.columns | Unknown | An array of tuples describing the result schema. |
Command Example
!vectra-investigation-result-get id=b57d7a27-28ad-4c0c-b28a-0e7b3
Context Example
{
"Vectra": {
"Investigation": {
"request_id": "b57d7a27-28ad-4c0c-b28a-0e7b3",
"meta": {
"page": 1,
"page_size": 50,
"estimated_file_size_bytes": 0,
"num_rows_available": 0,
"query_status": "SUCCESS",
"columns": [
[
"timestamp",
[
{
"type": "timestamp"
},
""
]
],
[
"orig_h",
[
{
"type": "string"
},
""
]
],
[
"resp_h",
[
{
"type": "string"
},
""
]
],
[
"resp_p",
[
{
"type": "number"
},
""
]
]
]
}
}
}
}
Human Readable Output
Investigation Result for Request ID: b57d7a27-28ad-4c0c-b28a-0e7b3
Query Status Page Number Page size Total Rows File Size (bytes) Columns SUCCESS 1 50 0 0 - values: timestamp, [{‘type’: ‘timestamp’}, ‘’]
- values: orig_h, [{‘type’: ‘string’}, ‘’]
- values: resp_h, [{‘type’: ‘string’}, ‘’]
- values: resp_p, [{‘type’: ‘number’}, ‘’]
Investigation Results Data
No entries.
Configuration parameters
server_url— Server URL (required)credentials— Client ID (required)isFetch— Fetch incidentsmax_fetch— Max Fetchfirst_fetch— First Fetch Timeentity_types— Entity Typesonly_prioritized_detections— Create Incidents for Prioritized Detectionsonly_escalated_detections— Create Incidents for Escalated Detectionsmirror_direction— Mirroring Directionnote_tag— Mirror tag for notesopen_detection_on_incident_reopen— Open Detection on Incident Reopendetection_status_for_reopen— Detection Status for Incident Reopenclose_detection_on_incident_closure— Close Detection on Incident Closureclose_reason_of_detection— Detection Close Reason for Incident ClosureincidentType— Incident typeincidentFetchInterval— Incidents Fetch Intervalinsecure— Trust any certificate (not secure)proxy— Use system proxy settings
Commands (36)
-
vectra-assignment-listReturns a list of all assignments.
-
vectra-detection-describeReturns a list of detections for the specified detection ID(s).
-
vectra-detection-external-id-updateUpdate the external reference ID for the provided detection ID(s).
-
vectra-detection-investigation-status-updateUpdate the investigation status of the detection by detection ID(s).
-
vectra-detection-listReturns a list of detections based on the specified filters.
-
vectra-detection-note-addAdd a note to the detection.
-
vectra-detection-note-listReturns a list of notes for a specified detection.
-
vectra-detection-note-removeRemove a note from the detection.
-
vectra-detection-note-updateUpdate a note in the detection.
-
vectra-detection-pcap-downloadDownload pcap of the detection.
-
vectra-detection-tag-addAdd tags to a detection.
-
vectra-detection-tag-listReturns a list of tags for a specified detection.
-
vectra-detection-tag-removeRemove tags from the 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-entity-assignment-addAdd an assignment for the entity.
-
vectra-entity-assignment-updateUpdate an assignment in the entity.
-
vectra-entity-describeDescribes an entity by ID.
-
vectra-entity-detection-listReturns a list of detections for a specified entity.
-
vectra-entity-detections-mark-asclosedMark the detections of the entity as closed with the provided entity ID in the argument.
-
vectra-entity-external-id-updateUpdate the external reference ID for the provided entity.
-
vectra-entity-listReturns a list of entities.
-
vectra-entity-note-addAdd a note to the entity.
-
vectra-entity-note-listReturns a list of notes for a specified entity.
-
vectra-entity-note-removeRemove a note from the entity.
-
vectra-entity-note-updateUpdate a note in the entity.
-
vectra-entity-tag-addAdd tags in the entity.
-
vectra-entity-tag-listReturns a list of tags for a specified entity.
-
vectra-entity-tag-removeRemove tags from the entity.
-
vectra-entity-unresolved-priority-resetUpdate the unresolved priority of an entity to false.
-
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-investigation-query-sendSubmit an investigation query and receive a request ID for retrieving results.
-
vectra-investigation-result-getRetrieve the results of a previously submitted investigation query using the request ID.
-
vectra-user-listReturns a list of users.
from pathlib import Path import pytest from requests.models import Response # type: ignore[import] from urllib.parse import urlencode import demistomock as demisto from CommonServerPython import * # noqa # pylint: disable=unused-wildcard-import import VectraRUXEventsDetections from VectraRUXEventsDetections import ( VectraEventsDetectionsClient, fetch_incidents, test_module as module_function, validate_fetch_params, map_severity, get_modified_remote_data_command, get_remote_data_command, update_remote_system_command, vectra_user_list_command, vectra_entity_list_command, vectra_entity_describe_command, vectra_entity_detection_list_command, vectra_detection_describe_command, vectra_entity_note_list_command, vectra_entity_note_add_command, vectra_entity_note_update_command, vectra_entity_note_remove_command, vectra_entity_tag_add_command, vectra_entity_tag_remove_command, vectra_entity_tag_list_command, vectra_assignment_list_command, vectra_entity_assignment_add_command, vectra_entity_assignment_update_command, vectra_detection_pcap_download_command, vectra_group_list_command, vectra_group_assign_command, vectra_group_unassign_command, vectra_entity_detections_mark_asclosed_command, vectra_detections_mark_asclosed_command, vectra_detections_mark_asopen_command, vectra_detection_tag_list_command, vectra_detection_tag_add_command, vectra_detection_tag_remove_command, vectra_detection_note_list_command, vectra_detection_note_add_command, vectra_detection_note_remove_command, vectra_detection_note_update_command, vectra_entity_unresolved_priority_reset_command, vectra_detection_investigation_status_update_command, vectra_detection_external_id_update_command, vectra_entity_external_id_update_command, vectra_detection_list_command, vectra_investigation_query_send_command, vectra_investigation_result_get_command, ERRORS, VALID_ENTITY_TYPES, VALID_DETECTION_STATUS, ENDPOINTS, VALID_CLOSE_REASON, VALID_ENTITY_TYPE, VALID_ENTITY_STATE, DETECTION_CATEGORY_TO_ARG, VALID_GROUP_TYPE, VALID_IMPORTANCE_VALUE, USER_ROLE_MAPPING, VALID_BOOL_VALUES, ) # Constants TEST_DATA_DIR = Path(__file__).parent / "test_data" BASE_URL = "https://serverurl.com" # Helper Functions def util_load_json(path): with open(path, encoding="utf-8") as f: return json.loads(f.read()) @pytest.fixture def client(mocker): mocker.patch.object(VectraEventsDetectionsClient, "_generate_tokens", return_value="test_token") return VectraEventsDetectionsClient(BASE_URL, "client_id", "client_secret_key", verify=False, proxy=False) def add_params_in_url(base_url: str, params: dict): encoded_params = urlencode(params) base_url = f"{base_url}?{encoded_params}" return base_url def test_generate_tokens(requests_mock): """ Given - Mocked response for generating access tokens. - VectraEventsDetectionsClient instance. When - Calling the `_generate_tokens` method. Then - Ensure the generated access token matches the expected access token. """ # Set up access_token = "access_token" refresh_token = "refresh_token" response_data = {"access_token": access_token, "refresh_token": refresh_token} requests_mock.post(os.path.join(BASE_URL, ENDPOINTS["AUTH_ENDPOINT"]), json=response_data, status_code=200) client = VectraEventsDetectionsClient(BASE_URL, "client_id", "client_secret_key", verify=True, proxy=False) token = client._generate_tokens() assert token == access_token def test_generate_tokens_failure(requests_mock): """ Given - Mocked failed response for generating access tokens. - VectraEventsDetectionsClient instance. When - Calling the `_generate_tokens` method. Then - Ensure the method raises an exception. """ access_token = "access_token" refresh_token = "refresh_token" response_data = {"access_token": access_token, "refresh_token": refresh_token} requests_mock.post(os.path.join(BASE_URL, ENDPOINTS["AUTH_ENDPOINT"]), json=response_data, status_code=500) client = VectraEventsDetectionsClient(BASE_URL, "client_id", "client_secret_key", verify=True, proxy=False) # Call the method with pytest.raises(Exception): client._generate_tokens() def test_generate_access_token_using_refresh_token(requests_mock, mocker): """ Given - Mocked response for generating access token using refresh token. - VectraEventsDetectionsClient instance. - Mocked `get_integration_context` method. When - Calling the `_generate_access_token_using_refresh_token` method. Then - Ensure the generated access token matches the expected access token. """ # Set up access_token = "access_token" response_data = { "access_token": access_token, } requests_mock.post(os.path.join(BASE_URL, ENDPOINTS["AUTH_ENDPOINT"]), json=response_data, status_code=200) client = VectraEventsDetectionsClient(BASE_URL, "client_id", "client_secret_key", verify=True, proxy=False) mocker.patch("CommonServerPython.get_integration_context", return_value={"refresh_token": "refresh_token"}) token = client._generate_access_token_using_refresh_token() assert token == access_token def test_generate_access_token_using_refresh_token_failure(requests_mock): """ Given - Mocked failed response for generating access token using refresh token. - VectraClient instance. When - Calling the `_generate_access_token_using_refresh_token` method. Then - Ensure the method raises an exception. """ access_token = "access_token" refresh_token = "refresh_token" response_data = {"access_token": access_token, "refresh_token": refresh_token} requests_mock.post(os.path.join(BASE_URL, ENDPOINTS["AUTH_ENDPOINT"]), json=response_data, status_code=500) client = VectraEventsDetectionsClient(BASE_URL, "client_id", "client_secret_key", verify=True, proxy=False) # Call the method with pytest.raises(Exception): client._generate_access_token_using_refresh_token() def test_generate_access_token_using_refresh_token_401_status_code(requests_mock, mocker, client): """ Given: - A client object. - A mocked HTTP POST request to the token endpoint with a status code of 401. - A mocked '_generate_tokens' method that raises an exception. When: - Calling the '_generate_access_token_using_refresh_token' method. Then: - Assert that an exception is raised. - Assert that the '_generate_tokens' method is called once. """ requests_mock.post(os.path.join(BASE_URL, ENDPOINTS["AUTH_ENDPOINT"]), status_code=401) generate_token = mocker.patch.object(client, "_generate_tokens", side_effect=Exception()) # Call the method with pytest.raises(Exception): client._generate_access_token_using_refresh_token() generate_token.assert_called_once() def test_http_request_with_valid_parameters(mocker, client): """ Given: - A mocked `_http_request` method. - A client object. When: - Making a request with valid parameters. Then: - Assert that the response status code is 200 (indicating a successful request). """ response = Response() response.status_code = 200 mocker.patch.object(BaseClient, "_http_request", return_value=response) response = client.http_request(method="GET", url_suffix="/test") assert response.status_code == 200 def test_http_request_with_invalid_parameters(mocker, client): """ Given: - A mocked `_http_request` method that raises an exception. - A client object. When: - Making a request with invalid parameters. Then: - Assert that the raised exception matches the expected exception. """ # Mock the `_http_request` method to raise an exception. mocker.patch.object(BaseClient, "_http_request", side_effect=Exception()) # Make a request with invalid parameters. with pytest.raises(Exception): client.http_request(method="GET", url_suffix="/test") def test_http_request_with_401_status_code(mocker, client): """ Given: - A mocked `_http_request` method that returns a response with a 401 status code. - A client object. When: - Making a request that results in a 401 status code. Then: - Assert that an exception is raised. - Assert that the `_generate_access_token_using_refresh_token` method is called once. """ response = Response() response.status_code = 401 mocker.patch.object(BaseClient, "_http_request", return_value=response) generate_token = mocker.patch.object(client, "_generate_access_token_using_refresh_token", side_effect=Exception()) with pytest.raises(Exception): client.http_request(method="GET", url_suffix="/test") generate_token.assert_called_once() def test_list_events_detections_request(mocker, client): """ Given: - A client object. - Mocked http_request method. When: - Calling list_events_detections_request with parameters. Then: - Assert that the method calls http_request with correct parameters. """ mock_response: dict = {"events": [], "next_checkpoint": None, "remaining_count": 0} mocker.patch.object(client, "http_request", return_value=mock_response) params = { "type": "host,account", "status": "open,escalated", "unresolved_priority": True, "limit": 50, "event_timestamp_gte": "2025-12-21T00:00:00Z", "ordering": "event_timestamp", } result = client.list_events_detections_request(params=params) assert result == mock_response client.http_request.assert_called_once_with( method="GET", url_suffix=ENDPOINTS["EVENTS_DETECTIONS_ENDPOINT"], params=params, response_type="json", ) def test_list_events_detections_request_with_from_checkpoint(mocker, client): """ Given: - A client object. - Mocked http_request method. - Parameters including 'from' checkpoint. When: - Calling list_events_detections_request with 'from' parameter. Then: - Assert that the 'from' parameter is correctly added to params. """ mock_response = {"events": [], "next_checkpoint": "checkpoint123", "remaining_count": 0} mocker.patch.object(client, "http_request", return_value=mock_response) params = { "type": "host", "limit": 50, "from": "checkpoint123", } result = client.list_events_detections_request(params=params) assert result == mock_response call_args = client.http_request.call_args assert call_args[1]["params"]["from"] == "checkpoint123" def test_test_module_success(mocker, client): """ Given - VectraRUXEventsDetections test module When - mock the demisto params. - mock the VectraEventsDetectionsClient's generate_tokens. - mock the VectraEventsDetectionsClient. - mock the VectraEventsDetectionsClient's list_events_detections_request. Then - run the test_module command using the Client Validate The response is ok. """ mocker.patch.object(demisto, "params", return_value={"isFetch": False}) mock_response: dict = {"events": [], "next_checkpoint": None, "remaining_count": 0} mocker.patch.object(client, "list_events_detections_request", return_value=mock_response) result = module_function(client, {}) assert result == "ok" def test_test_module_with_fetch_enabled(mocker, client): """ Given - VectraRUXEventsDetections test module and fetch incident is enabled When - mock the VectraEventsDetectionsClient's generate_tokens. - mock the VectraEventsDetectionsClient. - mock the fetch_incidents function. Then - run the test_module command using the Client Validate The response is ok. """ params = { "isFetch": True, "first_fetch": "1 hour", "max_fetch": "50", "entity_types": "Host,Account", "unresolved_priority": "Yes", "detection_status": "Escalated", } mocker.patch.object(demisto, "params", return_value=params) mocker.patch("VectraRUXEventsDetections.fetch_incidents", return_value=([], {})) result = module_function(client, params) assert result == "ok" def test_validate_fetch_params_success(): """ Given: - Valid fetch parameters and last_run data. When: - Calling validate_fetch_params with valid parameters. Then: - Assert that the validated parameters are correctly formatted. """ params = { "first_fetch": "2025-12-25T00:00:00Z", "max_fetch": 50, "entity_types": "Host,Account", "only_prioritized_detections": "Yes", "only_escalated_detections": "No", } last_run = {"event_timestamp": "2025-12-21T00:00:00Z", "from": "", "was_fetched": []} result = validate_fetch_params(params, last_run) assert result["type"] == "account,host" assert result["investigation_status"] == "acknowledged,escalated,open,paused" assert result["unresolved_priority"] is True assert result["limit"] == 50 assert result["event_timestamp_gte"] == last_run.get("event_timestamp") @pytest.mark.parametrize("max_fetch", [201, -1, 0]) def test_validate_fetch_params_invalid_max_fetch(max_fetch): """ Given: - Invalid max_fetch parameter. When: - Calling validate_fetch_params with invalid max_fetch. Then: - Assert that ValueError is raised. """ params = { "first_fetch": "1 hour", "max_fetch": max_fetch, } last_run: dict = {} with pytest.raises(ValueError) as exception: validate_fetch_params(params, last_run, is_test=True) assert ERRORS["INVALID_MAX_FETCH"].format(max_fetch) in str(exception.value) def test_validate_fetch_params_max_fetch_exceeds_limit(mocker): """ Given: - max_fetch parameter exceeding MAX_FETCH limit. When: - Calling validate_fetch_params with max_fetch > 200. Then: - Assert that max_fetch is capped at 200. """ params = { "first_fetch": "1 hour", "max_fetch": 300, } last_run: dict = {} mocker.patch.object(demisto, "debug") result = validate_fetch_params(params, last_run) assert result["limit"] == 200 def test_validate_fetch_params_invalid_entity_type(): """ Given: - Invalid entity_type parameter. When: - Calling validate_fetch_params with invalid entity_type. Then: - Assert that ValueError is raised. """ params = { "first_fetch": "1 hour", "max_fetch": 50, "entity_types": "InvalidType", } last_run: dict = {} with pytest.raises(ValueError) as exception: validate_fetch_params(params, last_run, is_test=True) assert ERRORS["INVALID_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPES)) in str(exception.value) def test_validate_fetch_params_invalid_entity_type_no_test_module(): """ Given: - Invalid entity_type parameter. When: - Calling validate_fetch_params with invalid entity_type. Then: - Assert that ValueError is raised. """ params = { "first_fetch": "1 hour", "max_fetch": 50, "entity_types": "InvalidType", } last_run: dict = {} valid_params = validate_fetch_params(params, last_run) assert valid_params["type"] == "account,host" def test_validate_fetch_params_invalid_detection_status(): """ Given: - Valid parameters (detection_status is no longer validated as it's not used). When: - Calling validate_fetch_params. Then: - Assert that default detection statuses are used. """ params = { "first_fetch": "1 hour", "max_fetch": 50, "entity_types": "Host", } last_run: dict = {} result = validate_fetch_params(params, last_run, is_test=True) assert result["investigation_status"] == "acknowledged,escalated,open,paused" def test_validate_fetch_params_invalid_detection_status_no_test_module(): """ Given: - Valid parameters (detection_status is no longer validated as it's not used). When: - Calling validate_fetch_params. Then: - Assert that default detection statuses are used. """ params = { "first_fetch": "1 hour", "max_fetch": 50, "entity_types": "Host", } last_run: dict = {} valid_params = validate_fetch_params(params, last_run) assert valid_params["investigation_status"] == "acknowledged,escalated,open,paused" def test_validate_fetch_params_with_from_checkpoint(): """ Given: - Valid parameters with from checkpoint in last_run. When: - Calling validate_fetch_params with from checkpoint. Then: - Assert that 'from' parameter is included in result. """ params = { "first_fetch": "1 hour", "max_fetch": 50, "only_prioritized_detections": False, "only_escalated_detections": False, } last_run = { "event_timestamp": "2025-12-21T00:00:00Z", "from": "checkpoint123", "was_fetched": [], "selected_statuses": "acknowledged,escalated,open,paused", "selected_types": "account,host", "unresolved_priority": "", } result = validate_fetch_params(params, last_run) assert result.get("from") == "checkpoint123" @pytest.mark.parametrize( "urgency_score,expected_severity", [ (90, 4), (80, 3), (70, 3), (60, 3), (50, 2), (40, 2), (30, 1), (20, 1), (10, 1), (0, 0.5), ], ) def test_map_severity(urgency_score, expected_severity): """ Given: - Different severity values. When: - Calling map_severity with various severity values. Then: - Assert that severity is correctly mapped to incident severity. """ # map_severity logic: > 8 -> 4, > 5 -> 3, > 3 -> 2, else -> 1 assert map_severity(urgency_score) == expected_severity def test_fetch_incidents_first_run(mocker, client): """ Given: - A client object. - A mocked 'getLastRun' method that returns an empty dictionary. - A mocked 'list_events_detections_request' method that returns sample events data. When: - Fetching incidents using the 'fetch_incidents' function with no previous run. Then: - Assert that the number of fetched incidents matches the number of events. - Assert incident properties are correctly set. """ events_data = util_load_json(f"{TEST_DATA_DIR}/events_detections_response.json") mocker.patch.object(demisto, "getLastRun", return_value={}) mocker.patch.object(demisto, "setLastRun") mocker.patch.object(client, "list_events_detections_request", return_value=events_data) params = { "first_fetch": "1 hour", "max_fetch": 50, "entity_types": "Host,Account", "unresolved_priority": "Yes", "detection_status": "Open,Escalated", } incidents, next_run = fetch_incidents(client, params, {}) assert len(incidents) == 2 assert incidents[0]["name"] == "Vectra RUX: Dummy_Category_1 - Dummy Detection One - dummy-entity-one" assert incidents[0]["occurred"] == events_data.get("events")[0].get("event_timestamp", "") assert incidents[0]["severity"] == 2 # severity 4 maps to 2 (Medium) assert "rawJSON" in incidents[0] assert next_run["event_timestamp"] == events_data.get("events")[-1].get("event_timestamp", "") assert next_run["from"] == 200002 or next_run["from"] == "200002" # next_checkpoint can be int or str def test_fetch_incidents_with_last_run(mocker, client): """ Given: - A client object. - A mocked 'getLastRun' method that returns last run data. - A mocked 'list_events_detections_request' method that returns sample events data. When: - Fetching incidents using the 'fetch_incidents' function with previous run data. Then: - Assert that incidents are fetched using the last run checkpoint. """ events_data = util_load_json(f"{TEST_DATA_DIR}/events_detections_response.json") last_run = { "event_timestamp": "2025-12-21T00:00:00Z", "from": "checkpoint123", "was_fetched": [], "selected_statuses": "acknowledged,escalated,open,paused", "selected_types": "account,host", "unresolved_priority": "", } mocker.patch.object(demisto, "getLastRun", return_value=last_run) mocker.patch.object(demisto, "setLastRun") mocker.patch.object(client, "list_events_detections_request", return_value=events_data) params = { "first_fetch": "1 hour", "max_fetch": 50, } incidents, _ = fetch_incidents(client, params, last_run) assert len(incidents) == 2 # Verify that the API was called with the from checkpoint call_args = client.list_events_detections_request.call_args assert call_args[1]["params"]["from"] == "checkpoint123" def test_fetch_incidents_duplicate_detection_id(mocker, client): """ Given: - A client object. - Events with duplicate detection IDs (already fetched). When: - Fetching incidents where some detections were already fetched. Then: - Assert that duplicate detections are not included in incidents. """ events_data = util_load_json(f"{TEST_DATA_DIR}/events_detections_response.json") last_run = { "event_timestamp": "2025-12-21T00:00:00Z", "from": "", "was_fetched": [11111], # First detection ID already fetched } mocker.patch.object(demisto, "getLastRun", return_value=last_run) mocker.patch.object(demisto, "setLastRun") mocker.patch.object(client, "list_events_detections_request", return_value=events_data) params = { "first_fetch": "1 hour", "max_fetch": 50, } incidents, next_run = fetch_incidents(client, params, last_run) assert len(incidents) == 1 # Only second detection should be fetched assert incidents[0]["rawJSON"] # Verify rawJSON contains the event data assert next_run["was_fetched"] == [11111, 22222] def test_fetch_incidents_empty_response(mocker, client): """ Given: - A client object. - An empty events response. When: - Fetching incidents when no events are returned. Then: - Assert that no incidents are created. - Assert that last_run is updated with current time. """ empty_response: dict = {"events": [], "next_checkpoint": None, "remaining_count": 0} last_run = { "event_timestamp": "2025-12-21T00:00:00Z", "from": "", "was_fetched": [], } mocker.patch.object(demisto, "getLastRun", return_value=last_run) mocker.patch.object(demisto, "setLastRun") mocker.patch.object(client, "list_events_detections_request", return_value=empty_response) params = { "first_fetch": "1 hour", "max_fetch": 50, } incidents, next_run = fetch_incidents(client, params, last_run) assert len(incidents) == 0 assert "event_timestamp" in next_run assert next_run["from"] == "" def test_fetch_incidents_with_test_flag(mocker, client): """ Given: - A client object. - is_test flag set to True. When: - Fetching incidents with is_test=True. Then: - Assert that empty incidents and empty last_run are returned. """ events_data = util_load_json(f"{TEST_DATA_DIR}/events_detections_response.json") mocker.patch.object(client, "list_events_detections_request", return_value=events_data) params = { "first_fetch": "1 hour", "max_fetch": 50, } incidents, next_run = fetch_incidents(client, params, {}, is_test=True) assert len(incidents) == 0 assert next_run == {} def test_fetch_incidents_severity_mapping(mocker, client): """ Given: - A client object. - Events with different severity values. When: - Fetching incidents. Then: - Assert that severity is correctly mapped using map_severity function. """ events_data = util_load_json(f"{TEST_DATA_DIR}/events_detections_response.json") mocker.patch.object(demisto, "getLastRun", return_value={}) mocker.patch.object(demisto, "setLastRun") mocker.patch.object(client, "list_events_detections_request", return_value=events_data) params = { "first_fetch": "1 hour", "max_fetch": 50, } incidents, _ = fetch_incidents(client, params, {}) # First event has severity 4 -> mapped to 2 (Medium) assert incidents[0]["severity"] == 2 # Second event has severity 8 -> mapped to 3 (High) assert incidents[1]["severity"] == 3 def test_fetch_incidents_api_error(mocker, client): """ Given: - A client object. - An API error when fetching events. When: - Fetching incidents when API returns an error. Then: - Assert that DemistoException is raised. """ from CommonServerPython import DemistoException mocker.patch.object(demisto, "getLastRun", return_value={}) mocker.patch.object(client, "list_events_detections_request", side_effect=DemistoException("API Error")) params = { "first_fetch": "1 hour", "max_fetch": 50, } with pytest.raises(DemistoException): fetch_incidents(client, params, {}) def test_fetch_incidents_with_next_checkpoint(mocker, client): """ Given: - A client object. - Events response with next_checkpoint. When: - Fetching incidents with pagination checkpoint. Then: - Assert that next_run contains the next_checkpoint. """ events_data = util_load_json(f"{TEST_DATA_DIR}/events_detections_response.json") mocker.patch.object(demisto, "getLastRun", return_value={}) mocker.patch.object(demisto, "setLastRun") mocker.patch.object(client, "list_events_detections_request", return_value=events_data) params = { "first_fetch": "1 hour", "max_fetch": 50, } _, next_run = fetch_incidents(client, params, {}) assert next_run["from"] == 200002 or next_run["from"] == "200002" # next_checkpoint from response (can be int or str) def test_validate_fetch_params_defaults(): """ Given: - Parameters with default values. When: - Calling validate_fetch_params with minimal parameters. Then: - Assert that default values are used correctly. """ params = { "first_fetch": "1 hour", "max_fetch": 50, } last_run: dict = {} result = validate_fetch_params(params, last_run) assert result["investigation_status"] == "acknowledged,escalated,open,paused" # Default detection status assert "unresolved_priority" not in result # Default unresolved priority def test_validate_fetch_params_comma_separated_values(): """ Given: - Parameters with comma-separated entity types. When: - Calling validate_fetch_params with comma-separated values. Then: - Assert that values are correctly parsed and joined. """ params = { "first_fetch": "1 hour", "max_fetch": 50, "entity_types": "Host, Account", "only_prioritized_detections": False, "only_escalated_detections": False, } last_run: dict = {} result = validate_fetch_params(params, last_run) assert result["type"] == "account,host" assert result["investigation_status"] == "acknowledged,escalated,open,paused" def test_validate_fetch_params_empty_detection_status_uses_default(): """ Given: - Parameters with default flags. When: - Calling validate_fetch_params. Then: - Assert that default detection status is used. """ params = { "first_fetch": "1 hour", "max_fetch": 50, } last_run: dict = {} result = validate_fetch_params(params, last_run) assert result["investigation_status"] == "acknowledged,escalated,open,paused" # Default def test_validate_fetch_params_unresolved_priority_false(): """ Given: - Parameters with unresolved_priority set to "No". When: - Calling validate_fetch_params with unresolved_priority="No". Then: - Assert that unresolved_priority is False. """ params = { "first_fetch": "1 hour", "max_fetch": 50, "only_prioritized_detections": False, } last_run: dict = {} result = validate_fetch_params(params, last_run) assert result.get("unresolved_priority") is None def test_validate_fetch_params_uses_last_run_timestamp(): """ Given: - Parameters and last_run with event_timestamp. When: - Calling validate_fetch_params. Then: - Assert that last_run event_timestamp is used instead of first_fetch. """ params = { "first_fetch": "2025-12-25T10:00:00Z", "max_fetch": 50, } last_run = { "event_timestamp": "2025-12-21T10:00:00Z", "from": "", "was_fetched": [], } result = validate_fetch_params(params, last_run) assert result["event_timestamp_gte"] == last_run.get("event_timestamp") def test_main_function_test_module(mocker, client): """ Given: - Mocked demisto.command() returning "test-module". When: - Calling main function with test-module command. Then: - Assert that test_module is called and returns "ok". """ mocker.patch.object(demisto, "params", return_value={"isFetch": False}) mocker.patch.object(demisto, "command", return_value="test-module") mocker.patch.object(demisto, "args", return_value={}) mocker.patch.object(client, "list_events_detections_request", return_value={"events": []}) mocker.patch("VectraRUXEventsDetections.VectraEventsDetectionsClient", return_value=client) mocker.patch("VectraRUXEventsDetections.return_results") VectraRUXEventsDetections.main() VectraRUXEventsDetections.return_results.assert_called_once_with("ok") # type: ignore[attr-defined] def test_main_function_fetch_incidents(mocker, client): """ Given: - Mocked demisto.command() returning "fetch-incidents". When: - Calling main function with fetch-incidents command. Then: - Assert that fetch_incidents is called and incidents are set. """ events_data = util_load_json(f"{TEST_DATA_DIR}/events_detections_response.json") mocker.patch.object(demisto, "params", return_value={"first_fetch": "1 hour", "max_fetch": 50}) mocker.patch.object(demisto, "command", return_value="fetch-incidents") mocker.patch.object(demisto, "getLastRun", return_value={}) mocker.patch.object(demisto, "setLastRun") mocker.patch.object(demisto, "incidents") mocker.patch.object(client, "list_events_detections_request", return_value=events_data) mocker.patch("VectraRUXEventsDetections.VectraEventsDetectionsClient", return_value=client) VectraRUXEventsDetections.main() demisto.incidents.assert_called_once() # type: ignore[attr-defined] call_args = demisto.incidents.call_args[0][0] # type: ignore[attr-defined] assert len(call_args) == 2 def test_main_function_invalid_command(mocker, client): """ Given: - Mocked demisto.command() returning invalid command. When: - Calling main function with invalid command. Then: - Assert that NotImplementedError is raised. """ mocker.patch.object(demisto, "params", return_value={}) mocker.patch.object(demisto, "command", return_value="invalid-command") mocker.patch.object(demisto, "args", return_value={}) mocker.patch("VectraRUXEventsDetections.VectraEventsDetectionsClient", return_value=client) mocker.patch("VectraRUXEventsDetections.return_error") VectraRUXEventsDetections.main() VectraRUXEventsDetections.return_error.assert_called_once() # type: ignore[attr-defined] def test_main_function_exception_handling(mocker, client): """ Given: - Mocked demisto.command() that raises an exception. When: - Calling main function when an exception occurs. Then: - Assert that return_error is called with error message. """ mocker.patch.object(demisto, "params", return_value={}) mocker.patch.object(demisto, "command", return_value="test-module") mocker.patch.object(demisto, "args", return_value={}) mocker.patch("VectraRUXEventsDetections.VectraEventsDetectionsClient", side_effect=Exception("Test error")) mocker.patch("VectraRUXEventsDetections.return_error") VectraRUXEventsDetections.main() VectraRUXEventsDetections.return_error.assert_called_once() # type: ignore[attr-defined] assert "Test error" in str(VectraRUXEventsDetections.return_error.call_args[0][0]) # type: ignore[attr-defined] @pytest.mark.parametrize("close_reason", ["benign", "remediated"]) def test_mark_detections_asclosed_command_valid_close(requests_mock, client, close_reason): """ Tests mark_detection_as_closed_command with valid close reason. """ response = {"_meta": {"level": "success", "message": f"Successfully closed detection as {close_reason}"}} status_response = {"message": {"success": ["Successfully updated detection statuses"]}, "_meta": {"level": "success"}} requests_mock.patch(os.path.join(BASE_URL, ENDPOINTS["CLOSE_DETECTIONS_ENDPOINT"]), json=response) requests_mock.patch(os.path.join(BASE_URL, ENDPOINTS["DETECTION_ENDPOINT"]), json=status_response) args = {"detection_ids": "123,234", "close_reason": close_reason} result = vectra_detections_mark_asclosed_command(client=client, args=args) assert result.outputs is None assert result.readable_output == f"##### The provided detection IDs have been successfully closed as {close_reason}." assert result.raw_response == response @pytest.mark.parametrize( "args,error_msg", [ ({}, ERRORS["REQUIRED_ARGUMENT"].format("detection_ids")), ({"detection_ids": "abc"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_ids", "abc")), ({"detection_ids": "0"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_ids", "0")), ({"detection_ids": "-5"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_ids", "-5")), ({"detection_ids": "123,abc,456"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_ids", "abc")), ({"detection_ids": "123"}, ERRORS["REQUIRED_ARGUMENT"].format("close_reason")), ( {"detection_ids": "123", "close_reason": "invalid"}, ERRORS["INVALID_ARG_VALUE"].format("close_reason", ", ".join(VALID_CLOSE_REASON)), ), ], ) def test_mark_detections_asclosed_command_invalid_args(args, client, error_msg): """ Tests mark_detections_asclosed_command with invalid arguments. """ with pytest.raises(ValueError) as err: vectra_detections_mark_asclosed_command(client=client, args=args) assert str(err.value) == error_msg def test_get_modified_remote_data_command_successful_retrieval(mocker, client): """ Given: - A client object. - A mocked list_events_detections_request that returns events. When: - Calling the 'get_modified_remote_data_command' function with valid arguments. Then: - Assert that the function returns a GetModifiedRemoteDataResponse with modified incident IDs. """ events_data = util_load_json(f"{TEST_DATA_DIR}/events_detections_response.json") # Set remaining_count to 0 to avoid infinite loop events_data["remaining_count"] = 0 mocker.patch.object(client, "list_events_detections_request", return_value=events_data) args = {"lastUpdate": "2025-01-01T00:00:00+00:00"} result = get_modified_remote_data_command(client, args) assert result.modified_incident_ids is not None assert "11111" in result.modified_incident_ids assert "22222" in result.modified_incident_ids def test_get_modified_remote_data_command_empty_response(mocker, client): """ Given: - A client object. - A mocked list_events_detections_request that returns empty events. When: - Calling the 'get_modified_remote_data_command' function with valid arguments. Then: - Assert that the function returns a GetModifiedRemoteDataResponse with empty modified incident IDs. """ empty_response: dict = {"events": [], "next_checkpoint": None, "remaining_count": 0} mocker.patch.object(client, "list_events_detections_request", return_value=empty_response) args = {"lastUpdate": "2025-01-01T00:00:00+00:00"} result = get_modified_remote_data_command(client, args) assert result.modified_incident_ids == [] def test_get_modified_remote_data_command_with_pagination(mocker, client): """ Given: - A client object. - A mocked list_events_detections_request that returns events with remaining_count > 0 initially. When: - Calling the 'get_modified_remote_data_command' function with valid arguments. Then: - Assert that the function handles pagination and collects all modified incident IDs. """ first_response = { "events": [{"detection_id": 11111, "event_timestamp": "2025-01-01T01:00:00Z"}], "next_checkpoint": "checkpoint123", "remaining_count": 1, } second_response: dict = { "events": [{"detection_id": 22222, "event_timestamp": "2025-01-01T02:00:00Z"}], "next_checkpoint": "checkpoint456", "remaining_count": 0, } mocker.patch.object(client, "list_events_detections_request", side_effect=[first_response, second_response]) args = {"lastUpdate": "2025-01-01T00:00:00+00:00"} result = get_modified_remote_data_command(client, args) assert "11111" in result.modified_incident_ids assert "22222" in result.modified_incident_ids def test_get_modified_remote_data_command_api_error(mocker, client): """ Given: - A client object. - A mocked list_events_detections_request that raises a DemistoException. When: - Calling the 'get_modified_remote_data_command' function with valid arguments. Then: - Assert that the DemistoException is raised. """ mocker.patch.object(client, "list_events_detections_request", side_effect=DemistoException("API Error")) args = {"lastUpdate": "2025-01-01T00:00:00+00:00"} with pytest.raises(DemistoException): get_modified_remote_data_command(client, args) def test_get_modified_remote_data_command_filters_duplicates(mocker, client): """ Given: - A client object. - A mocked list_events_detections_request that returns events with duplicate detection IDs. When: - Calling the 'get_modified_remote_data_command' function with valid arguments. Then: - Assert that duplicate incident IDs are filtered out. """ response_with_duplicates = { "events": [ {"detection_id": 11111, "event_timestamp": "2025-01-01T01:00:00Z"}, {"detection_id": 11111, "event_timestamp": "2025-01-01T02:00:00Z"}, {"detection_id": 22222, "event_timestamp": "2025-01-01T03:00:00Z"}, ], "next_checkpoint": None, "remaining_count": 0, } mocker.patch.object(client, "list_events_detections_request", return_value=response_with_duplicates) args = {"lastUpdate": "2025-01-01T00:00:00+00:00"} result = get_modified_remote_data_command(client, args) assert len(result.modified_incident_ids) == 2 assert "11111" in result.modified_incident_ids assert "22222" in result.modified_incident_ids def test_get_remote_data_command_detection_found(mocker, client): """ Given: - A client object. - A mocked list_events_detections_request that returns event data. When: - Calling the 'get_remote_data_command' function with valid arguments. Then: - Assert that the function returns a GetRemoteDataResponse with the detection data. """ events_data = util_load_json(f"{TEST_DATA_DIR}/events_detections_response.json") mocker.patch.object(client, "list_events_detections_request", return_value=events_data) args = {"id": "11111", "lastUpdate": "2024-01-01T00:00:00+00:00"} result = get_remote_data_command(client, args) assert result.mirrored_object is not None assert result.mirrored_object.get("detection_id") == 11111 def test_get_remote_data_command_detection_not_found(mocker, client): """ Given: - A client object. - A mocked list_events_detections_request that returns empty events. When: - Calling the 'get_remote_data_command' function with a detection ID that doesn't exist. Then: - Assert that the function returns "Incident was not found." message. """ empty_response: dict = {"events": [], "next_checkpoint": None, "remaining_count": 0} mocker.patch.object(client, "list_events_detections_request", return_value=empty_response) args = {"id": "99999", "lastUpdate": "2024-01-01T00:00:00+00:00"} result = get_remote_data_command(client, args) assert result == "Incident was not found." def test_get_remote_data_command_detection_updated(mocker, client): """ Given: - A client object. - A mocked list_events_detections_request that returns updated event data. When: - Calling the 'get_remote_data_command' function where the event_timestamp is newer than lastUpdate. Then: - Assert that the function returns a GetRemoteDataResponse with updated detection data. """ response_new_append = { "events": [ { "detection_id": 11111, "event_timestamp": "2025-01-15T10:00:00Z", "detection_href": "https://dummy.url/detections/11111", "url": "https://dummy.url/entities/11", "status": "escalated", } ], "next_checkpoint": None, "remaining_count": 0, } response_other = { "events": [ { "detection_id": 11111, "event_timestamp": "2025-01-14T10:00:00Z", "priority": "high", } ], "next_checkpoint": None, "remaining_count": 0, } mocker.patch.object(client, "list_events_detections_request", side_effect=[response_new_append, response_other]) args = {"id": "11111", "lastUpdate": "2025-01-01T00:00:00+00:00"} result = get_remote_data_command(client, args) assert result.mirrored_object is not None assert result.mirrored_object.get("detection_id") == 11111 assert "pivot=Vectra-RUX-XSOAR" in result.mirrored_object.get("detection_href", "") def test_get_remote_data_command_nothing_new(mocker, client): """ Given: - A client object. - A mocked list_events_detections_request that returns event data with old event_timestamp. When: - Calling the 'get_remote_data_command' function where the event_timestamp is older than lastUpdate. Then: - Assert that the function returns a GetRemoteDataResponse (no new updates). """ response = { "events": [ { "detection_id": 11111, "event_timestamp": "2025-01-01T00:00:00Z", "detection_href": "https://dummy.url/detections/11111", "url": "https://dummy.url/entities/11", } ], "next_checkpoint": None, "remaining_count": 0, } mocker.patch.object(client, "list_events_detections_request", return_value=response) args = {"id": "11111", "lastUpdate": "2025-01-15T00:00:00+00:00"} result = get_remote_data_command(client, args) assert result.mirrored_object is not None assert result.entries == [] def test_update_remote_system_command_with_notes(mocker, client): """ Given: - A client object. - Mocked arguments with notes to be mirrored. When: - Calling the 'update_remote_system_command' function with valid arguments containing notes. Then: - Assert that the remote incident ID is returned. - Assert that add_note_to_detection_request is called. """ mock_args = util_load_json(f"{TEST_DATA_DIR}/update_remote_system_args.json") mocker.patch.object(client, "list_detection_tags_request", return_value={}) mocker.patch.object(client, "update_detection_tags_request", return_value={}) add_note_mock = mocker.patch.object(client, "add_note_to_detection_request", return_value={}) remote_incident_id = update_remote_system_command(client, mock_args, {}) assert remote_incident_id == "12345" add_note_mock.assert_called_once() def test_update_remote_system_command_with_tags(mocker, client): """ Given: - A client object. - Mocked arguments with tags to be mirrored. When: - Calling the 'update_remote_system_command' function with valid arguments containing tags. Then: - Assert that the remote incident ID is returned. - Assert that update_detection_tags_request is called with the tags. """ mock_args = util_load_json(f"{TEST_DATA_DIR}/update_remote_system_args.json") mocker.patch.object(client, "list_detection_tags_request", return_value={}) update_tags_mock = mocker.patch.object(client, "update_detection_tags_request", return_value={}) mocker.patch.object(client, "add_note_to_detection_request", return_value={}) remote_incident_id = update_remote_system_command(client, mock_args, {}) assert remote_incident_id == "12345" update_tags_mock.assert_called_once() def test_update_remote_system_command_remove_all_tags(mocker, client): """ Given: - A client object. - Mocked arguments where all tags are removed from XSOAR. When: - Calling the 'update_remote_system_command' function with arguments indicating tag removal. Then: - Assert that the remote incident ID is returned. - Assert that update_detection_tags_request is called to remove tags. """ mock_args = util_load_json(f"{TEST_DATA_DIR}/update_remote_system_args.json") mock_args["delta"]["tags"] = [] mocker.patch.object(client, "list_detection_tags_request", return_value={"tags": ["tag1", "tag2"]}) update_tags_mock = mocker.patch.object(client, "update_detection_tags_request", return_value={}) mocker.patch.object(client, "add_note_to_detection_request", return_value={}) remote_incident_id = update_remote_system_command(client, mock_args, {}) assert remote_incident_id == "12345" update_tags_mock.assert_called_once_with(detection_id="12345", tags=[]) def test_update_remote_system_command_with_detection_status(mocker, client, requests_mock): """ Given: - A client object. - Mocked arguments with detection status to be updated. When: - Calling the 'update_remote_system_command' function with detection status. Then: - Assert that update_detection_status_request is called with the correct status. """ mock_args = util_load_json(f"{TEST_DATA_DIR}/update_remote_system_args.json") mock_args["data"]["vectraruxinvestigationstatus"] = "closed" mocker.patch.object(client, "list_detection_tags_request", return_value={}) mocker.patch.object(client, "update_detection_tags_request", return_value={}) mocker.patch.object(client, "add_note_to_detection_request", return_value={}) requests_mock.patch(os.path.join(BASE_URL, ENDPOINTS["DETECTION_ENDPOINT"]), json={}) remote_incident_id = update_remote_system_command(client, mock_args, {}) assert remote_incident_id == "12345" def test_update_remote_system_command_with_priority_status(mocker, client, requests_mock): """ Given: - A client object. - Mocked arguments with priority status to be updated to Not Prioritized. When: - Calling the 'update_remote_system_command' function with priority status. Then: - Assert that update_entity_unresolved_priority_status_request is called. """ mock_args = util_load_json(f"{TEST_DATA_DIR}/update_remote_system_args.json") mock_args["data"]["vectraruxentityprioritystatus"] = "Not Prioritized" mock_args["data"]["vectraruxentityunresolvedprioritystatus"] = "" entity_id = mock_args.get("data").get("vectraruxentityid") mocker.patch.object(client, "list_detection_tags_request", return_value={}) mocker.patch.object(client, "update_detection_tags_request", return_value={}) mocker.patch.object(client, "add_note_to_detection_request", return_value={}) requests_mock.patch(os.path.join(BASE_URL, f"{ENDPOINTS['ENTITY_ENDPOINT']}/{entity_id}"), json={}) remote_incident_id = update_remote_system_command(client, mock_args, {}) assert remote_incident_id == "12345" def test_update_remote_system_command_with_closing_notes(mocker, client): """ Given: - A client object. - Mocked arguments with closing notes for incident closure. When: - Calling the 'update_remote_system_command' function with closing notes. Then: - Assert that add_note_to_detection_request is called with the closing note. """ mock_args = util_load_json(f"{TEST_DATA_DIR}/update_remote_system_args.json") mock_args["data"]["closeNotes"] = "Closing this incident due to resolution" mock_args["data"]["closeReason"] = "Resolved" mock_args["data"]["closingUserId"] = "admin_user" mock_args["delta"]["closingUserId"] = "admin_user" mock_args["entries"] = [] mocker.patch.object(client, "list_detection_tags_request", return_value={}) mocker.patch.object(client, "update_detection_tags_request", return_value={}) add_note_mock = mocker.patch.object(client, "add_note_to_detection_request", return_value={}) remote_incident_id = update_remote_system_command(client, mock_args, {}) assert remote_incident_id == "12345" add_note_mock.assert_called_once() call_args = add_note_mock.call_args assert "Mirrored From XSOAR" in call_args[1]["note"] assert "Close Reason: Resolved" in call_args[1]["note"] assert "Closed By: admin_user" in call_args[1]["note"] assert "Close Notes: Closing this incident due to resolution" in call_args[1]["note"] def test_update_remote_system_command_no_changes(mocker, client): """ Given: - A client object. - Mocked arguments with no changes (empty delta, no entries). When: - Calling the 'update_remote_system_command' function with no changes. Then: - Assert that the remote incident ID is returned without making any API calls. """ mock_args = util_load_json(f"{TEST_DATA_DIR}/update_remote_system_args.json") mock_args["delta"] = {} mock_args["entries"] = [] mocker.patch.object(client, "list_detection_tags_request", return_value={}) update_tags_mock = mocker.patch.object(client, "update_detection_tags_request", return_value={}) add_note_mock = mocker.patch.object(client, "add_note_to_detection_request", return_value={}) remote_incident_id = update_remote_system_command(client, mock_args, {}) assert remote_incident_id == "12345" update_tags_mock.assert_not_called() add_note_mock.assert_not_called() def test_update_remote_system_command_note_exceeds_limit(mocker, client): """ Given: - A client object. - Mocked arguments with a note that exceeds the maximum character limit. When: - Calling the 'update_remote_system_command' function with a long note. Then: - Assert that the note is skipped and info is logged. """ mock_args = util_load_json(f"{TEST_DATA_DIR}/update_remote_system_args.json") mock_args["entries"] = [ { "id": "note_id", "type": "note", "contents": "A" * 9000, # Exceeds MAX_OUTGOING_NOTE_LIMIT (8000) "user": "user1", } ] mocker.patch.object(client, "list_detection_tags_request", return_value={}) mocker.patch.object(client, "update_detection_tags_request", return_value={}) add_note_mock = mocker.patch.object(client, "add_note_to_detection_request", return_value={}) info_mock = mocker.patch.object(demisto, "info") remote_incident_id = update_remote_system_command(client, mock_args, {}) assert remote_incident_id == "12345" add_note_mock.assert_called_once() info_mock.assert_called() def test_main_function_get_modified_remote_data(mocker, client): """ Given: - Mocked demisto.command() returning "get-modified-remote-data". When: - Calling main function with get-modified-remote-data command. Then: - Assert that get_modified_remote_data_command is called. """ events_data: dict = {"events": [], "next_checkpoint": None, "remaining_count": 0} mocker.patch.object(demisto, "params", return_value={}) mocker.patch.object(demisto, "command", return_value="get-modified-remote-data") mocker.patch.object(demisto, "args", return_value={"lastUpdate": "2025-01-01T00:00:00+00:00"}) mocker.patch.object(client, "list_events_detections_request", return_value=events_data) mocker.patch("VectraRUXEventsDetections.VectraEventsDetectionsClient", return_value=client) mocker.patch("VectraRUXEventsDetections.return_results") VectraRUXEventsDetections.main() VectraRUXEventsDetections.return_results.assert_called_once() # type: ignore[attr-defined] def test_main_function_get_remote_data(mocker, client): """ Given: - Mocked demisto.command() returning "get-remote-data". When: - Calling main function with get-remote-data command. Then: - Assert that get_remote_data_command is called. """ events_data = util_load_json(f"{TEST_DATA_DIR}/events_detections_response.json") mocker.patch.object(demisto, "params", return_value={}) mocker.patch.object(demisto, "command", return_value="get-remote-data") mocker.patch.object(demisto, "args", return_value={"id": "11111", "lastUpdate": "2025-01-01T00:00:00+00:00"}) mocker.patch.object(client, "list_events_detections_request", return_value=events_data) mocker.patch("VectraRUXEventsDetections.VectraEventsDetectionsClient", return_value=client) mocker.patch("VectraRUXEventsDetections.return_results") VectraRUXEventsDetections.main() VectraRUXEventsDetections.return_results.assert_called_once() # type: ignore[attr-defined] def test_main_function_update_remote_system(mocker, client, requests_mock): """ Given: - Mocked demisto.command() returning "update-remote-system". When: - Calling main function with update-remote-system command. Then: - Assert that update_remote_system_command is called. """ mock_args = util_load_json(f"{TEST_DATA_DIR}/update_remote_system_args.json") mocker.patch.object(demisto, "params", return_value={}) mocker.patch.object(demisto, "command", return_value="update-remote-system") mocker.patch.object(demisto, "args", return_value=mock_args) requests_mock.get(os.path.join(BASE_URL, ENDPOINTS["LIST_TAGS_ENDPOINT"].format(mock_args.get("remoteId"))), json={}) requests_mock.patch(os.path.join(BASE_URL, ENDPOINTS["LIST_TAGS_ENDPOINT"].format(mock_args.get("remoteId"))), json={}) requests_mock.post(os.path.join(BASE_URL, ENDPOINTS["ADD_NOTE_ENDPOINT"].format(mock_args.get("remoteId"))), json={}) mocker.patch("VectraRUXEventsDetections.VectraEventsDetectionsClient", return_value=client) mocker.patch("VectraRUXEventsDetections.return_results") VectraRUXEventsDetections.main() VectraRUXEventsDetections.return_results.assert_called_once() # type: ignore[attr-defined] def test_vectra_user_list_valid_arguments(requests_mock, client): """ Given: - A mocked client for requests. - A mock assignment response. - Expected context data and human-readable output. When: - Calling the 'vectra_user_list_command' function with the provided client and arguments. Then: - Assert that the CommandResults object contains the expected outputs. - Assert that the human-readable output indicates that no results were found. - Assert that the 'EntryContext' property in the context is an empty dictionary. """ user_res = util_load_json(f"{TEST_DATA_DIR}/user_list_response.json") context_data = util_load_json(f"{TEST_DATA_DIR}/user_list_context.json") with open(f"{TEST_DATA_DIR}/user_list_hr.md") as f: result_hr = f.read() requests_mock.get(BASE_URL + ENDPOINTS["USER_ENDPOINT"], json=user_res) # Call the function result = vectra_user_list_command(client, {"last_login_timestamp": "1 year"}) result_context = result.to_context() # Assert the CommandResults assert result.outputs_prefix == "Vectra.User" assert result_context.get("HumanReadable") == result_hr assert result_context.get("EntryContext") == context_data assert result.outputs_key_field == ["user_id"] def test_vectra_user_list_when_response_is_empty(requests_mock, client): """ Given: - A client object. - Mocked 'list_users_request' method returning an empty response. - Arguments specifying invalid tags. When: - Calling the 'vectra_user_list_command' function with the provided client and arguments. Then: - Assert that the CommandResults object contains the expected outputs. - Assert that the human-readable output indicates that no results were found. - Assert that the 'EntryContext' property in the context is an empty dictionary. """ empty_response: dict = {"count": 0, "next": None, "previous": None, "results": []} requests_mock.get(BASE_URL + ENDPOINTS["USER_ENDPOINT"], json=empty_response) # Call the function result = vectra_user_list_command(client, {}) result_context = result.to_context() # Assert the CommandResults assert result_context.get("HumanReadable") == "##### Got the empty list of users." assert result_context.get("EntryContext") == {} def test_vectra_user_list_with_email_filter(mocker, client): """ Given: - A mocked client. - Arguments with an email filter. When: - Calling the 'vectra_user_list_command' function with email argument. Then: - Assert that 'list_users_request' is called with 'email' (not 'username'). - Assert that the command returns expected user data. """ user_res = { "count": 1, "next": None, "previous": None, "results": [ { "id": 10, "name": "brandon.bishop", "email": "test_user@example.com", "role": "Security Analyst", "last_login_timestamp": "2023-08-22T09:24:44Z", } ], } mock_request = mocker.patch.object(client, "list_users_request", return_value=user_res) result = vectra_user_list_command(client, {"email": "test_user@example.com"}) mock_request.assert_called_once_with(email="test_user@example.com", role="", last_login_timestamp=None) assert result.outputs_prefix == "Vectra.User" assert len(result.outputs) == 1 def test_vectra_entity_list_valid_arguments(requests_mock, client): """ Given: - A mocked client for requests. - A mock entity response. - Arguments specifying valid parameters for entity listing. When: - Calling the 'vectra_entity_list_command' function with the provided client and arguments. Then: - Assert that the CommandResults object contains the expected outputs. - Assert the correctness of the 'outputs_prefix' property. - Assert that the human-readable output contains the expected content. - Assert that the 'Contents' property in the context matches the entity data. - Assert that the 'EntryContext' property in the context matches the expected context data. - Assert the correctness of the 'outputs_key_field' property. """ entity_data = util_load_json(f"{TEST_DATA_DIR}/list_entity_response.json") context_data = util_load_json(f"{TEST_DATA_DIR}/list_entity_context.json") requests_mock.get(BASE_URL + ENDPOINTS["ENTITY_ENDPOINT_v34"], json=entity_data) with open(f"{TEST_DATA_DIR}/list_entity_hr.md") as f: result_hr = f.read() args = { "entity_type": "account", "name": "name", "state": "active", "ordering": "name", "page": "1", "page_size": "4", "prioritized": "true", "tags": "test,test1", "last_modified_timestamp": "2 days", "last_detection_timestamp": "2 days", } # Call the function result = vectra_entity_list_command(client, args) result_context = result.to_context() # Assert the CommandResults assert result.outputs_prefix == "Vectra.Entity" assert result_context.get("HumanReadable") == result_hr assert result_context.get("Contents") == entity_data.get("results") assert result_context.get("EntryContext") == remove_empty_elements(context_data) assert result.outputs_key_field == ["id", "type"] def test_vectra_entity_list_when_response_is_empty(mocker, client): """ Given: - A client object. - Mocked 'list_entities_request' method returning an empty response. - Arguments specifying invalid tags. When: - Calling the 'vectra_entity_list_command' function with the provided client and arguments. Then: - Assert that the CommandResults object contains the expected outputs. - Assert that the human-readable output indicates that no results were found. - Assert that the 'EntryContext' property in the context is an empty dictionary. """ empty_response: dict = {"count": 0, "next": None, "previous": None, "results": []} mocker.patch.object(client, "list_entities_request", return_value=empty_response) args = { "tags": "invalid_tag", "name": "invalid_name", } # Call the function result = vectra_entity_list_command(client, args) result_context = result.to_context() # Assert the CommandResults assert result_context.get("HumanReadable") == "##### Couldn't find any matching entities for provided filters." assert result_context.get("EntryContext") == {} @pytest.mark.parametrize( "args,error_msg", [ ( {"entity_type": "invalid_type"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)), ), ({"state": "invalid_state"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("state", ", ".join(VALID_ENTITY_STATE))), ({"page_size": "5001"}, ERRORS["INVALID_PAGE_SIZE"]), ], ) def test_vectra_entity_list_invalid_args(client, args, error_msg): """ Given: - A client object. - Arguments specifying invalid values. When: - Calling the 'vectra_entity_list_command' function with the provided client and arguments. Then: - Assert that the function raises a ValueError. - Assert that the error message matches the expected value for the corresponding invalid argument. """ # Call the function and assert that it raises ValueError with pytest.raises(ValueError) as exception: vectra_entity_list_command(client, args) assert str(exception.value) == error_msg def test_vectra_entity_describe_valid_arguments(requests_mock, client): """ Given: - A mocked client for requests. - A mock entity response. - The expected human-readable output file. - Arguments specifying valid parameters for getting an entity. When: - Calling the 'vectra_entity_describe_command' function with the provided client and arguments. Then: - Assert that the CommandResults object contains the expected outputs. - Assert the correctness of the 'outputs_prefix' property. - Assert that the human-readable output matches the content of the expected file. - Assert that the 'Contents' property in the context matches the entity data. - Assert that the 'EntryContext' property in the context matches the expected context data. - Assert the correctness of the 'outputs_key_field' property. """ entity_data = util_load_json(f"{TEST_DATA_DIR}/get_entity_response.json") context_data = util_load_json(f"{TEST_DATA_DIR}/get_entity_context.json") requests_mock.get(BASE_URL + ENDPOINTS["ENTITY_ENDPOINT_v34"] + "/21", json=entity_data) with open(f"{TEST_DATA_DIR}/get_entity_hr.md") as f: result_hr = f.read() args = {"entity_id": "21", "entity_type": "account"} # Call the function result = vectra_entity_describe_command(client, args) result_context = result.to_context() # Assert the CommandResults assert result.outputs_prefix == "Vectra.Entity" assert result_context.get("HumanReadable") == result_hr assert result_context.get("Contents") == entity_data assert result_context.get("EntryContext") == remove_empty_elements(context_data) assert result.outputs_key_field == ["id", "type"] @pytest.mark.parametrize( "args,error_msg", [ ( {"entity_id": "1", "entity_type": "invalid_type"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)), ), ({"entity_type": "account"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")), ({"entity_id": "1", "entity_type": ""}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type")), ], ) def test_vectra_entity_describe_invalid_args(client, args, error_msg): """ Given: - A client object. - Arguments specifying an invalid entity_type value. When: - Calling the 'vectra_entity_describe_command' function with the provided client and arguments. Then: - Assert that the function raises a ValueError. - Assert that the error message matches the expected value for an invalid entity_type value. """ # Call the function and assert that it raises ValueError with pytest.raises(ValueError) as exception: vectra_entity_describe_command(client, args) assert str(exception.value) == error_msg def test_vectra_entity_detection_list_valid_arguments(requests_mock, client): """ Given: - A mocked client for requests. When: - A mock entity and detections response. - Opening and reading a specific human-readable file. - Providing arguments with a valid entity_id, page, and page_size. Then: - Call the 'vectra_list_entity_detection_command' function with the provided client and arguments. - Assert that the CommandResults outputs_prefix is 'Vectra.Entity.Detections'. - Assert that the CommandResults HumanReadable matches the content of the read human-readable file. - Assert that the CommandResults Contents match the expected detections data. - Assert that the 'EntryContext' property in the context matches the expected context data. - Assert that the CommandResults outputs_key_field is 'id'. """ detections_data = util_load_json(f"{TEST_DATA_DIR}/entity_detection_list_response.json") context_data = util_load_json(f"{TEST_DATA_DIR}/entity_detection_list_context.json") entity_data = util_load_json(f"{TEST_DATA_DIR}/get_entity_response.json") requests_mock.get(BASE_URL + ENDPOINTS["ENTITY_ENDPOINT_v34"] + "/21", json=entity_data) requests_mock.get(BASE_URL + ENDPOINTS["DETECTION_ENDPOINT"], json=detections_data) with open(f"{TEST_DATA_DIR}/entity_detection_list_hr.md") as f: result_hr = f.read() args = { "entity_id": "21", "entity_type": "account", "page": "1", "page_size": "50", "last_timestamp": "2 days", "detection_category": "Botnet", } # Call the function result = vectra_entity_detection_list_command(client, args) result_context = result.to_context() # Assert the CommandResults assert result.outputs_prefix == "Vectra.Entity.Detections" assert result_context.get("HumanReadable") == result_hr assert result_context.get("Contents") == detections_data assert result_context.get("EntryContext") == remove_empty_elements(context_data) assert result.outputs_key_field == "id" def test_vectra_entity_detection_list_when_detection_response_is_empty(mocker, client): """ Given: - A client object. - Mocked 'list_detections_request' method returning an empty response. - Arguments specifying invalid tags. When: - Calling the 'vectra_entity_list_command' function with the provided client and arguments. Then: - Assert that the CommandResults object contains the expected outputs. - Assert that the human-readable output indicates that no results were found. - Assert that the 'EntryContext' property in the context is an empty dictionary. """ empty_response: dict = {"count": 0, "next": None, "previous": None, "results": []} entity_data = util_load_json(f"{TEST_DATA_DIR}/get_entity_response.json") mocker.patch.object(client, "get_entity_request", return_value=entity_data) mocker.patch.object(client, "list_detections_request", return_value=empty_response) args = { "entity_id": "1", "entity_type": "account", "tags": "invalid_tag", } # Call the function result = vectra_entity_detection_list_command(client, args) result_context = result.to_context() # Assert the CommandResults assert result_context.get("HumanReadable") == "##### Couldn't find any matching entity detections for provided filters." assert result_context.get("EntryContext") == {} def test_vectra_entity_detection_list_when_entity_response_is_empty(mocker, client): """ Given: - A client object. - Mocked 'get_entity_request' method returning an empty response. - Arguments specifying invalid tags. When: - Calling the 'vectra_entity_list_command' function with the provided client and arguments. Then: - Assert that the CommandResults object contains the expected outputs. - Assert that the human-readable output indicates that no results were found. - Assert that the 'EntryContext' property in the context is an empty dictionary. """ empty_response: dict = {"count": 0, "next": None, "previous": None, "results": []} mocker.patch.object(client, "get_entity_request", return_value={}) mocker.patch.object(client, "list_detections_request", return_value=empty_response) args = { "entity_id": "1", "entity_type": "account", "tags": "invalid_tag", } # Call the function result = vectra_entity_detection_list_command(client, args) result_context = result.to_context() # Assert the CommandResults assert result_context.get("HumanReadable") == "##### Couldn't find any matching detections for provided entity ID and type." assert result_context.get("EntryContext") == {} @pytest.mark.parametrize( "args,error_msg", [ ({"entity_id": None}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")), ({"entity_id": "1", "entity_type": "account", "page": "0"}, ERRORS["INVALID_INTEGER_VALUE"].format("page", "0")), ( {"entity_id": "1", "entity_type": "account", "page_size": "0"}, ERRORS["INVALID_INTEGER_VALUE"].format("page_size", "0"), ), ({"entity_id": "1", "entity_type": "account", "page_size": "5001"}, ERRORS["INVALID_PAGE_SIZE"]), ( {"entity_id": "1", "entity_type": "account", "detection_category": "command and control"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("detection_category", ", ".join(DETECTION_CATEGORY_TO_ARG.keys())), ), ({"entity_id": "1", "entity_type": ""}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type")), ( {"entity_id": "1", "entity_type": "invalid"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)), ), ], ) def test_vectra_entity_detection_list_invalid_args(client, args, error_msg): """ Given: - A client object. - Arguments specifying different invalid values for entity_id, page, and page_size. When: - Calling the 'vectra_list_entity_detection_command' function with the provided client and arguments. Then: - Assert that the function raises a ValueError. - Assert that the error message matches the expected error message for each invalid argument. """ # Call the function and assert that it raises ValueError with pytest.raises(ValueError) as exception: vectra_entity_detection_list_command(client, args) assert str(exception.value) == error_msg def test_vectra_detection_describe_valid_arguments(mocker, client): """ Given: - A client object. When: - Mocking the 'list_detections_request' method of the client to return a specific detection data. - Opening and reading a specific human-readable file. - Providing arguments with a valid detection_ids, page, and page_size. Then: - Call the 'vectra_detection_describe_command' function with the provided client and arguments. - Assert that the CommandResults outputs_prefix is 'Vectra.Entity.Detections'. - Assert that the CommandResults HumanReadable matches the content of the read human-readable file. - Assert that the CommandResults Contents match the expected detection data. - Assert that the 'EntryContext' property in the context matches the expected context data. - Assert that the CommandResults outputs_key_field is 'id'. """ detections_data = util_load_json(f"{TEST_DATA_DIR}/entity_detection_list_response.json") context_data = util_load_json(f"{TEST_DATA_DIR}/entity_detection_list_context.json") mocker.patch.object(client, "list_detections_request", return_value=detections_data) with open(f"{TEST_DATA_DIR}/entity_detection_list_hr.md") as f: result_hr = f.read() args = {"detection_ids": "21", "page": "1", "page_size": "50"} # Call the function result = vectra_detection_describe_command(client, args) result_context = result.to_context() # Assert the CommandResults assert result.outputs_prefix == "Vectra.Entity.Detections" assert result_context.get("HumanReadable") == result_hr assert result_context.get("Contents") == detections_data assert result_context.get("EntryContext") == remove_empty_elements(context_data) assert result.outputs_key_field == "id" def test_vectra_detection_describe_when_detection_response_is_empty(mocker, client): """ Given: - A client object. - Mocked 'list_detections_request' method returning an empty response. - Arguments specifying invalid tags. When: - Calling the 'vectra_detection_describe_command' function with the provided client and arguments. Then: - Assert that the CommandResults object contains the expected outputs. - Assert that the human-readable output indicates that no results were found. - Assert that the 'EntryContext' property in the context is an empty dictionary. """ empty_response: dict = {"count": 0, "next": None, "previous": None, "results": []} mocker.patch.object(client, "list_detections_request", return_value=empty_response) args = {"detection_ids": "21"} # Call the function result = vectra_detection_describe_command(client, args) result_context = result.to_context() # Assert the CommandResults assert result_context.get("HumanReadable") == "##### Couldn't find any matching detections for provided detection ID(s)." assert result_context.get("EntryContext") == {} @pytest.mark.parametrize( "args,error_msg", [ ({"detection_ids": None}, ERRORS["REQUIRED_ARGUMENT"].format("detection_ids")), ({"detection_ids": " "}, ERRORS["REQUIRED_ARGUMENT"].format("detection_ids")), ({"detection_ids": ", , ,"}, ERRORS["REQUIRED_ARGUMENT"].format("detection_ids")), ({"detection_ids": ",,abc,"}, ERRORS["INVALID_NUMBER"].format("abc")), ({"detection_ids": ",,abc,12"}, ERRORS["INVALID_NUMBER"].format("abc")), ({"detection_ids": ",-12,"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_ids", "-12")), ({"detection_ids": ",12,", "page": "0"}, ERRORS["INVALID_INTEGER_VALUE"].format("page", "0")), ({"detection_ids": ",12,", "page_size": "0"}, ERRORS["INVALID_INTEGER_VALUE"].format("page_size", "0")), ({"detection_ids": ",12,", "page_size": "5001"}, ERRORS["INVALID_PAGE_SIZE"]), ], ) def test_vectra_detection_describe_invalid_args(client, args, error_msg): """ Given: - A client object. - Arguments specifying different invalid values for detection_ids, page, and page_size. When: - Calling the 'vectra_detection_describe_command' function with the provided client and arguments. Then: - Assert that the function raises a ValueError. - Assert that the error message matches the expected error message for each invalid argument. """ # Call the function and assert that it raises ValueError with pytest.raises(ValueError) as exception: vectra_detection_describe_command(client, args) assert str(exception.value) == error_msg def test_vectra_entity_note_add_valid_arguments(requests_mock, client): """ Given: - A mocked client for requests. - A mock notes response. - The expected human-readable output file. - Arguments specifying valid parameters for adding a note to an entity. When: - Calling the 'vectra_entity_note_add_command' function with the provided client and arguments. Then: - Assert that the CommandResults object contains the expected outputs. - Assert the correctness of the 'outputs_prefix' property. - Assert that the human-readable output matches the content of the expected file. - Assert that the 'Contents' property in the context matches the notes response. - Assert that the 'EntryContext' property in the context matches the context data. - Assert the correctness of the 'outputs_key_field' property. """ notes_res = util_load_json(f"{TEST_DATA_DIR}/entity_note_add_response.json") context_data = util_load_json(f"{TEST_DATA_DIR}/entity_note_add_context.json") requests_mock.post(BASE_URL + ENDPOINTS["ADD_AND_LIST_ENTITY_NOTE_ENDPOINT"].format("1"), json=notes_res) with open(f"{TEST_DATA_DIR}/entity_note_add_hr.md") as f: result_hr = f.read() args = { "entity_id": "1", "entity_type": "account", "note": "test_note", } # Call the function result = vectra_entity_note_add_command(client, args) result_context = result.to_context() notes_res["note_id"] = notes_res["id"] notes_res["entity_id"] = 1 notes_res["entity_type"] = "account" # Assert the CommandResults assert result.outputs_prefix == "Vectra.Entity.Notes" assert result_context.get("HumanReadable") == result_hr assert result_context.get("Contents") == notes_res assert result_context.get("EntryContext") == context_data assert result.outputs_key_field == ["entity_id", "entity_type", "note_id"] @pytest.mark.parametrize( "args,error_msg", [ ( {"entity_id": "1", "entity_type": "invalid_type"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)), ), ({"entity_id": "1", "entity_type": "account"}, ERRORS["REQUIRED_ARGUMENT"].format("note")), ({"entity_type": "account", "note": "test_note"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")), ( {"entity_id": "0", "entity_type": "account", "note": "test_note"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "0"), ), ( {"entity_id": "-1", "entity_type": "account", "note": "test_note"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "-1"), ), ( {"entity_id": "1.5", "entity_type": "account", "note": "test_note"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "1.5"), ), ({"entity_id": "1", "entity_type": ""}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type")), ], ) def test_vectra_entity_note_add_invalid_args(client, args, error_msg): """ Given: - A client object. - Arguments specifying different invalid values for entity_id, entity_type, and note. When: - Calling the 'vectra_entity_note_add_command' function with the provided client and arguments. Then: - Assert that the function raises a ValueError. - Assert that the error message matches the expected error message for each invalid argument. """ # Call the function and assert that it raises ValueError with pytest.raises(ValueError) as exception: vectra_entity_note_add_command(client, args) assert str(exception.value) == error_msg def test_vectra_entity_note_update_valid_arguments(requests_mock, client): """ Given: - A mocked client for requests. - A mock note response. - The expected human-readable output file. - Arguments specifying valid parameters for updating a note of an entity. When: - Calling the 'vectra_entity_note_update_command' function with the provided client and arguments. Then: - Assert that the CommandResults object contains the expected outputs. - Assert the correctness of the 'outputs_prefix' property. - Assert that the human-readable output matches the content of the expected file. - Assert that the 'Contents' property in the context matches the notes response. - Assert that the 'EntryContext' property in the context matches the context data. - Assert the correctness of the 'outputs_key_field' property. """ notes_res = util_load_json(f"{TEST_DATA_DIR}/entity_note_update_response.json") context_data = util_load_json(f"{TEST_DATA_DIR}/entity_note_update_context.json") requests_mock.patch(BASE_URL + ENDPOINTS["UPDATE_AND_REMOVE_ENTITY_NOTE_ENDPOINT"].format(1, 1), json=notes_res) with open(f"{TEST_DATA_DIR}/entity_note_update_hr.md") as f: result_hr = f.read() args = { "entity_id": "1", "entity_type": "account", "note_id": "1", "note": "test_note", } # Call the function result = vectra_entity_note_update_command(client, args) result_context = result.to_context() notes_res["note_id"] = notes_res["id"] notes_res["entity_id"] = 1 notes_res["entity_type"] = "account" # Assert the CommandResults assert result.outputs_prefix == "Vectra.Entity.Notes" assert result_context.get("HumanReadable") == result_hr assert result_context.get("Contents") == notes_res assert result_context.get("EntryContext") == context_data assert result.outputs_key_field == ["entity_id", "entity_type", "note_id"] @pytest.mark.parametrize( "args,error_msg", [ ( {"entity_id": "1", "entity_type": "invalid_type", "note_id": "1"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)), ), ({"entity_id": "1", "entity_type": "account", "note_id": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("note")), ({"entity_type": "account", "note": "test_note", "note_id": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")), ({"entity_id": "1", "entity_type": "account", "note": "test_note"}, ERRORS["REQUIRED_ARGUMENT"].format("note_id")), ( {"entity_id": "0", "entity_type": "account", "note": "test_note", "note_id": "1"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "0"), ), ( {"entity_id": "-1", "entity_type": "account", "note": "test_note", "note_id": "1"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "-1"), ), ( {"entity_id": "1.5", "entity_type": "account", "note": "test_note", "note_id": "1"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "1.5"), ), ( {"note_id": "0", "entity_type": "account", "note": "test_note", "entity_id": "1"}, ERRORS["INVALID_INTEGER_VALUE"].format("note_id", "0"), ), ( {"note_id": "-1", "entity_type": "account", "note": "test_note", "entity_id": "1"}, ERRORS["INVALID_INTEGER_VALUE"].format("note_id", "-1"), ), ( {"note_id": "1.5", "entity_type": "account", "note": "test_note", "entity_id": "1"}, ERRORS["INVALID_INTEGER_VALUE"].format("note_id", "1.5"), ), ({"entity_id": "1", "entity_type": "", "note_id": "2"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type")), ], ) def test_vectra_entity_note_update_invalid_args(client, args, error_msg): """ Given: - A client object. - Invalid arguments for updating a note of an entity. When: - Calling the 'vectra_entity_note_update_command' function with the provided client and arguments. Then: - Assert that a ValueError is raised with the expected error message. """ # Call the function and assert that it raises ValueError with pytest.raises(ValueError) as exception: vectra_entity_note_update_command(client, args) assert str(exception.value) == error_msg def test_vectra_entity_note_remove_valid_arguments(requests_mock, client): """ Tests the 'vectra_entity_note_remove_command' function with valid arguments. Ensures that the function removes an entity note and returns the expected CommandResults object. Args: requests_mock: The requests mocker object. client: The VectraClient instance. Returns: None. Raises an AssertionError if the test fails. """ requests_mock.delete(BASE_URL + ENDPOINTS["UPDATE_AND_REMOVE_ENTITY_NOTE_ENDPOINT"].format(1, 1), status_code=204) with open(f"{TEST_DATA_DIR}/entity_note_remove_hr.md") as f: result_hr = f.read() args = { "entity_id": "1", "entity_type": "account", "note_id": "1", } # Call the function result = vectra_entity_note_remove_command(client, args) result_context = result.to_context() # Assert the CommandResults assert result_context.get("HumanReadable") == result_hr assert result_context.get("EntryContext") == {} def test_vectra_entity_note_remove_invalid_status_code(requests_mock, client): """ Tests the 'vectra_entity_note_remove_command' function with valid arguments. Ensures that the function gives error in HR for status code. Args: requests_mock: The requests mocker object. client: The VectraClient instance. Returns: None. Raises an AssertionError if the test fails. """ requests_mock.delete(BASE_URL + ENDPOINTS["UPDATE_AND_REMOVE_ENTITY_NOTE_ENDPOINT"].format(1, 1), status_code=200) args = { "entity_id": "1", "entity_type": "account", "note_id": "1", } # Call the function result = vectra_entity_note_remove_command(client, args) result_context = result.to_context() # Assert the CommandResults assert result_context.get("HumanReadable") == "Something went wrong." assert result_context.get("EntryContext") == {} @pytest.mark.parametrize( "args,error_msg", [ ( {"entity_id": "1", "entity_type": "invalid_type", "note_id": "1"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)), ), ({"entity_type": "account", "note": "test_note", "note_id": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")), ({"entity_id": "1", "note_id": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type")), ({"entity_id": "1", "entity_type": "account"}, ERRORS["REQUIRED_ARGUMENT"].format("note_id")), ( {"entity_id": "0", "entity_type": "account", "note": "test_note", "note_id": "1"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "0"), ), ( {"entity_id": "-1", "entity_type": "account", "note": "test_note", "note_id": "1"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "-1"), ), ( {"entity_id": "1.5", "entity_type": "account", "note": "test_note", "note_id": "1"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "1.5"), ), ( {"note_id": "0", "entity_type": "account", "note": "test_note", "entity_id": "1"}, ERRORS["INVALID_INTEGER_VALUE"].format("note_id", "0"), ), ( {"note_id": "-1", "entity_type": "account", "note": "test_note", "entity_id": "1"}, ERRORS["INVALID_INTEGER_VALUE"].format("note_id", "-1"), ), ( {"note_id": "1.5", "entity_type": "account", "note": "test_note", "entity_id": "1"}, ERRORS["INVALID_INTEGER_VALUE"].format("note_id", "1.5"), ), ], ) def test_vectra_entity_note_remove_invalid_args(client, args, error_msg): """ Given: - A client object. - Invalid arguments for updating a note of an entity. When: - Calling the 'vectra_entity_note_remove_command' function with the provided client and arguments. Then: - Assert that a ValueError is raised with the expected error message. """ # Call the function and assert that it raises ValueError with pytest.raises(ValueError) as exception: vectra_entity_note_remove_command(client, args) assert str(exception.value) == error_msg def test_vectra_entity_tag_add_valid_arguments(requests_mock, client): """ Given: - A mocked client for requests. - A mock get and update tag response. - The expected human-readable output file. - Arguments specifying valid parameters for adding the tags to an entity. When: - Calling the 'vectra_entity_tag_add_command' function with the provided client and arguments. Then: - Assert that the CommandResults object contains the expected outputs. - Assert the correctness of the 'outputs_prefix' property. - Assert that the human-readable output matches the content of the expected file. - Assert that the 'Contents' property in the context matches the tags response. - Assert that the 'EntryContext' property in the context matches the context data. - Assert the correctness of the 'outputs_key_field' property. """ add_tags_res = util_load_json(f"{TEST_DATA_DIR}/entity_tag_add_response.json") get_tags_res = util_load_json(f"{TEST_DATA_DIR}/entity_tag_get_response.json") context_data = util_load_json(f"{TEST_DATA_DIR}/entity_tag_add_context.json") requests_mock.get(BASE_URL + ENDPOINTS["ENTITY_TAG_ENDPOINT"].format(1), json=get_tags_res) requests_mock.patch(BASE_URL + ENDPOINTS["ENTITY_TAG_ENDPOINT"].format(1), json=add_tags_res) with open(f"{TEST_DATA_DIR}/entity_tag_add_hr.md") as f: result_hr = f.read() args = { "entity_id": "1", "entity_type": "host", "tags": "tag1, tag2", } # Call the function result = vectra_entity_tag_add_command(client, args) result_context = result.to_context() add_tags_res.update({"entity_id": 1, "entity_type": "host"}) del add_tags_res["status"] # Assert the CommandResults assert result.outputs_prefix == "Vectra.Entity.Tags" assert result_context.get("HumanReadable") == result_hr assert result_context.get("Contents") == add_tags_res assert result_context.get("EntryContext") == remove_empty_elements(context_data) assert result.outputs_key_field == ["tag_id", "entity_type", "entity_id"] @pytest.mark.parametrize( "args,error_msg", [ ( {"entity_id": "1", "entity_type": "invalid_type"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)), ), ({"entity_id": "1", "entity_type": "account"}, ERRORS["REQUIRED_ARGUMENT"].format("tags")), ({"entity_id": "1", "entity_type": "account", "tags": " , "}, ERRORS["REQUIRED_ARGUMENT"].format("tags")), ({"entity_type": "account"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")), ( {"entity_id": "0", "entity_type": "account", "tags": " , tag1"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "0"), ), ( {"entity_id": "-1", "entity_type": "account", "tags": "tag1"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "-1"), ), ( {"entity_id": "1.5", "entity_type": "account", "tags": "tag1"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "1.5"), ), ({"entity_id": "1", "entity_type": "", "tags": "tag1"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type")), ], ) def test_vectra_entity_tag_add_invalid_args(client, args, error_msg): """ Given: - A client object. - Arguments specifying different invalid values for entity_id, entity_type, and tags. When: - Calling the 'vectra_entity_tag_add_command' function with the provided client and arguments. Then: - Assert that the function raises a ValueError. - Assert that the error message matches the expected error message for each invalid argument. """ # Call the function and assert that it raises ValueError with pytest.raises(ValueError) as exception: vectra_entity_tag_add_command(client, args) assert str(exception.value) == error_msg def test_vectra_entity_tag_add_when_get_tag_response_is_invalid(mocker, client): """ Given: - A client object. - Mocked 'list_entity_tags_request' method returning invalid response. - Arguments specifying invalid tags. When: - Calling the 'vectra_entity_tag_add_command' function with the provided client and arguments. Then: - Assert that the human-readable output indicates that invalid result was found. """ get_tags_res = util_load_json(f"{TEST_DATA_DIR}/entity_tag_get_invalid_response.json") mocker.patch.object(client, "list_entity_tags_request", return_value=get_tags_res) args = { "entity_id": "1", "entity_type": "host", "tags": "tag1, tag2", } # Call the function with pytest.raises(DemistoException) as exception: vectra_entity_tag_add_command(client, args) assert str(exception.value) == f"Something went wrong. Message: {get_tags_res.get('message')}." def test_vectra_entity_tag_add_when_add_tag_response_is_invalid(mocker, client): """ Given: - A client object. - Mocked 'update_entity_tags_request' method returning invalid response. - Arguments specifying invalid tags. When: - Calling the 'vectra_entity_tag_add_command' function with the provided client and arguments. Then: - Assert that the human-readable output indicates that invalid result was found. """ get_tags_res = util_load_json(f"{TEST_DATA_DIR}/entity_tag_get_response.json") add_tags_res = util_load_json(f"{TEST_DATA_DIR}/entity_tag_add_invalid_response.json") mocker.patch.object(client, "update_entity_tags_request", return_value=add_tags_res) mocker.patch.object(client, "list_entity_tags_request", return_value=get_tags_res) args = { "entity_id": "1", "entity_type": "host", "tags": "tag1, tag2", } # Call the function with pytest.raises(DemistoException) as exception: vectra_entity_tag_add_command(client, args) assert str(exception.value) == f"Something went wrong. Message: {add_tags_res.get('message')}." def test_vectra_entity_tag_remove_valid_arguments(mocker, client): """ Given: - A client object. - Mocked 'update_entity_tags_request' method returning tags response. - The expected human-readable output file. - Arguments specifying valid parameters for adding the tags to an entity. When: - Calling the 'vectra_entity_tag_remove_command' function with the provided client and arguments. Then: - Assert that the CommandResults object contains the expected outputs. - Assert the correctness of the 'outputs_prefix' property. - Assert that the human-readable output matches the content of the expected file. - Assert that the 'Contents' property in the context matches the tags response. - Assert that the 'EntryContext' property in the context matches the context data. - Assert the correctness of the 'outputs_key_field' property. """ add_tags_res = util_load_json(f"{TEST_DATA_DIR}/entity_tag_remove_response.json") get_tags_res = util_load_json(f"{TEST_DATA_DIR}/entity_tag_get_response_2.json") context_data = util_load_json(f"{TEST_DATA_DIR}/entity_tag_remove_context.json") mocker.patch.object(client, "update_entity_tags_request", return_value=add_tags_res) mocker.patch.object(client, "list_entity_tags_request", return_value=get_tags_res) with open(f"{TEST_DATA_DIR}/entity_tag_remove_hr.md") as f: result_hr = f.read() args = {"entity_id": "1", "entity_type": "host", "tags": "tag2"} # Call the function result = vectra_entity_tag_remove_command(client, args) result_context = result.to_context() add_tags_res.update({"entity_id": 1, "entity_type": "host"}) # Assert the CommandResults assert result.outputs_prefix == "Vectra.Entity.Tags" assert result_context.get("HumanReadable") == result_hr assert result_context.get("Contents") == add_tags_res assert result_context.get("EntryContext") == remove_empty_elements(context_data) assert result.outputs_key_field == ["tag_id", "entity_type", "entity_id"] @pytest.mark.parametrize( "args,error_msg", [ ( {"entity_id": "1", "entity_type": "invalid_type"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)), ), ({"entity_id": "1", "entity_type": "account"}, ERRORS["REQUIRED_ARGUMENT"].format("tags")), ({"entity_id": "1", "entity_type": "account", "tags": " , "}, ERRORS["REQUIRED_ARGUMENT"].format("tags")), ({"entity_type": "account"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")), ({"entity_id": "0", "entity_type": "account"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "0")), ({"entity_id": "-1", "entity_type": "account"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "-1")), ({"entity_id": "1.5", "entity_type": "account"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "1.5")), ], ) def test_vectra_entity_tag_remove_invalid_args(client, args, error_msg): """ Given: - A client object. - Arguments specifying different invalid values for entity_id and entity_type. When: - Calling the 'vectra_entity_tag_remove_command' function with the provided client and arguments. Then: - Assert that the function raises a ValueError. - Assert that the error message matches the expected error message for each invalid argument. """ # Call the function and assert that it raises ValueError with pytest.raises(ValueError) as exception: vectra_entity_tag_remove_command(client, args) assert str(exception.value) == error_msg def test_vectra_entity_tag_remove_when_get_tag_response_is_invalid(mocker, client): """ Given: - A client object. - Mocked 'list_entity_tags_request' method returning invalid response. - Arguments specifying invalid tags. When: - Calling the 'vectra_entity_tag_remove_command' function with the provided client and arguments. Then: - Assert that the human-readable output indicates that invalid result was found. """ get_tags_res = util_load_json(f"{TEST_DATA_DIR}/entity_tag_get_invalid_response.json") mocker.patch.object(client, "list_entity_tags_request", return_value=get_tags_res) args = { "entity_id": "1", "entity_type": "host", "tags": "tag2", } # Call the function with pytest.raises(DemistoException) as exception: vectra_entity_tag_remove_command(client, args) assert str(exception.value) == f"Something went wrong. Message: {get_tags_res.get('message')}." def test_vectra_entity_tag_remove_when_add_tag_response_is_invalid(mocker, client): """ Given: - A client object. - Mocked 'update_entity_tags_request' method returning invalid response. - Arguments specifying invalid tags. When: - Calling the 'vectra_entity_tag_remove_command' function with the provided client and arguments. Then: - Assert that the human-readable output indicates that invalid result was found. """ get_tags_res = util_load_json(f"{TEST_DATA_DIR}/entity_tag_get_response_2.json") add_tags_res = util_load_json(f"{TEST_DATA_DIR}/entity_tag_add_invalid_response.json") mocker.patch.object(client, "update_entity_tags_request", return_value=add_tags_res) mocker.patch.object(client, "list_entity_tags_request", return_value=get_tags_res) args = { "entity_id": "1", "entity_type": "host", "tags": "tag2", } # Call the function with pytest.raises(DemistoException) as exception: vectra_entity_tag_remove_command(client, args) assert str(exception.value) == f"Something went wrong. Message: {add_tags_res.get('message')}." def test_vectra_entity_tag_list_valid_arguments(requests_mock, client): """ Given: - A client object. - Mocked 'list_entity_tags_request' method returning tags response. - The expected human-readable output file. - Arguments specifying valid parameters for adding the tags to an entity. When: - Calling the 'vectra_entity_tag_list_command' function with the provided client and arguments. Then: - Assert that the CommandResults object contains the expected outputs. - Assert the correctness of the 'outputs_prefix' property. - Assert that the human-readable output matches the content of the expected file. - Assert that the 'Contents' property in the context matches the tags response. - Assert that the 'EntryContext' property in the context matches the context data. - Assert the correctness of the 'outputs_key_field' property. """ get_tags_res = util_load_json(f"{TEST_DATA_DIR}/entity_tag_get_response_2.json") context_data = util_load_json(f"{TEST_DATA_DIR}/entity_tag_list_context.json") requests_mock.get(BASE_URL + ENDPOINTS["ENTITY_TAG_ENDPOINT"].format(1), json=get_tags_res) with open(f"{TEST_DATA_DIR}/entity_tag_list_hr.md") as f: result_hr = f.read() args = {"entity_id": "1", "entity_type": "host"} # Call the function result = vectra_entity_tag_list_command(client, args) result_context = result.to_context() get_tags_res.update({"entity_id": 1, "entity_type": "host"}) del get_tags_res["status"] # Assert the CommandResults assert result.outputs_prefix == "Vectra.Entity.Tags" assert result_context.get("HumanReadable") == result_hr assert result_context.get("Contents") == get_tags_res assert result_context.get("EntryContext") == remove_empty_elements(context_data) assert result.outputs_key_field == ["tag_id", "entity_type", "entity_id"] def test_vectra_entity_tag_list_with_empty_tag_response(mocker, client): """ Given: - A client object. - Mocked 'update_entity_tags_request' method returning tags response. - The expected human-readable output file. - Arguments specifying valid parameters for adding the tags to an entity. When: - Calling the 'list_entity_tags_request' function with the provided client and arguments. Then: - Assert that the CommandResults object contains the expected outputs. - Assert the correctness of the 'outputs_prefix' property. - Assert that the human-readable output matches the content of the expected file. - Assert that the 'Contents' property in the context matches the tags response. - Assert that the 'EntryContext' property in the context matches the context data. - Assert the correctness of the 'outputs_key_field' property. """ get_tags_res = util_load_json(f"{TEST_DATA_DIR}/entity_tag_get_response_2.json") get_tags_res["tags"] = [] context_data = util_load_json(f"{TEST_DATA_DIR}/entity_tag_empty_list_context.json") mocker.patch.object(client, "list_entity_tags_request", return_value=get_tags_res) with open(f"{TEST_DATA_DIR}/entity_tag_empty_list_hr.md") as f: result_hr = f.read() args = {"entity_id": "1", "entity_type": "host"} # Call the function result = vectra_entity_tag_list_command(client, args) result_context = result.to_context() get_tags_res.update({"entity_id": 1, "entity_type": "host"}) # Assert the CommandResults assert result.outputs_prefix == "Vectra.Entity.Tags" assert result_context.get("HumanReadable") == result_hr assert result_context.get("Contents") == get_tags_res assert result_context.get("EntryContext") == remove_empty_elements(context_data) assert result.outputs_key_field == ["tag_id", "entity_type", "entity_id"] @pytest.mark.parametrize( "args,error_msg", [ ( {"entity_id": "1", "entity_type": "invalid_type"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)), ), ({"entity_type": "account"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")), ({"entity_id": "0", "entity_type": "account"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "0")), ({"entity_id": "-1", "entity_type": "account"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "-1")), ({"entity_id": "1.5", "entity_type": "account"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "1.5")), ({"entity_id": "1", "entity_type": ""}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type")), ], ) def test_vectra_entity_tag_list_invalid_args(client, args, error_msg): """ Given: - A client object. - Arguments specifying different invalid values for entity_id, entity_type, and tags. When: - Calling the 'vectra_entity_tag_list_command' function with the provided client and arguments. Then: - Assert that the function raises a ValueError. - Assert that the error message matches the expected error message for each invalid argument. """ # Call the function and assert that it raises ValueError with pytest.raises(ValueError) as exception: vectra_entity_tag_list_command(client, args) assert str(exception.value) == error_msg def test_vectra_entity_tag_list_when_response_is_invalid(mocker, client): """ Given: - A client object. - Mocked 'list_entity_tags_request' method returning invalid response. - Arguments specifying invalid tags. When: - Calling the 'vectra_entity_tag_list_command' function with the provided client and arguments. Then: - Assert that the human-readable output indicates that invalid result was found. """ get_tags_res = util_load_json(f"{TEST_DATA_DIR}/entity_tag_get_invalid_response.json") mocker.patch.object(client, "list_entity_tags_request", return_value=get_tags_res) args = { "entity_id": "1", "entity_type": "host", "tags": "tag1, tag2", } # Call the function with pytest.raises(DemistoException) as exception: vectra_entity_tag_list_command(client, args) assert str(exception.value) == f"Something went wrong. Message: {get_tags_res.get('message')}." def test_vectra_entity_assignment_add_valid_arguments(requests_mock, client): """ Given: - A client object. - Mocked 'add_entity_assignment_request' method returning assignment data. - The expected human-readable output file. - Arguments specifying valid parameters for adding an assignment. When: - Calling the 'vectra_entity_assignment_add_command' function with the provided client and arguments. Then: - Assert that the CommandResults object contains the expected outputs. - Assert the correctness of the 'outputs_prefix' property. - Assert that the human-readable output matches the content of the expected file. - Assert that the 'Contents' property in the context matches the assignment data. - Assert that the 'EntryContext' property in the context matches the expected context data. - Assert the correctness of the 'outputs_key_field' property. """ assignment_res = util_load_json(f"{TEST_DATA_DIR}/entity_assignment_add_response.json") context_data = util_load_json(f"{TEST_DATA_DIR}/entity_assignment_add_context.json") with open(f"{TEST_DATA_DIR}/entity_assignment_add_account_hr.md") as f: result_hr = f.read() args = {"entity_id": "1", "entity_type": "account", "user_id": "1"} requests_mock.post(BASE_URL + ENDPOINTS["ASSIGNMENT_ENDPOINT"], json=assignment_res[0]) # Call the function result = vectra_entity_assignment_add_command(client, args) result_context = result.to_context() # Assert the CommandResults assert result.outputs_prefix == "Vectra.Entity.Assignments" assert result_context.get("HumanReadable") == result_hr assert result_context.get("EntryContext") == context_data[0] assert result.outputs_key_field == ["assignment_id"] # For Host with open(f"{TEST_DATA_DIR}/entity_assignment_add_host_hr.md") as f: result_hr = f.read() args = {"entity_id": "1", "entity_type": "host", "user_id": "3"} requests_mock.post(BASE_URL + ENDPOINTS["ASSIGNMENT_ENDPOINT"], json=assignment_res[1]) # Call the function result = vectra_entity_assignment_add_command(client, args) result_context = result.to_context() # Assert the CommandResults assert result.outputs_prefix == "Vectra.Entity.Assignments" assert result_context.get("HumanReadable") == result_hr assert result_context.get("EntryContext") == context_data[1] assert result.outputs_key_field == ["assignment_id"] @pytest.mark.parametrize( "args,error_msg", [ ( {"entity_id": "1", "entity_type": "invalid_type", "user_id": "1"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)), ), ({"entity_id": "1", "entity_type": "account"}, ERRORS["REQUIRED_ARGUMENT"].format("user_id")), ({"entity_type": "account", "note": "test_note"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")), ( {"entity_id": "0", "entity_type": "account", "note": "test_note"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "0"), ), ( {"entity_id": "-1", "entity_type": "account", "note": "test_note"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "-1"), ), ( {"entity_id": "1.5", "entity_type": "account", "note": "test_note"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "1.5"), ), ({"entity_id": "1", "entity_type": "account", "user_id": "0"}, ERRORS["INVALID_INTEGER_VALUE"].format("user_id", "0")), ({"entity_id": "1", "entity_type": "account", "user_id": "-1"}, ERRORS["INVALID_INTEGER_VALUE"].format("user_id", "-1")), ( {"entity_id": "1", "entity_type": "account", "user_id": "1.5"}, ERRORS["INVALID_INTEGER_VALUE"].format("user_id", "1.5"), ), ( {"entity_id": "1", "entity_type": "", "user_id": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type"), ), ], ) def test_vectra_entity_assignment_add_invalid_args(client, args, error_msg): """ Given: - A client object. - Arguments specifying different invalid values for entity_id, entity_type, and user_id. When: - Calling the 'vectra_entity_assignment_add_command' function with the provided client and arguments. Then: - Assert that the function raises a ValueError. - Assert that the error message matches the expected error message for each invalid argument. """ # Call the function and assert that it raises ValueError with pytest.raises(ValueError) as exception: vectra_entity_assignment_add_command(client, args) assert str(exception.value) == error_msg def test_vectra_entity_assignment_update_valid_arguments(requests_mock, client): """ Given: - A mocked client for requests. - A mock entity assignment update response. - The expected human-readable output file. - Arguments specifying valid parameters for updating an entity assignment. When: - Calling the 'vectra_entity_assignment_update_command' function with the provided client and arguments. Then: - Assert that the CommandResults object contains the expected outputs. - Assert the correctness of the 'outputs_prefix' property. - Assert that the human-readable output matches the content of the expected file. - Assert that the 'Contents' property in the context matches the assignment data. - Assert that the 'EntryContext' property in the context matches the expected context data. - Assert the correctness of the 'outputs_key_field' property. """ assignment_res = util_load_json(f"{TEST_DATA_DIR}/entity_assignment_update_response.json") context_data = util_load_json(f"{TEST_DATA_DIR}/entity_assignment_update_context.json") requests_mock.put(BASE_URL + ENDPOINTS["UPDATE_ASSIGNMENT_ENDPOINT"].format(1), json=assignment_res) with open(f"{TEST_DATA_DIR}/entity_assignment_update_hr.md") as f: result_hr = f.read() args = {"assignment_id": "1", "user_id": "2"} # Call the function result = vectra_entity_assignment_update_command(client, args) result_context = result.to_context() assignment_res.get("assignment")["assignment_id"] = 1 # Assert the CommandResults assert result.outputs_prefix == "Vectra.Entity.Assignments" assert result_context.get("HumanReadable") == result_hr assert result_context.get("Contents") == assignment_res.get("assignment") assert result_context.get("EntryContext") == context_data assert result.outputs_key_field == ["assignment_id"] @pytest.mark.parametrize( "args,error_msg", [ ({"assignment_id": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("user_id")), ({"user_id": "2"}, ERRORS["REQUIRED_ARGUMENT"].format("assignment_id")), ({"assignment_id": "0", "user_id": "2"}, ERRORS["INVALID_INTEGER_VALUE"].format("assignment_id", "0")), ({"assignment_id": "-1", "user_id": "2"}, ERRORS["INVALID_INTEGER_VALUE"].format("assignment_id", "-1")), ({"assignment_id": "1.5", "user_id": "2"}, ERRORS["INVALID_INTEGER_VALUE"].format("assignment_id", "1.5")), ({"user_id": "0", "assignment_id": "2"}, ERRORS["INVALID_INTEGER_VALUE"].format("user_id", "0")), ({"user_id": "-1", "assignment_id": "2"}, ERRORS["INVALID_INTEGER_VALUE"].format("user_id", "-1")), ({"user_id": "1.5", "assignment_id": "2"}, ERRORS["INVALID_INTEGER_VALUE"].format("user_id", "1.5")), ], ) def test_vectra_entity_assignment_update_invalid_args(client, args, error_msg): """ Given: - A client object. - Arguments specifying different invalid values for assignment_id and user_id. When: - Calling the 'vectra_entity_assignment_update_command' function with the provided client and arguments. Then: - Assert that the function raises ValueError. - Assert that the error message matches the expected error message for each invalid argument. """ # Call the function and assert that it raises ValueError with pytest.raises(ValueError) as exception: vectra_entity_assignment_update_command(client, args) assert str(exception.value) == error_msg def test_vectra_detection_pcap_download_valid_arguments(requests_mock, client): """ Given: - A client object. - Mocked GET request for downloading PCAP data of a detection. - The expected binary file content of the PCAP. - Arguments specifying a valid detection ID for downloading PCAP. When: - Calling the 'vectra_detection_pcap_download_command' function with the provided client and arguments. Then: - Assert that the result contains the expected binary file content. """ mock_file_content = b"PCAP data of detection id 1431" args = {"detection_id": "1431"} requests_mock.get( BASE_URL + ENDPOINTS["DOWNLOAD_DETECTION_PCAP"].format("1431"), content=mock_file_content, headers={"Content-Disposition": 'attachement;filename="IP-1.1.1.1_hidden_dns_tunnel_1431.pcap"'}, ) # Call the function result = vectra_detection_pcap_download_command(client, args) # Assert the CommandResults assert result.get("File") == "IP-1.1.1.1_hidden_dns_tunnel_1431.pcap" @pytest.mark.parametrize( "args,error_msg", [ ({}, ERRORS["REQUIRED_ARGUMENT"].format("detection_id")), ({"detection_id": "as,2"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "as,2")), ({"detection_id": "1.5"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "1.5")), ({"detection_id": "-1"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "-1")), ], ) def test_vectra_detection_pcap_download_invalid_args(client, args, error_msg): """ Given: - A client object. - Arguments specifying different invalid values for detection_id. When: - Calling the 'vectra_detection_pcap_download_command' function with the provided client and arguments. Then: - Assert that the function raises a ValueError. - Assert that the error message matches the expected error message for each invalid argument. """ # Call the function and assert that it raises ValueError with pytest.raises(ValueError) as exception: vectra_detection_pcap_download_command(client, args) assert str(exception.value) == error_msg def test_vectra_assignment_list_valid_arguments(requests_mock, client): """ Given: - A mocked client for requests. - A mock assignment response. - Expected context data and human-readable output. When: - Calling the 'vectra_assignment_list_command' function with the provided client and no additional arguments. Then: - Assert that the CommandResults object contains the expected outputs. - Assert the correctness of the 'outputs_prefix' property. - Assert that the human-readable output matches the content of the expected file. - Assert that the 'Contents' property in the context matches the assignment data. - Assert that the 'EntryContext' property in the context matches the expected context data. - Assert the correctness of the 'outputs_key_field' property. """ assignment_res = util_load_json(f"{TEST_DATA_DIR}/assignment_list_response.json") context_data = util_load_json(f"{TEST_DATA_DIR}/assignment_list_context.json") with open(f"{TEST_DATA_DIR}/assignment_list_hr.md") as f: result_hr = f.read() requests_mock.get(BASE_URL + ENDPOINTS["ASSIGNMENT_ENDPOINT"], json=assignment_res) # Call the function result = vectra_assignment_list_command(client, {"entity_type": "host", "entity_ids": "1"}) result_context = result.to_context() # Assert the CommandResults assert result.outputs_prefix == "Vectra.Entity.Assignments" assert result_context.get("HumanReadable") == result_hr assert result_context.get("EntryContext") == context_data assert result.outputs_key_field == ["assignment_id"] def test_vectra_assignment_list_when_assignment_response_is_empty(requests_mock, client): """ Given: - A mocked client for requests. - An empty assignment response. When: - Calling the 'vectra_assignment_list_command' function with the provided empty assignment response. Then: - Assert that the CommandResults object contains the appropriate human-readable output for empty results. - Assert that the EntryContext is empty. """ empty_response: dict = {"count": 0, "next": None, "previous": None, "results": []} args = {"resolved": "False", "created_after": "1 day", "entity_type": "account", "entity_ids": "1"} requests_mock.get(BASE_URL + ENDPOINTS["ASSIGNMENT_ENDPOINT"], json=empty_response) # Call the function result = vectra_assignment_list_command(client, args) result_context = result.to_context() # Assert the CommandResults assert result_context.get("HumanReadable") == "##### Couldn't find any matching assignments for provided filters." assert result_context.get("EntryContext") == {} @pytest.mark.parametrize( "args,error_msg", [ ({"entity_type": "account"}, ERRORS["ENTITY_IDS_WITHOUT_TYPE"]), ({"entity_ids": "1,2"}, ERRORS["ENTITY_IDS_WITHOUT_TYPE"]), ( {"entity_ids": "1", "entity_type": "invalid_type"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)), ), ], ) def test_vectra_assignment_list_invalid_args(client, args, error_msg): """ Given: - A client object. - Arguments specifying invalid parameters for listing assignments. When: - Calling the 'vectra_assignment_list_command' function with the provided client and arguments. Then: - Assert that the function raises a ValueError. - Assert that the raised error message matches the expected error message. """ # Call the function and assert that it raises ValueError with pytest.raises(ValueError) as exception: vectra_assignment_list_command(client, args) assert str(exception.value) == error_msg def test_vectra_entity_note_list_valid_arguments(requests_mock, client): """ Given: - A mocked 'requests_mock' to simulate API responses. - A client object. - Mocked entity note list response data. - Mocked context data. When: - Calling the 'vectra_entity_note_list_command' function with valid arguments. Then: - Assert that the CommandResults object contains the expected outputs. - Assert the correctness of the 'outputs_prefix' property. - Assert that the human-readable output matches the content of the expected file. - Assert that the 'Contents' property in the context matches the entity note list data. - Assert that the 'EntryContext' property in the context matches the expected context data. - Assert the correctness of the 'outputs_key_field' property. """ notes_res = util_load_json(f"{TEST_DATA_DIR}/entity_note_list_response.json") context_data = util_load_json(f"{TEST_DATA_DIR}/entity_note_list_context.json") with open(f"{TEST_DATA_DIR}/entity_note_list_hr.md") as f: result_hr = f.read() args = { "entity_id": "1", "entity_type": "account", } url = BASE_URL + ENDPOINTS["ADD_AND_LIST_ENTITY_NOTE_ENDPOINT"].format(args.get("entity_id")) params = {"type": args.get("entity_type")} final_url = add_params_in_url(url, params) requests_mock.get(final_url, json=notes_res) # Call the function result = vectra_entity_note_list_command(client, args) result_context = result.to_context() # Assert the CommandResults assert result.outputs_prefix == "Vectra.Entity.Notes" assert result_context.get("HumanReadable") == result_hr assert result_context.get("Contents") == notes_res assert result_context.get("EntryContext") == remove_empty_elements(context_data) assert result.outputs_key_field == ["entity_id", "entity_type", "note_id"] @pytest.mark.parametrize( "args,error_msg", [ ( {"entity_id": "1", "entity_type": "invalid_type"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)), ), ({"entity_type": "account"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")), ({"entity_id": "0", "entity_type": "account"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "0")), ({"entity_id": "-1", "entity_type": "account"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "-1")), ({"entity_id": "1.5", "entity_type": "account"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "1.5")), ({"entity_id": "1", "entity_type": ""}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type")), ], ) def test_vectra_entity_note_list_invalid_args(client, args, error_msg): """ Given: - A client object. - Arguments specifying different invalid values for entity_id and entity_type. When: - Calling the 'vectra_entity_note_list_command' function with the provided client and arguments. Then: - Assert that the function raises a ValueError. - Assert that the error message matches the expected error message for each invalid argument. """ # Call the function and assert that it raises ValueError with pytest.raises(ValueError) as exception: vectra_entity_note_list_command(client, args) assert str(exception.value) == error_msg def test_vectra_entity_note_list_when_note_response_is_empty(requests_mock, client): """ Given: - A mocked client for requests. - An empty assignment response. When: - Calling the 'vectra_assignment_list_command' function with the provided empty assignment response. Then: - Assert that the CommandResults object contains the appropriate human-readable output for empty results. - Assert that the EntryContext is empty. """ empty_response: list = [] args = { "entity_id": "1", "entity_type": "account", } url = BASE_URL + ENDPOINTS["ADD_AND_LIST_ENTITY_NOTE_ENDPOINT"].format(args.get("entity_id")) params = {"type": args.get("entity_type")} final_url = add_params_in_url(url, params) requests_mock.get(final_url, json=empty_response) # Call the function result = vectra_entity_note_list_command(client, args) result_context = result.to_context() # Assert the CommandResults assert result_context.get("HumanReadable") == "##### Couldn't find any notes for provided entity." assert result_context.get("EntryContext") == {} def test_vectra_group_list_valid_arguments(requests_mock, client): """ Given: - A mocked client for requests. - A mock assignment response. - Expected context data and human-readable output. When: - Calling the 'vectra_group_list_command' function with the provided client and arguments. Then: - Assert that the CommandResults object contains the expected outputs. - Assert that the human-readable output indicates that no results were found. - Assert that the 'EntryContext' property in the context is an empty dictionary. """ group_res = util_load_json(f"{TEST_DATA_DIR}/group_list_response.json") context_data = util_load_json(f"{TEST_DATA_DIR}/group_list_context.json") with open(f"{TEST_DATA_DIR}/group_list_hr.md") as f: result_hr = f.read() requests_mock.get(BASE_URL + ENDPOINTS["GROUP_ENDPOINT"], json=group_res) args = {"group_type": "account", "importance": "high"} # Call the function result = vectra_group_list_command(client, args) result_context = result.to_context() # Assert the CommandResults assert result.outputs_prefix == "Vectra.Group" assert result_context.get("HumanReadable") == result_hr assert result_context.get("EntryContext") == context_data assert result.outputs_key_field == ["group_id"] def test_vectra_group_list_when_response_is_empty(mocker, client): """ Given: - A client object. - Mocked 'list_group_request' method returning an empty response. - Arguments specifying invalid tags. When: - Calling the 'vectra_group_list_command' function with the provided client and arguments. Then: - Assert that the CommandResults object contains the expected outputs. - Assert that the human-readable output indicates that no results were found. - Assert that the 'EntryContext' property in the context is an empty dictionary. """ empty_response: dict = {"count": 0, "next": None, "previous": None, "results": []} mocker.patch.object(client, "list_group_request", return_value=empty_response) # Call the function result = vectra_group_list_command(client, {}) result_context = result.to_context() # Assert the CommandResults assert result_context.get("HumanReadable") == "##### Couldn't find any matching groups for provided filters." assert result_context.get("EntryContext") == {} @pytest.mark.parametrize( "args,error_msg", [ ({"group_type": "invalid"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("group_type", ", ".join(VALID_GROUP_TYPE))), ( {"group_type": "host", "account_names": "account_name"}, ERRORS["INVALID_SUPPORT_FOR_ARG"].format("group_type", "account", "account_names"), ), ( {"group_type": "host", "domains": "domain"}, ERRORS["INVALID_SUPPORT_FOR_ARG"].format("group_type", "domain", "domains"), ), ({"group_type": "account", "host_ids": "1"}, ERRORS["INVALID_SUPPORT_FOR_ARG"].format("group_type", "host", "host_ids")), ({"group_type": "host", "host_ids": "abc"}, 'Invalid number: "{}"="{}"'.format("host_ids", "abc")), ({"group_type": "host", "host_ids": "-1"}, ERRORS["INVALID_INTEGER_VALUE"].format("host_ids", "-1")), ( {"group_type": "account", "host_names": "host_name"}, ERRORS["INVALID_SUPPORT_FOR_ARG"].format("group_type", "host", "host_names"), ), ({"group_type": "host", "ips": "0.0.0.0"}, ERRORS["INVALID_SUPPORT_FOR_ARG"].format("group_type", "ip", "ips")), ({"importance": "invalid"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("importance", ", ".join(VALID_IMPORTANCE_VALUE))), ], ) def test_vectra_group_list_invalid_args(client, args, error_msg): """ Given: - A client object. - Arguments specifying invalid values. When: - Calling the 'vectra_group_list_command' function with the provided client and arguments. Then: - Assert that the function raises a ValueError. - Assert that the error message matches the expected value for the corresponding invalid argument. """ # Call the function and assert that it raises ValueError with pytest.raises(ValueError) as exception: vectra_group_list_command(client, args) assert str(exception.value) == error_msg def test_vectra_assign_domain_group_valid_arguments(requests_mock, client): """ Given: - A client object. - Mocked response data for an existing group. - Arguments specifying a group ID and members to assign. When: - Calling the 'vectra_group_assign_command' function with the provided client and arguments. Then: - Assert that the result's human-readable output, context data, and key field match the expected values for domain type. """ assign_group_res = util_load_json(f"{TEST_DATA_DIR}/assign_group_response.json") groups = util_load_json(f"{TEST_DATA_DIR}/get_groups_response.json") context_data = util_load_json(f"{TEST_DATA_DIR}/assign_group_context.json") # For Domain group with open(f"{TEST_DATA_DIR}/assign_domain_group_hr.md") as f: result_hr = f.read() args = {"group_id": "1", "members": "*.domain3.com,*.domain2.com"} requests_mock.get(BASE_URL + "{}/{}".format(ENDPOINTS["GROUP_ENDPOINT"], args.get("group_id")), json=groups[0]) requests_mock.patch(BASE_URL + "{}/{}".format(ENDPOINTS["GROUP_ENDPOINT"], args.get("group_id")), json=assign_group_res[0]) # Call the function result = vectra_group_assign_command(client, args) result_context = result.to_context() # Assert the CommandResults assert result.outputs_prefix == "Vectra.Group" assert result_context.get("HumanReadable") == result_hr assert result_context.get("EntryContext") == context_data[0] assert result.outputs_key_field == ["group_id"] def test_vectra_assign_account_group_valid_arguments(requests_mock, client): """ Given: - A client object. - Mocked response data for an existing group. - Arguments specifying a group ID and members to assign. When: - Calling the 'vectra_group_assign_command' function with the provided client and arguments. Then: - Assert that the result's human-readable output, context data, and key field match the expected values for account type. """ assign_group_res = util_load_json(f"{TEST_DATA_DIR}/assign_group_response.json") groups = util_load_json(f"{TEST_DATA_DIR}/get_groups_response.json") context_data = util_load_json(f"{TEST_DATA_DIR}/assign_group_context.json") # For Account group with open(f"{TEST_DATA_DIR}/assign_account_group_hr.md") as f: result_hr = f.read() args = {"group_id": "3", "members": "account_3,account_4"} requests_mock.get(BASE_URL + "{}/{}".format(ENDPOINTS["GROUP_ENDPOINT"], args.get("group_id")), json=groups[2]) requests_mock.patch(BASE_URL + "{}/{}".format(ENDPOINTS["GROUP_ENDPOINT"], args.get("group_id")), json=assign_group_res[2]) # Call the function result = vectra_group_assign_command(client, args) result_context = result.to_context() # Assert the CommandResults assert result.outputs_prefix == "Vectra.Group" assert result_context.get("HumanReadable") == result_hr assert result_context.get("EntryContext") == context_data[2] assert result.outputs_key_field == ["group_id"] def test_vectra_assign_host_group_valid_arguments(requests_mock, client): """ Given: - A client object. - Mocked response data for an existing group. - Arguments specifying a group ID and members to assign. When: - Calling the 'vectra_group_assign_command' function with the provided client and arguments. Then: - Assert that the result's human-readable output, context data, and key field match the expected values for host type. """ assign_group_res = util_load_json(f"{TEST_DATA_DIR}/assign_group_response.json") groups = util_load_json(f"{TEST_DATA_DIR}/get_groups_response.json") context_data = util_load_json(f"{TEST_DATA_DIR}/assign_group_context.json") # For Host group with open(f"{TEST_DATA_DIR}/assign_host_group_hr.md") as f: result_hr = f.read() args = {"group_id": "2", "members": "1,2"} requests_mock.get(BASE_URL + "{}/{}".format(ENDPOINTS["GROUP_ENDPOINT"], args.get("group_id")), json=groups[1]) requests_mock.patch(BASE_URL + "{}/{}".format(ENDPOINTS["GROUP_ENDPOINT"], args.get("group_id")), json=assign_group_res[1]) # Call the function result = vectra_group_assign_command(client, args) result_context = result.to_context() # Assert the CommandResults assert result.outputs_prefix == "Vectra.Group" assert result_context.get("HumanReadable") == result_hr assert result_context.get("EntryContext") == context_data[1] assert result.outputs_key_field == ["group_id"] def test_vectra_assign_member_already_exist(requests_mock, client): """ Given: - A client object. - Mocked response data for an existing group. - Arguments specifying a group ID and members to assign. When: - Calling the 'vectra_group_assign_command' function with the provided client and arguments. Then: - Assert that the result's human-readable output indicates that the members are already in the group. """ groups = util_load_json(f"{TEST_DATA_DIR}/assign_group_response.json") args = {"group_id": "2", "members": "1,2"} requests_mock.get(BASE_URL + "{}/{}".format(ENDPOINTS["GROUP_ENDPOINT"], args.get("group_id")), json=groups[1]) # Call the function result = vectra_group_assign_command(client, args) result_context = result.to_context() # Assert the CommandResults assert result_context.get("HumanReadable") == "##### Member(s) 1, 2 are already in the group." @pytest.mark.parametrize( "args,error_msg", [ ({"members": "account1"}, ERRORS["REQUIRED_ARGUMENT"].format("group_id")), ({"group_id": "0", "members": "account1"}, ERRORS["INVALID_INTEGER_VALUE"].format("group_id", "0")), ({"group_id": "-1", "members": "account1"}, ERRORS["INVALID_INTEGER_VALUE"].format("group_id", "-1")), ({"group_id": "1.5", "members": "account1"}, ERRORS["INVALID_INTEGER_VALUE"].format("group_id", "1.5")), ({"group_id": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("members")), ], ) def test_vectra_group_assign_invalid_args(client, args, error_msg): """ Given: - A client object. - Arguments specifying invalid parameters for assigning members to a group. When: - Calling the 'vectra_group_assign_command' function with the provided client and arguments. Then: - Assert that the function raises a ValueError. - Assert that the raised error message matches the expected error message. """ # Call the function and assert that it raises ValueError with pytest.raises(ValueError) as exception: vectra_group_assign_command(client, args) assert str(exception.value) == error_msg def test_vectra_unassign_domain_group_valid_arguments(requests_mock, client): """ Given: - A client object. - Mocked response data for an existing group. - Arguments specifying a group ID and members to assign. When: - Calling the 'vectra_group_unassign_command' function with the provided client and arguments. Then: - Assert that the result's human-readable output, context data, and key field match the expected values for domain type. """ unassign_group_res = util_load_json(f"{TEST_DATA_DIR}/unassign_group_response.json") groups = util_load_json(f"{TEST_DATA_DIR}/get_groups_response.json") context_data = util_load_json(f"{TEST_DATA_DIR}/unassign_group_context.json") # For Domain group with open(f"{TEST_DATA_DIR}/unassign_domain_group_hr.md") as f: result_hr = f.read() args = {"group_id": "1", "members": "*.domain1.net"} requests_mock.get(BASE_URL + "{}/{}".format(ENDPOINTS["GROUP_ENDPOINT"], args.get("group_id")), json=groups[0]) requests_mock.patch(BASE_URL + "{}/{}".format(ENDPOINTS["GROUP_ENDPOINT"], args.get("group_id")), json=unassign_group_res[0]) # Call the function result = vectra_group_unassign_command(client, args) result_context = result.to_context() # Assert the CommandResults assert result.outputs_prefix == "Vectra.Group" assert result_context.get("HumanReadable") == result_hr assert result_context.get("EntryContext") == context_data[0] assert result.outputs_key_field == ["group_id"] def test_vectra_unassign_host_group_valid_arguments(requests_mock, client): """ Given: - A client object. - Mocked response data for an existing group. - Arguments specifying a group ID and members to assign. When: - Calling the 'vectra_group_unassign_command' function with the provided client and arguments. Then: - Assert that the result's human-readable output, context data, and key field match the expected values for host type. """ unassign_group_res = util_load_json(f"{TEST_DATA_DIR}/unassign_group_response.json") groups = util_load_json(f"{TEST_DATA_DIR}/get_groups_response.json") context_data = util_load_json(f"{TEST_DATA_DIR}/unassign_group_context.json") with open(f"{TEST_DATA_DIR}/unassign_host_group_hr.md") as f: result_hr = f.read() args = {"group_id": "2", "members": "3"} requests_mock.get(BASE_URL + "{}/{}".format(ENDPOINTS["GROUP_ENDPOINT"], args.get("group_id")), json=groups[1]) requests_mock.patch(BASE_URL + "{}/{}".format(ENDPOINTS["GROUP_ENDPOINT"], args.get("group_id")), json=unassign_group_res[1]) # Call the function result = vectra_group_unassign_command(client, args) result_context = result.to_context() # Assert the CommandResults assert result.outputs_prefix == "Vectra.Group" assert result_context.get("HumanReadable") == result_hr assert result_context.get("EntryContext") == context_data[1] assert result.outputs_key_field == ["group_id"] def test_vectra_unassign_account_group_valid_arguments(requests_mock, client): """ Given: - A client object. - Mocked response data for an existing group. - Arguments specifying a group ID and members to assign. When: - Calling the 'vectra_group_unassign_command' function with the provided client and arguments. Then: - Assert that the result's human-readable output, context data, and key field match the expected values for account type. """ unassign_group_res = util_load_json(f"{TEST_DATA_DIR}/unassign_group_response.json") groups = util_load_json(f"{TEST_DATA_DIR}/get_groups_response.json") context_data = util_load_json(f"{TEST_DATA_DIR}/unassign_group_context.json") with open(f"{TEST_DATA_DIR}/unassign_account_group_hr.md") as f: result_hr = f.read() args = {"group_id": "3", "members": "account_1"} requests_mock.get(BASE_URL + "{}/{}".format(ENDPOINTS["GROUP_ENDPOINT"], args.get("group_id")), json=groups[2]) requests_mock.patch(BASE_URL + "{}/{}".format(ENDPOINTS["GROUP_ENDPOINT"], args.get("group_id")), json=unassign_group_res[2]) # Call the function result = vectra_group_unassign_command(client, args) result_context = result.to_context() # Assert the CommandResults assert result.outputs_prefix == "Vectra.Group" assert result_context.get("HumanReadable") == result_hr assert result_context.get("EntryContext") == context_data[2] assert result.outputs_key_field == ["group_id"] def test_vectra_unassign_member_already_exist(requests_mock, client): """ Given: - A client object. - Mocked response data for an existing group. - Arguments specifying a group ID and members to assign. When: - Calling the 'vectra_group_unassign_command' function with the provided client and arguments. Then: - Assert that the result's human-readable output indicates that the members are already in the group. """ groups = util_load_json(f"{TEST_DATA_DIR}/assign_group_response.json") args = {"group_id": "2", "members": "6,7"} requests_mock.get(BASE_URL + "{}/{}".format(ENDPOINTS["GROUP_ENDPOINT"], args.get("group_id")), json=groups[1]) # Call the function result = vectra_group_unassign_command(client, args) result_context = result.to_context() # Assert the CommandResults assert result_context.get("HumanReadable") == "##### Member(s) 6, 7 do not exist in the group." @pytest.mark.parametrize( "args,error_msg", [ ({"members": "account1"}, ERRORS["REQUIRED_ARGUMENT"].format("group_id")), ({"group_id": "0", "members": "account1"}, ERRORS["INVALID_INTEGER_VALUE"].format("group_id", "0")), ({"group_id": "-1", "members": "account1"}, ERRORS["INVALID_INTEGER_VALUE"].format("group_id", "-1")), ({"group_id": "1.5", "members": "account1"}, ERRORS["INVALID_INTEGER_VALUE"].format("group_id", "1.5")), ({"group_id": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("members")), ], ) def test_vectra_group_unassign_invalid_args(client, args, error_msg): """ Given: - A client object. - Arguments specifying invalid parameters for assigning members to a group. When: - Calling the 'vectra_group_unassign_command' function with the provided client and arguments. Then: - Assert that the function raises a ValueError. - Assert that the raised error message matches the expected error message. """ # Call the function and assert that it raises ValueError with pytest.raises(ValueError) as exception: vectra_group_unassign_command(client, args) assert str(exception.value) == error_msg @pytest.mark.parametrize("close_reason", ["benign", "remediated"]) def test_vectra_entity_detections_mark_asclosed_valid_arguments(requests_mock, client, close_reason): """ Given: - A client object. - Mocked responses for entity data and marking detections as closed. - Arguments specifying valid parameters for marking detections as closed for an entity. When: - Calling the 'vectra_entity_detections_mark_asclosed_command' function with the provided client and arguments. Then: - Assert that the human-readable output matches the expected output. """ entity_response = util_load_json(f"{TEST_DATA_DIR}/get_entity_response.json") response = {"_meta": {"level": "Success", "message": f"Successfully closed detections as {close_reason}"}} status_response = {"message": {"success": ["Successfully updated detection statuses"]}, "_meta": {"level": "success"}} args = {"entity_id": "334", "entity_type": "account", "close_reason": close_reason} requests_mock.get(BASE_URL + "{}/{}".format(ENDPOINTS["ENTITY_ENDPOINT_v34"], args["entity_id"]), json=entity_response) requests_mock.patch(BASE_URL + ENDPOINTS["DETECTION_CLOSE_ENDPOINT"], json=response) requests_mock.patch(BASE_URL + ENDPOINTS["DETECTION_ENDPOINT"], json=status_response) # Call the function result = vectra_entity_detections_mark_asclosed_command(client, args) result_context = result.to_context() # Assert the CommandResults expected_hr = f"##### The detections (1933, 1934) of the provided entity ID have been successfully closed as {close_reason}." assert result_context.get("HumanReadable") == expected_hr def test_vectra_entity_detections_mark_asclosed_with_no_detections(requests_mock, client): """ Given: - A client object. - Mocked responses for entity data with no detections. - Arguments specifying valid parameters for marking detections as closed for an entity with no detections. When: - Calling the 'vectra_entity_detections_mark_asclosed_command' function with the provided client and arguments. Then: - Assert that the human-readable output matches the expected output indicating no detections to mark as closed. """ args = {"entity_id": "1", "entity_type": "account", "close_reason": "benign"} requests_mock.get( BASE_URL + "{}/{}".format(ENDPOINTS["ENTITY_ENDPOINT_v34"], args["entity_id"]), json={"entity_id": "1", "type": "account"} ) # Call the function result = vectra_entity_detections_mark_asclosed_command(client, args) result_context = result.to_context() # Assert the CommandResults assert result_context.get("HumanReadable") == "There are no active detections to mark as closed for this entity ID: 1." def test_vectra_entity_detections_mark_asclosed_command_invalid_response(requests_mock, client): """ Given: - A mocked client for requests. - A mock entity detection mark as closed invalid response. - Arguments specifying valid parameters for marking detections as closed for an entity. When: - Calling the 'vectra_entity_detections_mark_asclosed_command' function with the provided client and arguments. Then: - Assert that the function raises a DemistoException with the expected error message. """ entity_response = util_load_json(f"{TEST_DATA_DIR}/get_entity_response.json") response = {"_meta": {"level": "Error", "message": "Failed to close detections"}} requests_mock.get(BASE_URL + "{}/{}".format(ENDPOINTS["ENTITY_ENDPOINT_v34"], 334), json=entity_response) requests_mock.patch(BASE_URL + ENDPOINTS["DETECTION_CLOSE_ENDPOINT"], json=response) args = {"entity_id": "334", "entity_type": "account", "close_reason": "benign"} # Capture exception from the function with pytest.raises(DemistoException) as exception: vectra_entity_detections_mark_asclosed_command(client, args) assert str(exception.value) == "Something went wrong. Message: Failed to close detections." @pytest.mark.parametrize( "args,error_msg", [ ({"entity_id": "", "entity_type": "account", "close_reason": "benign"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")), ( {"entity_id": "1", "entity_type": "invalid_type", "close_reason": "benign"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)), ), ({"entity_id": "1", "entity_type": "", "close_reason": "benign"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type")), ({"entity_id": "1", "entity_type": "account", "close_reason": ""}, ERRORS["REQUIRED_ARGUMENT"].format("close_reason")), ( {"entity_id": "1", "entity_type": "account", "close_reason": "invalid_reason"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("close_reason", "benign, remediated"), ), ], ) def test_vectra_entity_detections_mark_asclosed_invalid_args(client, args, error_msg): """ Given: - Invalid arguments for marking detections as closed. When: - Calling the 'vectra_entity_detections_mark_asclosed_command' function with the provided invalid arguments. Then: - Assert that the function raises a ValueError with the expected error message. """ # Call the function and assert that it raises ValueError with pytest.raises(ValueError) as exception: vectra_entity_detections_mark_asclosed_command(client, args) assert str(exception.value) == error_msg def test_vectra_detections_mark_asclosed_invalid_response(requests_mock, client): """ Given: - A mocked client for requests. - A mock detection mark as closed invalid response. - Arguments specifying valid detection IDs and close reason to mark as closed. When: - Calling the 'vectra_detections_mark_asclosed_command' function with the provided client and arguments. Then: - Assert that the function raises a DemistoException with the expected error message. """ response = {"_meta": {"level": "Error", "message": "Failed to close detections"}} requests_mock.patch(BASE_URL + ENDPOINTS["DETECTION_CLOSE_ENDPOINT"], json=response) args = {"detection_ids": "1,2,3", "close_reason": "benign"} # Capture exception from the function with pytest.raises(DemistoException) as exception: vectra_detections_mark_asclosed_command(client, args) assert str(exception.value) == "Something went wrong. Message: Failed to close detections." @pytest.mark.parametrize( "args,error_msg", [ ({}, ERRORS["REQUIRED_ARGUMENT"].format("detection_ids")), ({"detection_ids": "1,2,3"}, ERRORS["REQUIRED_ARGUMENT"].format("close_reason")), ({"detection_ids": "as,2", "close_reason": "benign"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_ids", "as")), ( {"detection_ids": "1,2, , , ,,,3", "close_reason": "benign"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_ids", ""), ), ( {"detection_ids": "1,2,3", "close_reason": "invalid"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("close_reason", "benign, remediated"), ), ], ) def test_vectra_detections_mark_asclosed_invalid_args(client, args, error_msg): """ Given: - A client object. - Invalid arguments (missing detection_ids, missing close_reason, invalid detection_ids, invalid close_reason). When: - Calling the 'vectra_detections_mark_asclosed_command' function with the provided client and arguments. Then: - Assert that the function raises a ValueError with the expected error message. """ # Call the function and assert that it raises ValueError with pytest.raises(ValueError) as exception: vectra_detections_mark_asclosed_command(client, args) assert str(exception.value) == error_msg def test_vectra_detections_mark_asopen_valid_arguments(requests_mock, client): """ Given: - A mocked client for requests. - A mock detection mark as open response. - Arguments specifying valid detection IDs to mark as open. When: - Calling the 'vectra_detections_mark_asopen_command' function with the provided client and arguments. Then: - Assert that the CommandResults object contains the expected outputs. - Assert that the human-readable output matches the expected content. """ response = {"_meta": {"level": "success", "message": "Successfully re-opened detections"}} status_response = {"message": {"success": ["Successfully updated detection statuses"]}, "_meta": {"level": "success"}} requests_mock.patch(BASE_URL + ENDPOINTS["DETECTION_OPEN_ENDPOINT"], json=response) requests_mock.patch(os.path.join(BASE_URL, ENDPOINTS["DETECTION_ENDPOINT"]), json=status_response) args = {"detection_ids": "1,2,3"} # Call the function result = vectra_detections_mark_asopen_command(client, args) result_context = result.to_context() expected_hr = "##### The provided detection IDs have been successfully re-opened." # Assert the CommandResults assert result_context.get("HumanReadable") == expected_hr assert result_context.get("EntryContext") == {} def test_vectra_detections_mark_asopen_invalid_response(requests_mock, client): """ Given: - A mocked client for requests. - A mock detection mark as open invalid response. - Arguments specifying valid detection IDs to mark as open. When: - Calling the 'vectra_detections_mark_asopen_command' function with the provided client and arguments. Then: - Assert that the function raises a DemistoException with the expected error message. """ response = {"_meta": {"level": "Error", "message": "Failed to open detections"}} requests_mock.patch(BASE_URL + ENDPOINTS["DETECTION_OPEN_ENDPOINT"], json=response) args = {"detection_ids": "1,2,3"} # Capture exception from the function with pytest.raises(DemistoException) as exception: vectra_detections_mark_asopen_command(client, args) assert str(exception.value) == "Something went wrong. Message: Failed to open detections." @pytest.mark.parametrize( "args,error_msg", [ ({}, ERRORS["REQUIRED_ARGUMENT"].format("detection_ids")), ({"detection_ids": ""}, ERRORS["REQUIRED_ARGUMENT"].format("detection_ids", "")), ({"detection_ids": "as,2"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_ids", "as")), ({"detection_ids": "1,2, , ,3"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_ids", "")), ], ) def test_vectra_detections_mark_asopen_invalid_args(client, args, error_msg): """ Given: - A client object. - Invalid arguments (missing detection_ids, empty detection_ids, invalid detection_ids). When: - Calling the 'vectra_detections_mark_asopen_command' function with the provided client and arguments. Then: - Assert that the function raises a ValueError with the expected error message. """ # Call the function and assert that it raises ValueError with pytest.raises(ValueError) as exception: vectra_detections_mark_asopen_command(client, args) assert str(exception.value) == error_msg def test_vectra_detection_tag_list_valid_arguments(requests_mock, client): """ Given: - A client object. - Mocked 'list_detection_tags_request' method returning tags response. - The expected human-readable output file. - Arguments specifying valid parameters for listing the tags of a detection. When: - Calling the 'vectra_detection_tag_list_command' function with the provided client and arguments. Then: - Assert that the CommandResults object contains the expected outputs. - Assert the correctness of the 'outputs_prefix' property. - Assert that the human-readable output matches the expected format. - Assert that the 'Contents' property in the context matches the tags response. - Assert that the 'EntryContext' property in the context matches the context data. - Assert the correctness of the 'outputs_key_field' property. """ get_tags_res = {"status": "success", "tag_id": "36", "tags": ["tag1", "tag2"]} requests_mock.get(BASE_URL + ENDPOINTS["LIST_TAGS_ENDPOINT"].format(123), json=get_tags_res) args = {"detection_id": "123"} # Call the function result = vectra_detection_tag_list_command(client, args) result_context = result.to_context() get_tags_res.update({"detection_id": 123}) # type: ignore del get_tags_res["status"] # Assert the CommandResults assert result.outputs_prefix == "Vectra.Detection.Tags" assert "##### List of tags: **tag1, tag2**" in result_context.get("HumanReadable") assert result_context.get("Contents") == get_tags_res assert result.outputs_key_field == ["tag_id", "detection_id"] def test_vectra_detection_tag_list_with_empty_tag_response(mocker, client): """ Given: - A client object. - Mocked 'list_detection_tags_request' method returning empty tags response. - Arguments specifying valid parameters for listing the tags of a detection. When: - Calling the 'vectra_detection_tag_list_command' function with the provided client and arguments. Then: - Assert that the CommandResults object contains the expected outputs. - Assert the correctness of the 'outputs_prefix' property. - Assert that the human-readable output indicates no tags were found. - Assert that the 'Contents' property in the context matches the tags response. - Assert the correctness of the 'outputs_key_field' property. """ get_tags_res = {"status": "success", "tag_id": "36", "tags": []} mocker.patch.object(client, "list_detection_tags_request", return_value=get_tags_res) args = {"detection_id": "123"} # Call the function result = vectra_detection_tag_list_command(client, args) result_context = result.to_context() get_tags_res.update({"detection_id": 123}) # type: ignore get_tags_res.pop("status", None) # Assert the CommandResults assert result.outputs_prefix == "Vectra.Detection.Tags" assert result_context.get("HumanReadable") == "##### No tags were found for the given detection ID." assert result_context.get("Contents") == get_tags_res assert result.outputs_key_field == ["tag_id", "detection_id"] @pytest.mark.parametrize( "args,error_msg", [ ({}, ERRORS["REQUIRED_ARGUMENT"].format("detection_id")), ({"detection_id": "0"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "0")), ({"detection_id": "-1"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "-1")), ({"detection_id": "1.5"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "1.5")), ({"detection_id": "abc"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "abc")), ], ) def test_vectra_detection_tag_list_invalid_args(client, args, error_msg): """ Given: - A client object. - Arguments specifying different invalid values for detection_id. When: - Calling the 'vectra_detection_tag_list_command' function with the provided client and arguments. Then: - Assert that the function raises a ValueError. - Assert that the error message matches the expected error message for each invalid argument. """ # Call the function and assert that it raises ValueError with pytest.raises(ValueError) as exception: vectra_detection_tag_list_command(client, args) assert str(exception.value) == error_msg def test_vectra_detection_tag_list_when_response_is_invalid(mocker, client): """ Given: - A client object. - Mocked 'list_detection_tags_request' method returning invalid response. - Arguments specifying valid detection_id. When: - Calling the 'vectra_detection_tag_list_command' function with the provided client and arguments. Then: - Assert that the function raises a DemistoException. - Assert that the error message indicates something went wrong. """ get_tags_res = {"status": "error", "message": "Detection not found"} mocker.patch.object(client, "list_detection_tags_request", return_value=get_tags_res) args = {"detection_id": "123"} # Call the function with pytest.raises(DemistoException) as exception: vectra_detection_tag_list_command(client, args) assert str(exception.value) == f"Something went wrong. Message: {get_tags_res.get('message')}." def test_vectra_detection_tag_list_when_response_has_no_status(mocker, client): """ Given: - A client object. - Mocked 'list_detection_tags_request' method returning response without status. - Arguments specifying valid detection_id. When: - Calling the 'vectra_detection_tag_list_command' function with the provided client and arguments. Then: - Assert that the function raises a DemistoException. - Assert that the error message indicates something went wrong. """ get_tags_res = {"tag_id": "36", "tags": ["tag1"]} mocker.patch.object(client, "list_detection_tags_request", return_value=get_tags_res) args = {"detection_id": "123"} # Call the function with pytest.raises(DemistoException) as exception: vectra_detection_tag_list_command(client, args) assert str(exception.value) == "Something went wrong." def test_vectra_detection_tag_add_valid_arguments(requests_mock, client): """ Given: - A mocked client for requests. - A mock get and update tag response. - The expected human-readable output file. - Arguments specifying valid parameters for adding the tags to an entity. When: - Calling the 'vectra_detection_tag_add_command' function with the provided client and arguments. Then: - Assert that the CommandResults object contains the expected outputs. - Assert the correctness of the 'outputs_prefix' property. - Assert that the human-readable output matches the content of the expected file. - Assert that the 'Contents' property in the context matches the tags response. - Assert that the 'EntryContext' property in the context matches the context data. - Assert the correctness of the 'outputs_key_field' property. """ add_tags_res = util_load_json(f"{TEST_DATA_DIR}/detection_tag_add_response.json") get_tags_res = util_load_json(f"{TEST_DATA_DIR}/detection_tag_get_response.json") context_data = util_load_json(f"{TEST_DATA_DIR}/detection_tag_add_context.json") with open(f"{TEST_DATA_DIR}/detection_tag_add_hr.md") as f: result_hr = f.read() requests_mock.get(BASE_URL + ENDPOINTS["LIST_TAGS_ENDPOINT"].format(1), json=get_tags_res) requests_mock.patch(BASE_URL + ENDPOINTS["LIST_TAGS_ENDPOINT"].format(1), json=add_tags_res) args = { "detection_id": "1", "tags": "tag1, tag2", } # Call the function result = vectra_detection_tag_add_command(client, args) result_context = result.to_context() add_tags_res.update({"detection_id": 1}) del add_tags_res["status"] # Assert the CommandResults assert result.outputs_prefix == "Vectra.Detection.Tags" assert result_context.get("HumanReadable") == result_hr assert result_context.get("Contents") == add_tags_res assert result_context.get("EntryContext") == remove_empty_elements(context_data) assert result.outputs_key_field == ["tag_id", "detection_id"] @pytest.mark.parametrize( "args,error_msg", [ ({"tags": "tag1"}, ERRORS["REQUIRED_ARGUMENT"].format("detection_id")), ({"detection_id": "1", "tags": ""}, ERRORS["REQUIRED_ARGUMENT"].format("tags")), ({"detection_id": "1", "tags": " , "}, ERRORS["REQUIRED_ARGUMENT"].format("tags")), ({"detection_id": "0", "tags": "tag1"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "0")), ({"detection_id": "-1", "tags": "tag1"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "-1")), ({"detection_id": "1.5", "tags": "tag1"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "1.5")), ({"detection_id": "abc", "tags": "tag1"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "abc")), ], ) def test_vectra_detection_tag_add_invalid_args(client, args, error_msg): """ Given: - A client object. - Arguments specifying different invalid values for entity_id, entity_type, and tags. When: - Calling the 'vectra_detection_tag_add_command' function with the provided client and arguments. Then: - Assert that the function raises a ValueError. - Assert that the error message matches the expected error message for each invalid argument. """ # Call the function and assert that it raises ValueError with pytest.raises(ValueError) as exception: vectra_detection_tag_add_command(client, args) assert str(exception.value) == error_msg def test_vectra_detection_tag_add_when_get_tag_response_is_invalid(requests_mock, client): """ Given: - A client object. - Mocked 'list_detection_tags_request' method returning invalid response. - Arguments specifying invalid tags. When: - Calling the 'vectra_detection_tag_add_command' function with the provided client and arguments. Then: - Assert that the human-readable output indicates that invalid result was found. """ get_tags_res = util_load_json(f"{TEST_DATA_DIR}/detection_tag_get_invalid_response.json") requests_mock.get(BASE_URL + ENDPOINTS["LIST_TAGS_ENDPOINT"].format(1), json=get_tags_res) args = { "detection_id": "1", "tags": "tag1, tag2", } # Call the function with pytest.raises(DemistoException) as exception: vectra_detection_tag_add_command(client, args) assert str(exception.value) == f"Something went wrong. Message: {get_tags_res.get('message')}." def test_vectra_detection_tag_add_when_add_tag_response_is_invalid(requests_mock, client): """ Given: - A client object. - Mocked 'update_detection_tags_request' method returning invalid response. - Arguments specifying invalid tags. When: - Calling the 'vectra_detection_tag_add_command' function with the provided client and arguments. Then: - Assert that the human-readable output indicates that invalid result was found. """ get_tags_res = util_load_json(f"{TEST_DATA_DIR}/detection_tag_get_response.json") add_tags_res = util_load_json(f"{TEST_DATA_DIR}/detection_tag_get_invalid_response.json") requests_mock.get(BASE_URL + ENDPOINTS["LIST_TAGS_ENDPOINT"].format(1), json=get_tags_res) requests_mock.patch(BASE_URL + ENDPOINTS["LIST_TAGS_ENDPOINT"].format(1), json=add_tags_res) args = { "detection_id": "1", "tags": "tag1, tag2", } # Call the function with pytest.raises(DemistoException) as exception: vectra_detection_tag_add_command(client, args) assert str(exception.value) == f"Something went wrong. Message: {add_tags_res.get('message')}." def test_vectra_detection_tag_remove_valid_arguments(requests_mock, client): """ Given: - A client object. - Mocked 'update_detection_tags_request' method returning tags response. - The expected human-readable output file. - Arguments specifying valid parameters for adding the tags to an entity. When: - Calling the 'vectra_detection_tag_remove_command' function with the provided client and arguments. Then: - Assert that the CommandResults object contains the expected outputs. - Assert the correctness of the 'outputs_prefix' property. - Assert that the human-readable output matches the content of the expected file. - Assert that the 'Contents' property in the context matches the tags response. - Assert that the 'EntryContext' property in the context matches the context data. - Assert the correctness of the 'outputs_key_field' property. """ remove_tags_res = util_load_json(f"{TEST_DATA_DIR}/detection_tag_remove_response.json") get_tags_res = util_load_json(f"{TEST_DATA_DIR}/detection_tag_get_response.json") context_data = util_load_json(f"{TEST_DATA_DIR}/detection_tag_remove_context.json") with open(f"{TEST_DATA_DIR}/detection_tag_remove_hr.md") as f: result_hr = f.read() requests_mock.get(BASE_URL + ENDPOINTS["LIST_TAGS_ENDPOINT"].format(1), json=get_tags_res) requests_mock.patch(BASE_URL + ENDPOINTS["LIST_TAGS_ENDPOINT"].format(1), json=remove_tags_res) args = {"detection_id": "1", "tags": "tag,tag2"} # Call the function result = vectra_detection_tag_remove_command(client, args) result_context = result.to_context() remove_tags_res.update({"detection_id": 1}) del remove_tags_res["status"] # Assert the CommandResults assert result.outputs_prefix == "Vectra.Detection.Tags" assert result_context.get("HumanReadable") == result_hr assert result_context.get("Contents") == remove_tags_res assert result_context.get("EntryContext") == remove_empty_elements(context_data) assert result.outputs_key_field == ["tag_id", "detection_id"] @pytest.mark.parametrize( "args,error_msg", [ ({"tags": "tag1"}, ERRORS["REQUIRED_ARGUMENT"].format("detection_id")), ({"detection_id": "1", "tags": ""}, ERRORS["REQUIRED_ARGUMENT"].format("tags")), ({"detection_id": "1", "tags": " , "}, ERRORS["REQUIRED_ARGUMENT"].format("tags")), ({"detection_id": "0", "tags": "tag1"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "0")), ({"detection_id": "-1", "tags": "tag1"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "-1")), ({"detection_id": "1.5", "tags": "tag1"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "1.5")), ({"detection_id": "abc", "tags": "tag1"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "abc")), ], ) def test_vectra_detection_tag_remove_invalid_args(client, args, error_msg): """ Given: - A client object. - Arguments specifying different invalid values for entity_id and entity_type. When: - Calling the 'vectra_detection_tag_remove_command' function with the provided client and arguments. Then: - Assert that the function raises a ValueError. - Assert that the error message matches the expected error message for each invalid argument. """ # Call the function and assert that it raises ValueError with pytest.raises(ValueError) as exception: vectra_detection_tag_remove_command(client, args) assert str(exception.value) == error_msg def test_vectra_detection_tag_remove_when_get_tag_response_is_invalid(requests_mock, client): """ Given: - A client object. - Mocked 'list_detection_tags_request' method returning invalid response. - Arguments specifying invalid tags. When: - Calling the 'vectra_detection_tag_remove_command' function with the provided client and arguments. Then: - Assert that the human-readable output indicates that invalid result was found. """ get_tags_res = util_load_json(f"{TEST_DATA_DIR}/detection_tag_get_invalid_response.json") requests_mock.get(BASE_URL + ENDPOINTS["LIST_TAGS_ENDPOINT"].format(1), json=get_tags_res) args = { "detection_id": "1", "tags": "tag2", } # Call the function with pytest.raises(DemistoException) as exception: vectra_detection_tag_remove_command(client, args) assert str(exception.value) == f"Something went wrong. Message: {get_tags_res.get('message')}." def test_vectra_detection_tag_remove_when_remove_tag_response_is_invalid(requests_mock, client): """ Given: - A client object. - Mocked 'update_detection_tags_request' method returning invalid response. - Arguments specifying invalid tags. When: - Calling the 'vectra_detection_tag_remove_command' function with the provided client and arguments. Then: - Assert that the human-readable output indicates that invalid result was found. """ get_tags_res = util_load_json(f"{TEST_DATA_DIR}/detection_tag_get_response.json") remove_tags_res = util_load_json(f"{TEST_DATA_DIR}/detection_tag_get_invalid_response.json") requests_mock.get(BASE_URL + ENDPOINTS["LIST_TAGS_ENDPOINT"].format(1), json=get_tags_res) requests_mock.patch(BASE_URL + ENDPOINTS["LIST_TAGS_ENDPOINT"].format(1), json=remove_tags_res) args = { "detection_id": "1", "tags": "tag, tag2", } # Call the function with pytest.raises(DemistoException) as exception: vectra_detection_tag_remove_command(client, args) assert str(exception.value) == f"Something went wrong. Message: {remove_tags_res.get('message')}." def test_vectra_entity_detection_list_passes_entity_id_and_type(mocker, client): """ Ensure entity_id and entity_type are passed correctly to list_detections_request. """ entity_id = 42 entity_type = "host" # Mock entity response with detection_set entity_data = {"detection_set": ["https://api/v3.3/detections/123"]} mocker.patch.object(client, "get_entity_request", return_value=entity_data) # Use realistic detection data with 'url' key detections_data = util_load_json(f"{TEST_DATA_DIR}/entity_detection_list_response.json") mock_list = mocker.patch.object(client, "list_detections_request", return_value=detections_data) args = {"entity_id": str(entity_id), "entity_type": entity_type} vectra_entity_detection_list_command(client, args) # Assert correct values are passed mock_list.assert_called_once() call_kwargs = mock_list.call_args.kwargs assert call_kwargs["entity_id"] == entity_id assert call_kwargs["entity_type"] == entity_type def test_vectra_detection_note_list_valid_arguments(requests_mock, client): """ Given: - A mocked 'requests_mock' to simulate API responses. - A client object. - Mocked detection note list response data. - Mocked context data. When: - Calling the 'vectra_detection_note_list_command' function with valid arguments. Then: - Assert that the CommandResults object contains the expected outputs. - Assert the correctness of the 'outputs_prefix' property. - Assert that the human-readable output matches the content of the expected file. - Assert that the 'Contents' property in the context matches the detection note list data. - Assert that the 'EntryContext' property in the context matches the expected context data. - Assert the correctness of the 'outputs_key_field' property. """ notes_res = util_load_json(f"{TEST_DATA_DIR}/detection_note_list_response.json") context_data = util_load_json(f"{TEST_DATA_DIR}/detection_note_list_context.json") with open(f"{TEST_DATA_DIR}/detection_note_list_hr.md") as f: result_hr = f.read() args = { "detection_id": "1", } url = BASE_URL + ENDPOINTS["ADD_AND_LIST_DETECTION_NOTE_ENDPOINT"].format(args.get("detection_id")) requests_mock.get(url, json=notes_res) # Call the function result = vectra_detection_note_list_command(client, args) result_context = result.to_context() # Assert the CommandResults assert result.outputs_prefix == "Vectra.Detection.Notes" assert result_context.get("HumanReadable") == result_hr assert result_context.get("Contents") == notes_res assert result_context.get("EntryContext") == remove_empty_elements(context_data) assert result.outputs_key_field == ["detection_id", "note_id"] def test_vectra_detection_note_add_valid_arguments(requests_mock, client): """ Given: - A mocked client for requests. - A mock notes response. - The expected human-readable output file. - Arguments specifying valid parameters for adding a note to a detection. When: - Calling the 'vectra_detection_note_add_command' function with the provided client and arguments. Then: - Assert that the CommandResults object contains the expected outputs. - Assert the correctness of the 'outputs_prefix' property. - Assert that the human-readable output matches the content of the expected file. - Assert that the 'Contents' property in the context matches the notes response. - Assert that the 'EntryContext' property in the context matches the context data. - Assert the correctness of the 'outputs_key_field' property. """ notes_res = util_load_json(f"{TEST_DATA_DIR}/detection_note_add_response.json") context_data = util_load_json(f"{TEST_DATA_DIR}/detection_note_add_context.json") requests_mock.post(BASE_URL + ENDPOINTS["ADD_AND_LIST_DETECTION_NOTE_ENDPOINT"].format("1"), json=notes_res) with open(f"{TEST_DATA_DIR}/detection_note_add_hr.md") as f: result_hr = f.read() args = { "detection_id": "1", "note": "test_note", } # Call the function result = vectra_detection_note_add_command(client, args) result_context = result.to_context() notes_res["note_id"] = notes_res["id"] notes_res["detection_id"] = 1 # Assert the CommandResults assert result.outputs_prefix == "Vectra.Detection.Notes" assert result_context.get("HumanReadable") == result_hr assert result_context.get("Contents") == notes_res assert result_context.get("EntryContext") == context_data assert result.outputs_key_field == ["detection_id", "note_id"] @pytest.mark.parametrize( "args,error_msg", [ ({"detection_id": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("note")), ({"note": "test_note"}, ERRORS["REQUIRED_ARGUMENT"].format("detection_id")), ({"detection_id": "0", "note": "test_note"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "0")), ({"detection_id": "-1", "note": "test_note"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "-1")), ( {"detection_id": "1.5", "note": "test_note"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "1.5"), ), ], ) def test_vectra_detection_note_add_invalid_args(client, args, error_msg): """ Given: - A client object. - Arguments specifying different invalid values for detection_id, and note. When: - Calling the 'vectra_detection_note_add_command' function with the provided client and arguments. Then: - Assert that the function raises a ValueError. - Assert that the error message matches the expected error message for each invalid argument. """ # Call the function and assert that it raises ValueError with pytest.raises(ValueError) as exception: vectra_detection_note_add_command(client, args) assert str(exception.value) == error_msg def test_vectra_detection_note_update_valid_arguments(requests_mock, client): """ Given: - A mocked client for requests. - A mock note response. - The expected human-readable output file. - Arguments specifying valid parameters for updating a note of an entity. When: - Calling the 'vectra_detection_note_update_command' function with the provided client and arguments. Then: - Assert that the CommandResults object contains the expected outputs. - Assert the correctness of the 'outputs_prefix' property. - Assert that the human-readable output matches the content of the expected file. - Assert that the 'Contents' property in the context matches the notes response. - Assert that the 'EntryContext' property in the context matches the context data. - Assert the correctness of the 'outputs_key_field' property. """ notes_res = util_load_json(f"{TEST_DATA_DIR}/detection_note_update_response.json") context_data = util_load_json(f"{TEST_DATA_DIR}/detection_note_update_context.json") requests_mock.patch(BASE_URL + ENDPOINTS["UPDATE_AND_REMOVE_DETECTION_NOTE_ENDPOINT"].format(1, 1), json=notes_res) with open(f"{TEST_DATA_DIR}/detection_note_update_hr.md") as f: result_hr = f.read() args = { "detection_id": "1", "note_id": "1", "note": "test_note", } # Call the function result = vectra_detection_note_update_command(client, args) result_context = result.to_context() notes_res["note_id"] = notes_res["id"] notes_res["detection_id"] = 1 # Assert the CommandResults assert result.outputs_prefix == "Vectra.Detection.Notes" assert result_context.get("HumanReadable") == result_hr assert result_context.get("Contents") == notes_res assert result_context.get("EntryContext") == context_data assert result.outputs_key_field == ["detection_id", "note_id"] @pytest.mark.parametrize( "args,error_msg", [ ({"detection_id": "1", "note_id": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("note")), ({"note": "test_note", "note_id": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("detection_id")), ({"detection_id": "1", "note": "test_note"}, ERRORS["REQUIRED_ARGUMENT"].format("note_id")), ( {"detection_id": "0", "note": "test_note", "note_id": "1"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "0"), ), ( {"detection_id": "-1", "note": "test_note", "note_id": "1"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "-1"), ), ( {"detection_id": "1.5", "note": "test_note", "note_id": "1"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "1.5"), ), ( {"note_id": "0", "note": "test_note", "detection_id": "1"}, ERRORS["INVALID_INTEGER_VALUE"].format("note_id", "0"), ), ( {"note_id": "-1", "note": "test_note", "detection_id": "1"}, ERRORS["INVALID_INTEGER_VALUE"].format("note_id", "-1"), ), ( {"note_id": "1.5", "note": "test_note", "detection_id": "1"}, ERRORS["INVALID_INTEGER_VALUE"].format("note_id", "1.5"), ), ], ) def test_vectra_detection_note_update_invalid_args(client, args, error_msg): """ Given: - A client object. - Invalid arguments for updating a note of an entity. When: - Calling the 'vectra_detection_note_update_command' function with the provided client and arguments. Then: - Assert that a ValueError is raised with the expected error message. """ # Call the function and assert that it raises ValueError with pytest.raises(ValueError) as exception: vectra_detection_note_update_command(client, args) assert str(exception.value) == error_msg def test_vectra_detection_note_remove_valid_arguments(requests_mock, client): """ Tests the 'vectra_detection_note_remove_command' function with valid arguments. Ensures that the function removes a detection note and returns the expected CommandResults object. Args: requests_mock: The requests mocker object. client: The VectraClient instance. Returns: None. Raises an AssertionError if the test fails. """ requests_mock.delete(BASE_URL + ENDPOINTS["UPDATE_AND_REMOVE_DETECTION_NOTE_ENDPOINT"].format(1, 1), status_code=204) with open(f"{TEST_DATA_DIR}/detection_note_remove_hr.md") as f: result_hr = f.read() args = { "detection_id": "1", "note_id": "1", } # Call the function result = vectra_detection_note_remove_command(client, args) result_context = result.to_context() # Assert the CommandResults assert result_context.get("HumanReadable") == result_hr assert result_context.get("EntryContext") == {} def test_vectra_detection_note_remove_invalid_status_code(requests_mock, client): """ Tests the 'vectra_detection_note_remove_command' function with valid arguments. Ensures that the function gives error in HR for status code. Args: requests_mock: The requests mocker object. client: The VectraClient instance. Returns: None. Raises an AssertionError if the test fails. """ requests_mock.delete(BASE_URL + ENDPOINTS["UPDATE_AND_REMOVE_DETECTION_NOTE_ENDPOINT"].format(1, 1), status_code=200) args = { "detection_id": "1", "note_id": "1", } # Call the function result = vectra_detection_note_remove_command(client, args) result_context = result.to_context() # Assert the CommandResults assert result_context.get("HumanReadable") == "Something went wrong." assert result_context.get("EntryContext") == {} @pytest.mark.parametrize( "args,error_msg", [ ({"note": "test_note", "note_id": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("detection_id")), ({"detection_id": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("note_id")), ( {"detection_id": "0", "note": "test_note", "note_id": "1"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "0"), ), ( {"detection_id": "-1", "note": "test_note", "note_id": "1"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "-1"), ), ( {"detection_id": "1.5", "note": "test_note", "note_id": "1"}, ERRORS["INVALID_INTEGER_VALUE"].format("detection_id", "1.5"), ), ( {"note_id": "0", "note": "test_note", "detection_id": "1"}, ERRORS["INVALID_INTEGER_VALUE"].format("note_id", "0"), ), ( {"note_id": "-1", "note": "test_note", "detection_id": "1"}, ERRORS["INVALID_INTEGER_VALUE"].format("note_id", "-1"), ), ( {"note_id": "1.5", "note": "test_note", "detection_id": "1"}, ERRORS["INVALID_INTEGER_VALUE"].format("note_id", "1.5"), ), ], ) def test_vectra_detection_note_remove_invalid_args(client, args, error_msg): """ Given: - A client object. - Invalid arguments for updating a note of an entity. When: - Calling the 'vectra_detection_note_remove_command' function with the provided client and arguments. Then: - Assert that a ValueError is raised with the expected error message. """ # Call the function and assert that it raises ValueError with pytest.raises(ValueError) as exception: vectra_detection_note_remove_command(client, args) assert str(exception.value) == error_msg @pytest.mark.parametrize("entity_type", ["account", "host"]) def test_vectra_entity_unresolved_priority_reset_valid_arguments(requests_mock, client, entity_type): """ Given: - A client object. - Valid arguments for marking entity unresolved priority as false. - Mocked API response for updating entity unresolved priority status. When: - Calling the 'vectra_entity_unresolved_priority_reset_command' function with valid entity_id and entity_type. Then: - Assert that the CommandResults object contains the expected outputs. - Assert the correctness of the 'outputs_prefix' property. - Assert that the human-readable output matches the expected message. - Assert that the 'EntryContext' property contains the correct entity_id and entity_type. - Assert the correctness of the 'outputs_key_field' property. """ entity_id = "123" mock_response = {"message": {"success": ["Successfully updated unresolved_priority"]}, "_meta": {"level": "success"}} url = f"{BASE_URL}{ENDPOINTS['ENTITY_ENDPOINT']}/{entity_id}?type={entity_type}" requests_mock.patch(url, json=mock_response, status_code=200) args = {"entity_id": entity_id, "entity_type": entity_type} result = vectra_entity_unresolved_priority_reset_command(client, args) result_context = result.to_context() assert result.outputs_prefix == "Vectra.Entity" assert result.outputs_key_field == ["id", "type"] assert ( result_context.get("HumanReadable") == "##### The unresolved priority of the provided entity has been successfully changed as 'false'." ) entry_context = result_context.get("EntryContext") context_key = "Vectra.Entity(val.id && val.id == obj.id && val.type && val.type == obj.type)" assert entry_context.get(context_key) is not None outputs = entry_context.get(context_key) assert outputs.get("id") == entity_id assert outputs.get("type") == entity_type assert outputs.get("unresolved_priority") is False def test_vectra_entity_unresolved_priority_reset_with_uppercase_entity_type(requests_mock, client): """ Given: - A client object. - Valid arguments with uppercase entity_type. When: - Calling the 'vectra_entity_unresolved_priority_reset_command' function with uppercase entity_type. Then: - Assert that the function correctly converts entity_type to lowercase when making the API call. - Assert that the CommandResults object contains the expected outputs. """ entity_id = "456" entity_type = "Account" mock_response = {"message": {"success": ["Successfully updated unresolved_priority"]}, "_meta": {"level": "success"}} url = f"{BASE_URL}{ENDPOINTS['ENTITY_ENDPOINT']}/{entity_id}?type=account" requests_mock.patch(url, json=mock_response, status_code=200) args = {"entity_id": entity_id, "entity_type": entity_type} result = vectra_entity_unresolved_priority_reset_command(client, args) result_context = result.to_context() assert result.outputs_prefix == "Vectra.Entity" assert ( result_context.get("HumanReadable") == "##### The unresolved priority of the provided entity has been successfully changed as 'false'." ) context_key = "Vectra.Entity(val.id && val.id == obj.id && val.type && val.type == obj.type)" outputs = result_context.get("EntryContext").get(context_key) assert outputs.get("id") == entity_id assert outputs.get("type") == entity_type assert outputs.get("unresolved_priority") is False def test_vectra_entity_unresolved_priority_reset_removes_nulls(requests_mock, client): """ Given: - A client object. - API response containing null values. When: - Calling the 'vectra_entity_unresolved_priority_reset_command' function. Then: - Assert that null values are removed from the response before being added to outputs. """ entity_id = "789" entity_type = "host" mock_response = {"message": {"success": ["Successfully updated unresolved_priority"]}, "_meta": {"level": "success"}} url = f"{BASE_URL}{ENDPOINTS['ENTITY_ENDPOINT']}/{entity_id}?type={entity_type}" requests_mock.patch(url, json=mock_response, status_code=200) args = {"entity_id": entity_id, "entity_type": entity_type} result = vectra_entity_unresolved_priority_reset_command(client, args) result_context = result.to_context() context_key = "Vectra.Entity(val.id && val.id == obj.id && val.type && val.type == obj.type)" outputs = result_context.get("EntryContext").get(context_key) assert outputs.get("id") == entity_id assert outputs.get("type") == entity_type assert outputs.get("unresolved_priority") is False @pytest.mark.parametrize( "args,error_msg", [ ({"entity_type": "account"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")), ({"entity_id": None, "entity_type": "account"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")), ({"entity_id": "123"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type")), ({"entity_id": "123", "entity_type": None}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type")), ({"entity_id": "123", "entity_type": ""}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type")), ( {"entity_id": "0", "entity_type": "account"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "0"), ), ( {"entity_id": "-1", "entity_type": "account"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "-1"), ), ( {"entity_id": "1.5", "entity_type": "account"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "1.5"), ), ( {"entity_id": "abc", "entity_type": "account"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "abc"), ), ( {"entity_id": "123", "entity_type": "invalid_type"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)), ), ( {"entity_id": "123", "entity_type": "user"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)), ), ( {"entity_id": "123", "entity_type": "detection"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)), ), ], ) def test_vectra_entity_unresolved_priority_reset_invalid_args(client, args, error_msg): """ Given: - A client object. - Invalid arguments for marking entity unresolved priority as false. When: - Calling the 'vectra_entity_unresolved_priority_reset_command' function with invalid arguments. Then: - Assert that a ValueError is raised. - Assert that the error message matches the expected error message for each invalid argument. """ with pytest.raises(ValueError) as exception: vectra_entity_unresolved_priority_reset_command(client, args) assert str(exception.value) == error_msg def test_vectra_entity_unresolved_priority_reset_api_error(requests_mock, client): """ Given: - A client object. - API returns an error response. When: - Calling the 'vectra_entity_unresolved_priority_reset_command' function. Then: - Assert that the function raises an appropriate exception when the API call fails. """ entity_id = "999" entity_type = "account" mock_response = { "errors": {"unresolved_priority": ["unresolved_priority can only be set to false when is_prioritized is false"]}, "_meta": {"level": "error"}, } url = f"{BASE_URL}{ENDPOINTS['ENTITY_ENDPOINT']}/{entity_id}?type={entity_type}" requests_mock.patch(url, json=mock_response, status_code=400) args = {"entity_id": entity_id, "entity_type": entity_type} with pytest.raises(DemistoException): vectra_entity_unresolved_priority_reset_command(client, args) @pytest.mark.parametrize("detection_status", ["open", "acknowledged", "escalated", "paused", "closed", "expired"]) def test_vectra_detection_status_update_valid_single_detection(requests_mock, client, detection_status): """ Given: - A client object. - Valid arguments for updating detection status with a single detection ID. - Mocked API response for updating detection status. When: - Calling the 'vectra_detection_investigation_status_update_command' function with valid detection_id and detection_status. Then: - Assert that the CommandResults object contains the expected outputs. - Assert the correctness of the 'outputs_prefix' property. - Assert that the human-readable output matches the expected message. - Assert that the 'EntryContext' property contains the correct detection_ids and detection_status. - Assert the correctness of the 'outputs_key_field' property. """ detection_id = "123" mock_response = {"message": {"success": ["Successfully updated detection statuses"]}, "_meta": {"level": "success"}} url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}" requests_mock.patch(url, json=mock_response, status_code=200) args = {"detection_ids": detection_id, "investigation_status": detection_status} result = vectra_detection_investigation_status_update_command(client, args) result_context = result.to_context() assert result.outputs_prefix == "Vectra.Detection" assert result.outputs_key_field == "id" assert result_context.get("HumanReadable") == ( f"##### The investigation status for provided Detection ID(s) ['{detection_id}'] have been updated as {detection_status}." ) context_key = "Vectra.Detection(val.id && val.id == obj.id)" outputs = result_context.get("EntryContext").get(context_key) assert isinstance(outputs, list) assert len(outputs) == 1 assert outputs[0].get("id") == detection_id assert outputs[0].get("investigation_status") == detection_status def test_vectra_detection_status_update_valid_multiple_detections(requests_mock, client): """ Given: - A client object. - Valid arguments for updating detection status with multiple detection IDs. When: - Calling the 'vectra_detection_investigation_status_update_command' function with multiple detection IDs. Then: - Assert that the CommandResults object contains the expected outputs. - Assert that detection IDs are sorted in the output. """ detection_ids = "3,1,2" detection_status = "escalated" mock_response = {"message": {"success": ["Successfully updated detection statuses"]}, "_meta": {"level": "success"}} url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}" requests_mock.patch(url, json=mock_response, status_code=200) args = {"detection_ids": detection_ids, "investigation_status": detection_status} result = vectra_detection_investigation_status_update_command(client, args) result_context = result.to_context() assert result.outputs_prefix == "Vectra.Detection" assert ( result_context.get("HumanReadable") == f"##### The investigation status for provided Detection ID(s) ['3', '1', '2'] have been updated as {detection_status}." ) context_key = "Vectra.Detection(val.id && val.id == obj.id)" outputs = result_context.get("EntryContext").get(context_key) assert isinstance(outputs, list) assert len(outputs) == 3 detection_ids_in_output = [item.get("id") for item in outputs] assert set(detection_ids_in_output) == {"3", "1", "2"} for item in outputs: assert item.get("investigation_status") == detection_status def test_vectra_detection_status_update_with_uppercase_status(requests_mock, client): """ Given: - A client object. - Valid arguments with uppercase detection_status. When: - Calling the 'vectra_detection_investigation_status_update_command' function with uppercase detection_status. Then: - Assert that the function accepts uppercase status values. - Assert that the CommandResults object contains the expected outputs. """ detection_id = "456" detection_status = "Escalated" mock_response = {"message": {"success": ["Successfully updated detection statuses"]}, "_meta": {"level": "success"}} url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}" requests_mock.patch(url, json=mock_response, status_code=200) args = {"detection_ids": detection_id, "investigation_status": detection_status} result = vectra_detection_investigation_status_update_command(client, args) result_context = result.to_context() assert result.outputs_prefix == "Vectra.Detection" context_key = "Vectra.Detection(val.id && val.id == obj.id)" outputs = result_context.get("EntryContext").get(context_key) assert isinstance(outputs, list) assert len(outputs) == 1 assert outputs[0].get("investigation_status") == detection_status def test_vectra_detection_status_update_removes_nulls(requests_mock, client): """ Given: - A client object. - API response containing null values. When: - Calling the 'vectra_detection_investigation_status_update_command' function. Then: - Assert that null values are removed from the response before being added to outputs. """ detection_id = "789" detection_status = "closed" mock_response = { "message": {"success": ["Successfully updated detection statuses"]}, "_meta": {"level": "success"}, "extra_field": None, "another_null": None, } url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}" requests_mock.patch(url, json=mock_response, status_code=200) args = {"detection_ids": detection_id, "investigation_status": detection_status} result = vectra_detection_investigation_status_update_command(client, args) result_context = result.to_context() context_key = "Vectra.Detection(val.id && val.id == obj.id)" outputs = result_context.get("EntryContext").get(context_key) assert isinstance(outputs, list) assert len(outputs) == 1 assert outputs[0].get("id") == detection_id assert outputs[0].get("investigation_status") == detection_status def test_vectra_detection_status_update_with_whitespace_in_ids(requests_mock, client): """ Given: - A client object. - Detection IDs with whitespace. When: - Calling the 'vectra_detection_investigation_status_update_command' function with detection IDs containing whitespace. Then: - Assert that whitespace is properly stripped from detection IDs. """ detection_ids = " 1 , 2 , 3 " detection_status = "acknowledged" mock_response = {"message": {"success": ["Successfully updated detection statuses"]}, "_meta": {"level": "success"}} url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}" requests_mock.patch(url, json=mock_response, status_code=200) args = {"detection_ids": detection_ids, "investigation_status": detection_status} result = vectra_detection_investigation_status_update_command(client, args) result_context = result.to_context() context_key = "Vectra.Detection(val.id && val.id == obj.id)" outputs = result_context.get("EntryContext").get(context_key) assert isinstance(outputs, list) assert len(outputs) == 3 detection_ids_in_output = [item.get("id") for item in outputs] assert set(detection_ids_in_output) == {"1", "2", "3"} def test_vectra_detection_status_update_with_mixed_valid_invalid_ids(mocker, requests_mock, client): """ Given: - A client object. - Mixed valid and invalid detection IDs. When: - Calling the 'vectra_detection_investigation_status_update_command' function with mixed valid/invalid IDs. Then: - Assert that a warning is returned for invalid IDs. - Assert that valid IDs are processed successfully. """ detection_ids = "1,abc,2,0,-3" detection_status = "paused" mock_response = {"message": {"success": ["Successfully updated detection statuses"]}, "_meta": {"level": "success"}} url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}" requests_mock.patch(url, json=mock_response, status_code=200) mock_return_warning = mocker.patch("VectraRUXEventsDetections.return_warning") args = {"detection_ids": detection_ids, "investigation_status": detection_status} result = vectra_detection_investigation_status_update_command(client, args) mock_return_warning.assert_called_once() call_args = mock_return_warning.call_args assert "abc,0,-3" in call_args[1]["message"] assert call_args[1]["exit"] is False result_context = result.to_context() context_key = "Vectra.Detection(val.id && val.id == obj.id)" outputs = result_context.get("EntryContext").get(context_key) assert isinstance(outputs, list) assert len(outputs) == 2 detection_ids_in_output = [item.get("id") for item in outputs] assert set(detection_ids_in_output) == {"1", "2"} @pytest.mark.parametrize( "args,error_msg", [ ({}, ERRORS["REQUIRED_ARGUMENT"].format("detection_ids")), ({"detection_ids": None}, ERRORS["REQUIRED_ARGUMENT"].format("detection_ids")), ({"detection_ids": ""}, ERRORS["REQUIRED_ARGUMENT"].format("detection_ids")), ({"detection_ids": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("investigation_status")), ({"detection_ids": "1", "investigation_status": None}, ERRORS["REQUIRED_ARGUMENT"].format("investigation_status")), ({"detection_ids": "1", "investigation_status": ""}, ERRORS["REQUIRED_ARGUMENT"].format("investigation_status")), ( {"detection_ids": "1", "investigation_status": "invalid_status"}, ERRORS["INVALID_ARG_VALUE"].format( "investigation_status", ", ".join([status.lower() for status in VALID_DETECTION_STATUS]) ), ), ( {"detection_ids": "1", "investigation_status": "pending"}, ERRORS["INVALID_ARG_VALUE"].format( "investigation_status", ", ".join([status.lower() for status in VALID_DETECTION_STATUS]) ), ), ( {"detection_ids": "1", "investigation_status": "resolved"}, ERRORS["INVALID_ARG_VALUE"].format( "investigation_status", ", ".join([status.lower() for status in VALID_DETECTION_STATUS]) ), ), ], ) def test_vectra_detection_status_update_invalid_args(client, args, error_msg): """ Given: - A client object. - Invalid arguments for updating detection status. When: - Calling the 'vectra_detection_investigation_status_update_command' function with invalid arguments. Then: - Assert that a ValueError is raised. - Assert that the error message matches the expected error message for each invalid argument. """ with pytest.raises(ValueError) as exception: vectra_detection_investigation_status_update_command(client, args) assert str(exception.value) == error_msg def test_vectra_detection_status_update_all_invalid_ids(mocker, requests_mock, client): """ Given: - A client object. - All invalid detection IDs. When: - Calling the 'vectra_detection_investigation_status_update_command' function with all invalid IDs. Then: - Assert that return_warning is called with exit=True. """ detection_ids = "abc,xyz,0,-1" detection_status = "escalated" args = {"detection_ids": detection_ids, "investigation_status": detection_status} with pytest.raises(DemistoException) as exception: vectra_detection_investigation_status_update_command(client, args) assert str(exception.value) == ERRORS["INVALID_INTEGER_VALUE"].format("detection_ids", ",".join(["abc", "xyz", "0", "-1"])) def test_vectra_detection_status_update_api_error(requests_mock, client): """ Given: - A client object. - API returns an error response. When: - Calling the 'vectra_detection_investigation_status_update_command' function. Then: - Assert that the function raises an appropriate exception when the API call fails. """ detection_id = "999" detection_status = "escalated" url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}" requests_mock.patch(url, json={"error": "Detection not found"}, status_code=404) args = {"detection_ids": detection_id, "investigation_status": detection_status} with pytest.raises(DemistoException): vectra_detection_investigation_status_update_command(client, args) def test_vectra_detection_external_id_update_valid_single_detection(requests_mock, client): """ Given: - A client object. - Valid arguments for updating external reference ID with a single detection ID. - Mocked API response for updating detection external reference ID. When: - Calling the 'vectra_detection_external_id_update_command' function with valid detection_id and external_reference_id. Then: - Assert that the CommandResults object contains the expected outputs. - Assert the correctness of the 'outputs_prefix' property. - Assert that the human-readable output matches the expected message. - Assert that the 'EntryContext' property contains the correct detection_id and external_reference_id. - Assert the correctness of the 'outputs_key_field' property. """ detection_id = "123" external_reference_id = "EXT-12345" mock_response = {"message": {"success": ["Successfully updated external reference ID"]}, "_meta": {"level": "success"}} url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}" requests_mock.patch(url, json=mock_response, status_code=200) args = {"detection_ids": detection_id, "external_reference_id": external_reference_id} result = vectra_detection_external_id_update_command(client, args) result_context = result.to_context() assert result.outputs_prefix == "Vectra.Detection" assert result.outputs_key_field == "id" assert result_context.get("HumanReadable") == ( f"##### The external reference ID for provided Detection ID(s) ['{detection_id}'] " f"have been updated as {external_reference_id}." ) context_key = "Vectra.Detection(val.id && val.id == obj.id)" outputs = result_context.get("EntryContext").get(context_key) assert isinstance(outputs, list) assert len(outputs) == 1 assert outputs[0].get("id") == detection_id assert outputs[0].get("external_reference_id") == external_reference_id def test_vectra_detection_external_id_update_valid_multiple_detections(requests_mock, client): """ Given: - A client object. - Valid arguments for updating external reference ID with multiple detection IDs. When: - Calling the 'vectra_detection_external_id_update_command' function with multiple detection IDs. Then: - Assert that the CommandResults object contains the expected outputs. - Assert that all detection IDs are present in the output. """ detection_ids = "3,1,2" external_reference_id = "EXT-67890" mock_response = {"message": {"success": ["Successfully updated external reference ID"]}, "_meta": {"level": "success"}} url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}" requests_mock.patch(url, json=mock_response, status_code=200) args = {"detection_ids": detection_ids, "external_reference_id": external_reference_id} result = vectra_detection_external_id_update_command(client, args) result_context = result.to_context() assert result.outputs_prefix == "Vectra.Detection" assert result_context.get("HumanReadable") == ( "##### The external reference ID for provided Detection ID(s) ['3', '1', '2'] " f"have been updated as {external_reference_id}." ) context_key = "Vectra.Detection(val.id && val.id == obj.id)" outputs = result_context.get("EntryContext").get(context_key) assert isinstance(outputs, list) assert len(outputs) == 3 detection_ids_in_output = [item.get("id") for item in outputs] assert set(detection_ids_in_output) == {"3", "1", "2"} for item in outputs: assert item.get("external_reference_id") == external_reference_id def test_vectra_detection_external_id_update_removes_nulls(requests_mock, client): """ Given: - A client object. - API response containing null values. When: - Calling the 'vectra_detection_external_id_update_command' function. Then: - Assert that null values are removed from the response before being added to outputs. """ detection_id = "789" external_reference_id = "EXT-TICKET-001" mock_response = { "message": {"success": ["Successfully updated external reference ID"]}, "_meta": {"level": "success"}, "extra_field": None, "another_null": None, } url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}" requests_mock.patch(url, json=mock_response, status_code=200) args = {"detection_ids": detection_id, "external_reference_id": external_reference_id} result = vectra_detection_external_id_update_command(client, args) result_context = result.to_context() context_key = "Vectra.Detection(val.id && val.id == obj.id)" outputs = result_context.get("EntryContext").get(context_key) assert isinstance(outputs, list) assert len(outputs) == 1 assert outputs[0].get("id") == detection_id assert outputs[0].get("external_reference_id") == external_reference_id def test_vectra_detection_external_id_update_with_whitespace_in_ids(requests_mock, client): """ Given: - A client object. - Detection IDs with whitespace. When: - Calling the 'vectra_detection_external_id_update_command' function with detection IDs containing whitespace. Then: - Assert that whitespace is properly stripped from detection IDs. """ detection_ids = " 1 , 2 , 3 " external_reference_id = "EXT-WS-123" mock_response = {"message": {"success": ["Successfully updated external reference ID"]}, "_meta": {"level": "success"}} url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}" requests_mock.patch(url, json=mock_response, status_code=200) args = {"detection_ids": detection_ids, "external_reference_id": external_reference_id} result = vectra_detection_external_id_update_command(client, args) result_context = result.to_context() context_key = "Vectra.Detection(val.id && val.id == obj.id)" outputs = result_context.get("EntryContext").get(context_key) assert isinstance(outputs, list) assert len(outputs) == 3 detection_ids_in_output = [item.get("id") for item in outputs] assert set(detection_ids_in_output) == {"1", "2", "3"} def test_vectra_detection_external_id_update_with_mixed_valid_invalid_ids(mocker, requests_mock, client): """ Given: - A client object. - A mix of valid and invalid detection IDs. When: - Calling the 'vectra_detection_external_id_update_command' function with mixed valid and invalid IDs. Then: - Assert that return_warning is called for invalid IDs. - Assert that valid IDs are processed successfully. """ detection_ids = "1,abc,2,0,-3" external_reference_id = "EXT-MIXED-456" mock_response = {"message": {"success": ["Successfully updated external reference ID"]}, "_meta": {"level": "success"}} url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}" requests_mock.patch(url, json=mock_response, status_code=200) mock_return_warning = mocker.patch("VectraRUXEventsDetections.return_warning") args = {"detection_ids": detection_ids, "external_reference_id": external_reference_id} result = vectra_detection_external_id_update_command(client, args) mock_return_warning.assert_called_once() call_args = mock_return_warning.call_args assert "abc,0,-3" in call_args[1]["message"] assert call_args[1]["exit"] is False result_context = result.to_context() context_key = "Vectra.Detection(val.id && val.id == obj.id)" outputs = result_context.get("EntryContext").get(context_key) assert isinstance(outputs, list) assert len(outputs) == 2 detection_ids_in_output = [item.get("id") for item in outputs] assert set(detection_ids_in_output) == {"1", "2"} @pytest.mark.parametrize( "args,error_msg", [ ({}, ERRORS["REQUIRED_ARGUMENT"].format("detection_ids")), ({"detection_ids": None}, ERRORS["REQUIRED_ARGUMENT"].format("detection_ids")), ({"detection_ids": ""}, ERRORS["REQUIRED_ARGUMENT"].format("detection_ids")), ({"detection_ids": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("external_reference_id")), ({"detection_ids": "1", "external_reference_id": None}, ERRORS["REQUIRED_ARGUMENT"].format("external_reference_id")), ({"detection_ids": "1", "external_reference_id": ""}, ERRORS["REQUIRED_ARGUMENT"].format("external_reference_id")), ], ) def test_vectra_detection_external_id_update_invalid_args(client, args, error_msg): """ Given: - A client object. - Invalid arguments for updating external reference ID. When: - Calling the 'vectra_detection_external_id_update_command' function with invalid arguments. Then: - Assert that a ValueError is raised. - Assert that the error message matches the expected error message for each invalid argument. """ with pytest.raises(ValueError) as exception: vectra_detection_external_id_update_command(client, args) assert str(exception.value) == error_msg def test_vectra_detection_external_id_update_all_invalid_ids(mocker, requests_mock, client): """ Given: - A client object. - All invalid detection IDs. When: - Calling the 'vectra_detection_external_id_update_command' function with all invalid IDs. Then: - Assert that return_warning is called with exit=True. """ detection_ids = "abc,xyz,0,-1" external_reference_id = "EXT-INVALID-789" args = {"detection_ids": detection_ids, "external_reference_id": external_reference_id} with pytest.raises(DemistoException) as exception: vectra_detection_external_id_update_command(client, args) assert str(exception.value) == ERRORS["INVALID_INTEGER_VALUE"].format("detection_ids", ",".join(["abc", "xyz", "0", "-1"])) def test_vectra_detection_external_id_update_api_error(requests_mock, client): """ Given: - A client object. - API returns an error response. When: - Calling the 'vectra_detection_external_id_update_command' function. Then: - Assert that the function raises an appropriate exception when the API call fails. """ detection_id = "999" external_reference_id = "EXT-ERROR-404" url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}" requests_mock.patch(url, json={"error": "Detection not found"}, status_code=404) args = {"detection_ids": detection_id, "external_reference_id": external_reference_id} with pytest.raises(DemistoException): vectra_detection_external_id_update_command(client, args) def test_vectra_detection_external_id_update_with_special_characters(requests_mock, client): """ Given: - A client object. - External reference ID with special characters. When: - Calling the 'vectra_detection_external_id_update_command' function with special characters in external_reference_id. Then: - Assert that special characters are handled correctly. """ detection_id = "456" external_reference_id = "TICKET-2024-#123-ABC_XYZ" mock_response = {"message": {"success": ["Successfully updated external reference ID"]}, "_meta": {"level": "success"}} url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}" requests_mock.patch(url, json=mock_response, status_code=200) args = {"detection_ids": detection_id, "external_reference_id": external_reference_id} result = vectra_detection_external_id_update_command(client, args) result_context = result.to_context() context_key = "Vectra.Detection(val.id && val.id == obj.id)" outputs = result_context.get("EntryContext").get(context_key) assert isinstance(outputs, list) assert len(outputs) == 1 assert outputs[0].get("id") == detection_id assert outputs[0].get("external_reference_id") == external_reference_id @pytest.mark.parametrize("entity_type", ["account", "host"]) def test_vectra_entity_external_id_update_valid_arguments(requests_mock, client, entity_type): """ Given: - A client object. - Valid arguments for updating entity external reference ID. - Mocked API response for updating entity external reference ID. When: - Calling the 'vectra_entity_external_id_update_command' function with valid entity_id, entity_type, and external_reference_id. Then: - Assert that the CommandResults object contains the expected outputs. - Assert the correctness of the 'outputs_prefix' property. - Assert that the human-readable output matches the expected message. - Assert that the 'EntryContext' property contains the correct entity_id, entity_type, and external_reference_id. - Assert the correctness of the 'outputs_key_field' property. """ entity_id = "123" external_reference_id = "EXT-ENTITY-12345" mock_response = {"message": {"success": ["Successfully updated external reference ID"]}, "_meta": {"level": "success"}} url = f"{BASE_URL}{ENDPOINTS['ENTITY_ENDPOINT']}/{entity_id}" requests_mock.patch(url, json=mock_response, status_code=200) args = {"entity_id": entity_id, "entity_type": entity_type, "external_reference_id": external_reference_id} result = vectra_entity_external_id_update_command(client, args) result_context = result.to_context() assert result.outputs_prefix == "Vectra.Entity" assert result.outputs_key_field == ["id", "type"] assert ( result_context.get("HumanReadable") == f"##### The external reference ID for provided Entity have been updated as {external_reference_id}." ) context_key = "Vectra.Entity(val.id && val.id == obj.id && val.type && val.type == obj.type)" outputs = result_context.get("EntryContext").get(context_key) assert outputs.get("id") == entity_id assert outputs.get("type") == entity_type assert outputs.get("external_reference_id") == external_reference_id def test_vectra_entity_external_id_update_with_uppercase_entity_type(requests_mock, client): """ Given: - A client object. - Valid arguments with uppercase entity_type. When: - Calling the 'vectra_entity_external_id_update_command' function with uppercase entity_type. Then: - Assert that the function accepts uppercase entity type values. - Assert that the CommandResults object contains the expected outputs. """ entity_id = "456" entity_type = "Account" external_reference_id = "EXT-UPPER-789" mock_response = {"message": {"success": ["Successfully updated external reference ID"]}, "_meta": {"level": "success"}} url = f"{BASE_URL}{ENDPOINTS['ENTITY_ENDPOINT']}/{entity_id}" requests_mock.patch(url, json=mock_response, status_code=200) args = {"entity_id": entity_id, "entity_type": entity_type, "external_reference_id": external_reference_id} result = vectra_entity_external_id_update_command(client, args) result_context = result.to_context() assert result.outputs_prefix == "Vectra.Entity" context_key = "Vectra.Entity(val.id && val.id == obj.id && val.type && val.type == obj.type)" outputs = result_context.get("EntryContext").get(context_key) assert outputs.get("id") == entity_id assert outputs.get("type") == entity_type assert outputs.get("external_reference_id") == external_reference_id def test_vectra_entity_external_id_update_removes_nulls(requests_mock, client): """ Given: - A client object. - API response containing null values. When: - Calling the 'vectra_entity_external_id_update_command' function. Then: - Assert that null values are removed from the response before being added to outputs. """ entity_id = "789" entity_type = "host" external_reference_id = "EXT-NULL-TEST" mock_response = { "message": {"success": ["Successfully updated external reference ID"]}, "_meta": {"level": "success"}, "extra_field": None, "another_null": None, } url = f"{BASE_URL}{ENDPOINTS['ENTITY_ENDPOINT']}/{entity_id}" requests_mock.patch(url, json=mock_response, status_code=200) args = {"entity_id": entity_id, "entity_type": entity_type, "external_reference_id": external_reference_id} result = vectra_entity_external_id_update_command(client, args) result_context = result.to_context() context_key = "Vectra.Entity(val.id && val.id == obj.id && val.type && val.type == obj.type)" outputs = result_context.get("EntryContext").get(context_key) assert outputs.get("id") == entity_id assert outputs.get("type") == entity_type assert outputs.get("external_reference_id") == external_reference_id @pytest.mark.parametrize( "args,error_msg", [ ({}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")), ({"entity_id": None}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")), ({"entity_id": ""}, ERRORS["REQUIRED_ARGUMENT"].format("entity_id")), ({"entity_id": "1"}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type")), ({"entity_id": "1", "entity_type": None}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type")), ({"entity_id": "1", "entity_type": ""}, ERRORS["REQUIRED_ARGUMENT"].format("entity_type")), ({"entity_id": "1", "entity_type": "account"}, ERRORS["REQUIRED_ARGUMENT"].format("external_reference_id")), ( {"entity_id": "1", "entity_type": "account", "external_reference_id": None}, ERRORS["REQUIRED_ARGUMENT"].format("external_reference_id"), ), ( {"entity_id": "1", "entity_type": "account", "external_reference_id": ""}, ERRORS["REQUIRED_ARGUMENT"].format("external_reference_id"), ), ( {"entity_id": "0", "entity_type": "account", "external_reference_id": "EXT-123"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "0"), ), ( {"entity_id": "-1", "entity_type": "account", "external_reference_id": "EXT-123"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "-1"), ), ( {"entity_id": "1.5", "entity_type": "account", "external_reference_id": "EXT-123"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "1.5"), ), ( {"entity_id": "abc", "entity_type": "account", "external_reference_id": "EXT-123"}, ERRORS["INVALID_INTEGER_VALUE"].format("entity_id", "abc"), ), ( {"entity_id": "1", "entity_type": "invalid_type", "external_reference_id": "EXT-123"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)), ), ( {"entity_id": "1", "entity_type": "user", "external_reference_id": "EXT-123"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)), ), ( {"entity_id": "1", "entity_type": "detection", "external_reference_id": "EXT-123"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", ", ".join(VALID_ENTITY_TYPE)), ), ], ) def test_vectra_entity_external_id_update_invalid_args(client, args, error_msg): """ Given: - A client object. - Invalid arguments for updating entity external reference ID. When: - Calling the 'vectra_entity_external_id_update_command' function with invalid arguments. Then: - Assert that a ValueError is raised. - Assert that the error message matches the expected error message for each invalid argument. """ with pytest.raises(ValueError) as exception: vectra_entity_external_id_update_command(client, args) assert str(exception.value) == error_msg def test_vectra_entity_external_id_update_api_error(requests_mock, client): """ Given: - A client object. - API returns an error response. When: - Calling the 'vectra_entity_external_id_update_command' function. Then: - Assert that the function raises an appropriate exception when the API call fails. """ entity_id = "999" entity_type = "account" external_reference_id = "EXT-ERROR-404" url = f"{BASE_URL}{ENDPOINTS['ENTITY_ENDPOINT']}/{entity_id}" requests_mock.patch(url, json={"error": "Entity not found"}, status_code=404) args = {"entity_id": entity_id, "entity_type": entity_type, "external_reference_id": external_reference_id} with pytest.raises(DemistoException): vectra_entity_external_id_update_command(client, args) def test_vectra_entity_external_id_update_with_special_characters(requests_mock, client): """ Given: - A client object. - External reference ID with special characters. When: - Calling the 'vectra_entity_external_id_update_command' function with special characters in external_reference_id. Then: - Assert that special characters are handled correctly. """ entity_id = "555" entity_type = "host" external_reference_id = "TICKET-2024-#456-XYZ_ABC" mock_response = {"message": {"success": ["Successfully updated external reference ID"]}, "_meta": {"level": "success"}} url = f"{BASE_URL}{ENDPOINTS['ENTITY_ENDPOINT']}/{entity_id}" requests_mock.patch(url, json=mock_response, status_code=200) args = {"entity_id": entity_id, "entity_type": entity_type, "external_reference_id": external_reference_id} result = vectra_entity_external_id_update_command(client, args) result_context = result.to_context() context_key = "Vectra.Entity(val.id && val.id == obj.id && val.type && val.type == obj.type)" outputs = result_context.get("EntryContext").get(context_key) assert outputs.get("id") == entity_id assert outputs.get("type") == entity_type assert outputs.get("external_reference_id") == external_reference_id def test_vectra_detection_list_command_success(requests_mock, client): """ Given: - A client object. - Valid arguments for listing detections. - Mocked API response with detection results. When: - Calling the 'vectra_detection_list_command' function. Then: - Assert that the CommandResults object contains the expected outputs. - Assert the correctness of the 'outputs_prefix' property. - Assert that the 'outputs' contains detection data. - Assert the correctness of the 'outputs_key_field' property. """ mock_response = { "count": 2, "results": [ { "id": 123, "detection": "Test Detection 1", "state": "active", "certainty": 85, "threat": 75, "url": "https://example.com/api/v3.3/detections/123", }, { "id": 456, "detection": "Test Detection 2", "state": "active", "certainty": 90, "threat": 80, "url": "https://example.com/api/v3.3/detections/456", }, ], } url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}" requests_mock.get(url, json=mock_response, status_code=200) args = {"page": "1", "page_size": "50"} result = vectra_detection_list_command(client, args) assert result.outputs_prefix == "Vectra.Detection" assert result.outputs_key_field == "id" assert isinstance(result.outputs, list) assert len(result.outputs) == 2 assert result.outputs[0]["id"] == 123 assert result.outputs[1]["id"] == 456 def test_vectra_detection_list_command_no_results(requests_mock, client): """ Given: - A client object. - Valid arguments for listing detections. - Mocked API response with no detection results. When: - Calling the 'vectra_detection_list_command' function. Then: - Assert that the function returns a message indicating no detections found. - Assert that outputs is an empty dictionary. """ mock_response = {"count": 0, "results": []} url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}" requests_mock.get(url, json=mock_response, status_code=200) args = {"page": "1", "page_size": "50"} result = vectra_detection_list_command(client, args) result_context = result.to_context() assert result_context.get("HumanReadable") == "##### Couldn't find any detections for provided filters." assert result_context.get("EntryContext") == {} def test_vectra_detection_list_command_with_filters(requests_mock, client): """ Given: - A client object. - Arguments with multiple filters (detection_name, detection_state, tags). When: - Calling the 'vectra_detection_list_command' function with filters. Then: - Assert that the function returns filtered detection results. """ mock_response = { "count": 1, "results": [ { "id": 789, "detection": "Filtered Detection", "state": "active", "tags": ["critical", "malware"], "url": "https://example.com/api/v3.3/detections/789", } ], } url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}" requests_mock.get(url, json=mock_response, status_code=200) args = { "detection_name": "Filtered Detection", "detection_state": "active", "tags": "critical,malware", "page": "1", "page_size": "50", } result = vectra_detection_list_command(client, args) assert result.outputs_prefix == "Vectra.Detection" assert len(result.outputs) == 1 assert result.outputs[0]["id"] == 789 def test_vectra_detection_list_command_with_pagination(requests_mock, client): """ Given: - A client object. - Arguments with custom page and page_size. When: - Calling the 'vectra_detection_list_command' function with pagination parameters. Then: - Assert that the function returns the correct page of results. """ mock_response = { "count": 100, "results": [ {"id": i, "detection": f"Detection {i}", "url": f"https://example.com/api/v3.3/detections/{i}"} for i in range(51, 101) ], } url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}" requests_mock.get(url, json=mock_response, status_code=200) args = {"page": "2", "page_size": "50"} result = vectra_detection_list_command(client, args) assert result.outputs_prefix == "Vectra.Detection" assert len(result.outputs) == 50 def test_vectra_detection_list_command_with_time_filters(requests_mock, client): """ Given: - A client object. - Arguments with time-based filters (created_after, created_before). When: - Calling the 'vectra_detection_list_command' function with time filters. Then: - Assert that the function returns detections within the time range. """ mock_response = { "count": 1, "results": [ { "id": 999, "detection": "Recent Detection", "created_timestamp": "2024-01-15T10:00:00Z", "url": "https://example.com/api/v3.3/detections/999", } ], } url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}" requests_mock.get(url, json=mock_response, status_code=200) args = {"created_after": "2024-01-01", "created_before": "2024-01-31", "page": "1", "page_size": "50"} result = vectra_detection_list_command(client, args) assert result.outputs_prefix == "Vectra.Detection" assert len(result.outputs) == 1 assert result.outputs[0]["id"] == 999 @pytest.mark.parametrize( "args,error_msg", [ ( {"include_info_category_detections": "invalid_bool", "page": "1", "page_size": "50"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("include_info_category_detections", ", ".join(VALID_BOOL_VALUES)), ), ( {"is_triaged": "invalid_bool", "page": "1", "page_size": "50"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("is_triaged", ", ".join(VALID_BOOL_VALUES)), ), ( {"close_reason": "invalid_reason", "page": "1", "page_size": "50"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("close_reason", ", ".join(VALID_CLOSE_REASON)), ), ( {"entity_type": "invalid_type", "page": "1", "page_size": "50"}, ERRORS["INVALID_COMMAND_ARG_VALUE"].format("entity_type", (", ".join(VALID_ENTITY_TYPES)).lower()), ), ({"page": "0", "page_size": "50"}, ERRORS["INVALID_INTEGER_VALUE"].format("page", "0")), ({"page": "-1", "page_size": "50"}, ERRORS["INVALID_INTEGER_VALUE"].format("page", "-1")), ({"page": "1", "page_size": "0"}, ERRORS["INVALID_INTEGER_VALUE"].format("page_size", "0")), ({"page": "1", "page_size": "6000"}, ERRORS["INVALID_PAGE_SIZE"]), ], ) def test_vectra_detection_list_command_invalid_args(client, args, error_msg): """ Given: - A client object. - Invalid arguments for listing detections. When: - Calling the 'vectra_detection_list_command' function with invalid arguments. Then: - Assert that a ValueError is raised. - Assert that the error message matches the expected error message. """ with pytest.raises(ValueError) as exception: vectra_detection_list_command(client, args) assert str(exception.value) == error_msg def test_vectra_detection_list_command_invalid_time_range(client): """ Given: - A client object. - Arguments with invalid time range (created_after > created_before). When: - Calling the 'vectra_detection_list_command' function. Then: - Assert that a ValueError is raised for invalid time range. """ args = {"created_after": "2024-01-31", "created_before": "2024-01-01", "page": "1", "page_size": "50"} with pytest.raises(ValueError) as exception: vectra_detection_list_command(client, args) assert "Invalid time range" in str(exception.value) def test_vectra_detection_list_command_with_description_filter(requests_mock, client): """ Given: - A client object. - Arguments with description filter. When: - Calling the 'vectra_detection_list_command' function with description filter. Then: - Assert that the function returns detections matching the description. """ mock_response = { "count": 1, "results": [ { "id": 111, "detection": "Suspicious Activity", "description": "Malware detected on endpoint", "url": "https://example.com/api/v3.3/detections/111", } ], } url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}" requests_mock.get(url, json=mock_response, status_code=200) args = {"description": "Malware", "page": "1", "page_size": "50"} result = vectra_detection_list_command(client, args) assert result.outputs_prefix == "Vectra.Detection" assert len(result.outputs) == 1 assert result.outputs[0]["id"] == 111 def test_vectra_detection_list_command_with_triaged_filter(requests_mock, client): """ Given: - A client object. - Arguments with is_triaged filter set to true. When: - Calling the 'vectra_detection_list_command' function with is_triaged filter. Then: - Assert that the function returns only triaged detections. """ mock_response = { "count": 1, "results": [ { "id": 222, "detection": "Triaged Detection", "is_triaged": True, "url": "https://example.com/api/v3.3/detections/222", } ], } url = f"{BASE_URL}{ENDPOINTS['DETECTION_ENDPOINT']}" requests_mock.get(url, json=mock_response, status_code=200) args = {"is_triaged": "true", "page": "1", "page_size": "50"} result = vectra_detection_list_command(client, args) assert result.outputs_prefix == "Vectra.Detection" assert len(result.outputs) == 1 assert result.outputs[0]["is_triaged"] is True def test_vectra_investigation_query_send_command_success(mocker, client): """ Given: - A client object. - Valid query and version arguments. When: - Calling vectra_investigation_query_send_command with valid arguments. Then: - Assert that the command returns a CommandResults object with the correct outputs. - Assert that the investigation_query_send method was called with correct parameters. - Assert that the human readable output contains the request_id. """ mock_response = { "request_id": "inv-12345", "query": "SELECT * FROM detections WHERE severity > 5", "version": "v1", "status": "pending", } mocker.patch.object(client, "investigation_query_send", return_value=mock_response) args = { "query": "SELECT * FROM detections WHERE severity > 5", "version": "v1", } result = vectra_investigation_query_send_command(client, args) client.investigation_query_send.assert_called_once_with( query="SELECT * FROM detections WHERE severity > 5", version="v1", ) assert result.outputs_prefix == "Vectra.Investigation" assert result.outputs_key_field == "request_id" assert result.outputs == mock_response assert result.raw_response == mock_response assert result.readable_output is not None assert "inv-12345" in result.readable_output assert "!vectra-investigation-result-get" in result.readable_output def test_vectra_investigation_query_send_command_without_version(mocker, client): """ Given: - A client object. - Valid query argument without version. When: - Calling vectra_investigation_query_send_command without version. Then: - Assert that the command returns a CommandResults object. - Assert that the investigation_query_send method was called with query and None for version. """ mock_response = { "request_id": "inv-67890", "query": "SELECT * FROM hosts", "version": None, "status": "pending", } mocker.patch.object(client, "investigation_query_send", return_value=mock_response) args = { "query": "SELECT * FROM hosts", } result = vectra_investigation_query_send_command(client, args) client.investigation_query_send.assert_called_once_with( query="SELECT * FROM hosts", version=None, ) assert result.outputs_prefix == "Vectra.Investigation" assert result.outputs_key_field == "request_id" assert result.outputs == mock_response assert result.readable_output is not None assert "inv-67890" in result.readable_output def test_vectra_investigation_query_send_command_missing_query(client): """ Given: - A client object. - Arguments without the required 'query' parameter. When: - Calling vectra_investigation_query_send_command without query. Then: - Assert that ValueError is raised with the appropriate error message. """ args = { "version": "v1", } with pytest.raises(ValueError) as exception: vectra_investigation_query_send_command(client, args) assert ERRORS["REQUIRED_ARGUMENT"].format("query") in str(exception.value) def test_vectra_investigation_query_send_command_empty_query(client): """ Given: - A client object. - Arguments with an empty 'query' parameter. When: - Calling vectra_investigation_query_send_command with empty query. Then: - Assert that ValueError is raised with the appropriate error message. """ args = { "query": "", "version": "v1", } with pytest.raises(ValueError) as exception: vectra_investigation_query_send_command(client, args) assert ERRORS["REQUIRED_ARGUMENT"].format("query") in str(exception.value) def test_vectra_investigation_query_send_command_with_complex_query(mocker, client): """ Given: - A client object. - A complex SQL-like query with multiple conditions. When: - Calling vectra_investigation_query_send_command with a complex query. Then: - Assert that the command returns a CommandResults object. - Assert that the query is passed correctly to the client method. """ complex_query = """ SELECT d.id, d.name, d.severity, h.ip_address FROM detections d JOIN hosts h ON d.host_id = h.id WHERE d.severity > 7 AND d.status = 'active' ORDER BY d.severity DESC LIMIT 100 """ mock_response = { "request_id": "inv-complex-001", "query": complex_query, "version": "v2", "status": "pending", } mocker.patch.object(client, "investigation_query_send", return_value=mock_response) args = { "query": complex_query, "version": "v2", } result = vectra_investigation_query_send_command(client, args) client.investigation_query_send.assert_called_once_with( query=complex_query, version="v2", ) assert result.outputs_prefix == "Vectra.Investigation" assert result.outputs == mock_response assert result.readable_output is not None assert "inv-complex-001" in result.readable_output def test_vectra_investigation_query_send_command_response_with_nulls(mocker, client): """ Given: - A client object. - Valid query argument. - API response contains null values. When: - Calling vectra_investigation_query_send_command. Then: - Assert that null values are removed from the response. - Assert that the command returns a CommandResults object without nulls. """ mock_response_with_nulls = { "request_id": "inv-nulls-123", "query": "SELECT * FROM accounts", "version": "v1", "status": "pending", "error": None, "metadata": None, } expected_response = { "request_id": "inv-nulls-123", "query": "SELECT * FROM accounts", "version": "v1", "status": "pending", } mocker.patch.object(client, "investigation_query_send", return_value=mock_response_with_nulls) args = { "query": "SELECT * FROM accounts", "version": "v1", } result = vectra_investigation_query_send_command(client, args) assert result.outputs == expected_response assert isinstance(result.outputs, dict) assert "error" not in result.outputs assert "metadata" not in result.outputs def test_vectra_investigation_query_send_command_api_error(mocker, client): """ Given: - A client object. - Valid query argument. - API call raises an exception. When: - Calling vectra_investigation_query_send_command. Then: - Assert that the exception is propagated. """ mocker.patch.object( client, "investigation_query_send", side_effect=DemistoException("API connection error"), ) args = { "query": "SELECT * FROM detections", "version": "v1", } with pytest.raises(DemistoException) as exception: vectra_investigation_query_send_command(client, args) assert "API connection error" in str(exception.value) def test_vectra_investigation_result_get_command_success(mocker, client): """ Given: - A client object. - Valid request ID with page and page_size arguments. When: - Calling vectra_investigation_result_get_command with valid arguments. Then: - Assert that the command returns a CommandResults object with the correct outputs. - Assert that the investigation_result_get method was called with correct parameters. - Assert that the human readable output contains the request_id and metadata. """ mock_response = { "request_id": "inv-12345", "meta": { "query_status": "completed", "page": 1, "page_size": 50, "num_rows_available": 100, "estimated_file_size_bytes": 2048, "columns": ["id", "name", "severity"], }, "data": [ {"id": 1, "name": "Detection 1", "severity": 8}, {"id": 2, "name": "Detection 2", "severity": 6}, ], } mocker.patch.object(client, "investigation_result_get", return_value=mock_response) args = { "id": "inv-12345", "page": "1", "page_size": "50", } result = vectra_investigation_result_get_command(client, args) client.investigation_result_get.assert_called_once_with( request_id="inv-12345", page=1, page_size=50, ) assert result.outputs_prefix == "Vectra.Investigation" assert result.outputs_key_field == "request_id" assert result.outputs == mock_response assert result.raw_response == mock_response assert result.readable_output is not None assert "inv-12345" in result.readable_output assert "completed" in result.readable_output assert "Detection 1" in result.readable_output def test_vectra_investigation_result_get_command_with_defaults(mocker, client): """ Given: - A client object. - Valid request ID without page and page_size arguments. When: - Calling vectra_investigation_result_get_command with only request ID. Then: - Assert that the command uses default values for page and page_size. - Assert that the investigation_result_get method was called with default parameters. """ mock_response = { "request_id": "inv-67890", "meta": { "query_status": "pending", "page": 1, "page_size": 50, "num_rows_available": 0, }, "data": [], } mocker.patch.object(client, "investigation_result_get", return_value=mock_response) args = { "id": "inv-67890", } result = vectra_investigation_result_get_command(client, args) client.investigation_result_get.assert_called_once_with( request_id="inv-67890", page=1, page_size=50, ) assert result.outputs_prefix == "Vectra.Investigation" assert result.outputs_key_field == "request_id" assert result.outputs == mock_response assert result.readable_output is not None assert "inv-67890" in result.readable_output assert "pending" in result.readable_output def test_vectra_investigation_result_get_command_missing_id(client): """ Given: - A client object. - Arguments without the required 'id' parameter. When: - Calling vectra_investigation_result_get_command without id. Then: - Assert that ValueError is raised with the appropriate error message. """ args = { "page": "1", "page_size": "50", } with pytest.raises(ValueError) as exception: vectra_investigation_result_get_command(client, args) assert ERRORS["REQUIRED_ARGUMENT"].format("id") in str(exception.value) def test_vectra_investigation_result_get_command_empty_id(client): """ Given: - A client object. - Arguments with an empty 'id' parameter. When: - Calling vectra_investigation_result_get_command with empty id. Then: - Assert that ValueError is raised with the appropriate error message. """ args = { "id": "", "page": "1", "page_size": "50", } with pytest.raises(ValueError) as exception: vectra_investigation_result_get_command(client, args) assert ERRORS["REQUIRED_ARGUMENT"].format("id") in str(exception.value) def test_vectra_investigation_result_get_command_invalid_page(client): """ Given: - A client object. - Valid request ID with invalid page number (negative). When: - Calling vectra_investigation_result_get_command with invalid page. Then: - Assert that ValueError is raised for invalid page number. """ args = { "id": "inv-12345", "page": "-1", "page_size": "50", } with pytest.raises(ValueError): vectra_investigation_result_get_command(client, args) def test_vectra_investigation_result_get_command_invalid_page_size(client): """ Given: - A client object. - Valid request ID with invalid page_size (zero). When: - Calling vectra_investigation_result_get_command with invalid page_size. Then: - Assert that ValueError is raised for invalid page_size. """ args = { "id": "inv-12345", "page": "1", "page_size": "0", } with pytest.raises(ValueError): vectra_investigation_result_get_command(client, args) def test_vectra_investigation_result_get_command_with_nulls(mocker, client): """ Given: - A client object. - Valid request ID. - API response contains null values. When: - Calling vectra_investigation_result_get_command. Then: - Assert that null values are removed from the response. - Assert that the command returns a CommandResults object without nulls. """ mock_response_with_nulls = { "request_id": "inv-nulls-456", "meta": { "query_status": "completed", "page": 1, "page_size": 50, "num_rows_available": 10, "estimated_file_size_bytes": None, "columns": ["id", "name"], }, "data": [ {"id": 1, "name": "Test", "extra": None}, ], "error": None, } mocker.patch.object(client, "investigation_result_get", return_value=mock_response_with_nulls) args = { "id": "inv-nulls-456", } result = vectra_investigation_result_get_command(client, args) # remove_nulls_from_dictionary removes top-level None values but not nested ones assert isinstance(result.outputs, dict) assert result.outputs["request_id"] == "inv-nulls-456" assert "error" not in result.outputs # Top-level None removed assert result.outputs["meta"]["query_status"] == "completed" # Nested None values may still be present depending on remove_nulls_from_dictionary implementation def test_vectra_investigation_result_get_command_empty_data(mocker, client): """ Given: - A client object. - Valid request ID. - API response with empty data array. When: - Calling vectra_investigation_result_get_command. Then: - Assert that the command handles empty data gracefully. - Assert that the readable output is generated correctly. """ mock_response = { "request_id": "inv-empty-789", "meta": { "query_status": "completed", "page": 1, "page_size": 50, "num_rows_available": 0, "columns": [], }, "data": [], } mocker.patch.object(client, "investigation_result_get", return_value=mock_response) args = { "id": "inv-empty-789", } result = vectra_investigation_result_get_command(client, args) # remove_nulls_from_dictionary removes empty lists, so 'data' and 'columns' keys may be removed assert isinstance(result.outputs, dict) assert result.outputs["request_id"] == "inv-empty-789" assert result.outputs["meta"]["query_status"] == "completed" assert result.readable_output is not None assert "inv-empty-789" in result.readable_output assert "completed" in result.readable_output def test_vectra_investigation_result_get_command_large_page_size(mocker, client): """ Given: - A client object. - Valid request ID with large page_size. When: - Calling vectra_investigation_result_get_command with large page_size. Then: - Assert that the command accepts the large page_size value. - Assert that the investigation_result_get method was called with the specified page_size. """ mock_response = { "request_id": "inv-large-001", "meta": { "query_status": "completed", "page": 1, "page_size": 1000, "num_rows_available": 500, }, "data": [], } mocker.patch.object(client, "investigation_result_get", return_value=mock_response) args = { "id": "inv-large-001", "page": "1", "page_size": "1000", } result = vectra_investigation_result_get_command(client, args) client.investigation_result_get.assert_called_once_with( request_id="inv-large-001", page=1, page_size=1000, ) assert result.outputs == mock_response def test_vectra_investigation_result_get_command_pagination(mocker, client): """ Given: - A client object. - Valid request ID with specific page number. When: - Calling vectra_investigation_result_get_command with page 3. Then: - Assert that the command correctly passes the page parameter. - Assert that the investigation_result_get method was called with page 3. """ mock_response = { "request_id": "inv-page-003", "meta": { "query_status": "completed", "page": 3, "page_size": 50, "num_rows_available": 200, }, "data": [ {"id": 101, "name": "Detection 101"}, {"id": 102, "name": "Detection 102"}, ], } mocker.patch.object(client, "investigation_result_get", return_value=mock_response) args = { "id": "inv-page-003", "page": "3", "page_size": "50", } result = vectra_investigation_result_get_command(client, args) client.investigation_result_get.assert_called_once_with( request_id="inv-page-003", page=3, page_size=50, ) assert isinstance(result.outputs, dict) assert result.outputs["meta"]["page"] == 3 # type: ignore assert result.readable_output is not None assert "Detection 101" in result.readable_output def test_vectra_investigation_result_get_command_api_error(mocker, client): """ Given: - A client object. - Valid request ID. - API call raises an exception. When: - Calling vectra_investigation_result_get_command. Then: - Assert that the exception is propagated. """ mocker.patch.object( client, "investigation_result_get", side_effect=DemistoException("API connection error"), ) args = { "id": "inv-error-999", "page": "1", "page_size": "50", } with pytest.raises(DemistoException) as exception: vectra_investigation_result_get_command(client, args) assert "API connection error" in str(exception.value) def test_vectra_investigation_result_get_command_query_status_failed(mocker, client): """ Given: - A client object. - Valid request ID. - API response with query_status as 'failed'. When: - Calling vectra_investigation_result_get_command. Then: - Assert that the command returns the response with failed status. - Assert that the readable output contains the failed status. """ mock_response = { "request_id": "inv-failed-111", "meta": { "query_status": "failed", "page": 1, "page_size": 50, "num_rows_available": 0, "error_message": "Query execution failed", }, "data": [], } mocker.patch.object(client, "investigation_result_get", return_value=mock_response) args = { "id": "inv-failed-111", } result = vectra_investigation_result_get_command(client, args) assert isinstance(result.outputs, dict) assert result.outputs == mock_response assert result.outputs["meta"]["query_status"] == "failed" # type: ignore assert result.readable_output is not None assert "failed" in result.readable_output assert "inv-failed-111" in result.readable_output def test_vectra_investigation_result_get_command_complex_data(mocker, client): """ Given: - A client object. - Valid request ID. - API response with complex nested data structures. When: - Calling vectra_investigation_result_get_command. Then: - Assert that the command handles complex data structures correctly. - Assert that the readable output is generated properly. """ mock_response = { "request_id": "inv-complex-222", "meta": { "query_status": "completed", "page": 1, "page_size": 50, "num_rows_available": 2, "columns": ["id", "name", "metadata", "tags"], }, "data": [ { "id": 1, "name": "Complex Detection", "metadata": {"severity": 9, "category": "exfiltration"}, "tags": ["critical", "reviewed"], }, { "id": 2, "name": "Another Detection", "metadata": {"severity": 5, "category": "reconnaissance"}, "tags": ["medium"], }, ], } mocker.patch.object(client, "investigation_result_get", return_value=mock_response) args = { "id": "inv-complex-222", } result = vectra_investigation_result_get_command(client, args) assert isinstance(result.outputs, dict) assert result.outputs == mock_response assert len(result.outputs["data"]) == 2 # type: ignore assert result.outputs["data"][0]["metadata"]["severity"] == 9 # type: ignore assert result.readable_output is not None assert "Complex Detection" in result.readable_output class TestUserRoleMapping: """Tests for the USER_ROLE_MAPPING constant added in v3.5 migration.""" def test_user_role_mapping_constant_exists(self): """ Given: - The USER_ROLE_MAPPING constant. When: - Checking if the constant is defined and contains expected mappings. Then: - Assert that the mapping contains all expected role conversions. """ assert USER_ROLE_MAPPING is not None assert isinstance(USER_ROLE_MAPPING, dict) expected_mappings = { "Admin": "admins", "Auditor": "auditor", "Global Analyst": "global_analyst", "Read-Only": "read_only", "Restricted Admin": "restricted_admins", "Security Analyst": "security_analyst", "Setting Admin": "setting_admins", "Super Admin": "super_admins", } assert expected_mappings == USER_ROLE_MAPPING def test_user_role_mapping_admin_role(self): """Test Admin role mapping.""" assert USER_ROLE_MAPPING.get("Admin") == "admins" def test_user_role_mapping_super_admin_role(self): """Test Super Admin role mapping.""" assert USER_ROLE_MAPPING.get("Super Admin") == "super_admins" def test_user_role_mapping_security_analyst_role(self): """Test Security Analyst role mapping.""" assert USER_ROLE_MAPPING.get("Security Analyst") == "security_analyst" class TestVectraUserListCommandRoleMapping: """Tests for the role mapping functionality in vectra_user_list_command.""" def test_vectra_user_list_with_role_mapping(self, mocker, client): """ Given: - A mocked client for requests. - Arguments with a user role that needs to be mapped. When: - Calling the 'vectra_user_list_command' function with a role like 'Super Admin'. Then: - Assert that the role is mapped to 'super_admins' and sent to the API. - Assert that the command returns expected user data. """ user_res = { "count": 1, "next": None, "previous": None, "results": [ { "id": 32, "name": "user.name2", "email": "user@example.com", "role": "Super Admin", "last_login_timestamp": "2023-07-02T18:41:19Z", } ], } # Mock the list_users_request method mocker.patch.object(client, "list_users_request", return_value=user_res) # Call the function with a role that needs mapping result = vectra_user_list_command(client, {"role": "Super Admin"}) # Assert the CommandResults assert result.outputs_prefix == "Vectra.User" assert len(result.outputs) == 1 assert result.outputs_key_field == ["user_id"] def test_vectra_user_list_with_unmapped_role(self, mocker, client): """ Given: - A mocked client for requests. - Arguments with a user role that doesn't need mapping. When: - Calling the 'vectra_user_list_command' function with an unmapped role. Then: - Assert that the role is passed as-is to the API. """ user_res = { "count": 1, "next": None, "previous": None, "results": [ { "id": 59, "name": "user.name1", "email": "user@example.com", "role": "custom_role", "last_login_timestamp": "2023-08-22T09:24:44Z", } ], } mocker.patch.object(client, "list_users_request", return_value=user_res) # Call with an unmapped role (should be passed as-is) result = vectra_user_list_command(client, {"role": "custom_role"}) assert result.outputs_prefix == "Vectra.User" assert len(result.outputs) > 0 def test_vectra_user_list_with_auditor_role_mapping(self, mocker, client): """ Given: - A mocked client for requests. - Arguments with the 'Auditor' role. When: - Calling the 'vectra_user_list_command' function. Then: - Assert that 'Auditor' is mapped to 'auditor'. """ user_res = { "count": 1, "next": None, "previous": None, "results": [ { "id": 10, "name": "auditor_user", "email": "auditor@example.com", "role": "Auditor", "last_login_timestamp": "2023-08-22T09:24:44Z", } ], } mocker.patch.object(client, "list_users_request", return_value=user_res) result = vectra_user_list_command(client, {"role": "Auditor"}) assert result.outputs_prefix == "Vectra.User" assert result.outputs_key_field == ["user_id"] @pytest.mark.parametrize( "role_input", [ "Admin", "Security Analyst", "Super Admin", "Auditor", "Global Analyst", "Read-Only", "Restricted Admin", "Setting Admin", ], ) def test_user_list_command_all_role_mappings(self, mocker, client, role_input): """ Test all possible role mappings for vectra_user_list_command. """ user_res = { "count": 1, "next": None, "previous": None, "results": [ { "id": 1, "name": "test_user", "email": "test@example.com", "role": role_input, "last_login_timestamp": "2023-08-22T09:24:44Z", } ], } mocker.patch.object(client, "list_users_request", return_value=user_res) result = vectra_user_list_command(client, {"role": role_input}) assert result.outputs_prefix == "Vectra.User" class TestUserListCommandWithNameField: """Tests for the updated 'name' field in user list command output.""" def test_user_list_uses_name_field(self, mocker, client): """ Given: - A mocked user response with 'name' field. When: - Calling the 'vectra_user_list_command' function. Then: - Assert that the human-readable output uses 'name' field instead of 'username'. """ # Create a user response with 'name' field (new v3.5 format) user_res = { "count": 1, "next": None, "previous": None, "results": [ { "id": 59, "name": "user.name1", # Using 'name' instead of 'username' "email": "user@example.com", "role": "Security Analyst", "last_login_timestamp": "2023-08-22T09:24:44Z", } ], } mocker.patch.object(client, "list_users_request", return_value=user_res) result = vectra_user_list_command(client, {}) # Check that the readable output contains the name assert result.readable_output is not None assert "user.name1" in result.readable_output assert result.outputs_prefix == "Vectra.User" class TestDetectionInvestigationStatusUpdateValidation: """Tests for the enhanced validation in vectra_detection_investigation_status_update_command.""" def test_detection_investigation_status_no_valid_ids_raises_exception(self, client): """ Given: - Arguments with detection IDs that are all invalid (non-numeric, negative, zero). When: - Calling the 'vectra_detection_investigation_status_update_command' function. Then: - Assert that a DemistoException is raised with appropriate error message. """ args = { "detection_ids": "-1,-5,0", "investigation_status": "Open", } with pytest.raises(DemistoException) as exc_info: vectra_detection_investigation_status_update_command(client, args) assert "INVALID_INTEGER_VALUE" in str(exc_info.value) or "invalid" in str(exc_info.value).lower() def test_detection_investigation_status_empty_detection_ids_raises_error(self, client): """ Given: - Arguments with empty detection_ids. When: - Calling the 'vectra_detection_investigation_status_update_command' function. Then: - Assert that a ValueError is raised. """ args = { "detection_ids": "", "investigation_status": "Open", } with pytest.raises(ValueError): vectra_detection_investigation_status_update_command(client, args) def test_detection_investigation_status_valid_ids_with_some_invalid(self, mocker, client): """ Given: - Arguments with both valid and invalid detection IDs. When: - Calling the 'vectra_detection_investigation_status_update_command' function. Then: - Assert that the command processes valid IDs and returns a warning for invalid ones. """ response = { "status": "success", "data": {"updated_count": 1}, } mocker.patch.object(client, "update_detection_status_request", return_value=response) args = { "detection_ids": "1,invalid,-5", # Mix of valid and invalid "investigation_status": "Open", } result = vectra_detection_investigation_status_update_command(client, args) # The command should process valid ID (1) and issue warnings for invalid ones assert result.outputs_prefix == "Vectra.Detection" def test_detection_investigation_status_only_valid_ids(self, mocker, client): """ Given: - Arguments with only valid detection IDs. When: - Calling the 'vectra_detection_investigation_status_update_command' function. Then: - Assert that the command successfully updates the detection status. """ response = { "status": "success", "data": {"updated_count": 3}, } mocker.patch.object(client, "update_detection_status_request", return_value=response) args = { "detection_ids": "1,2,3", "investigation_status": "Acknowledged", } result = vectra_detection_investigation_status_update_command(client, args) assert result.outputs_prefix == "Vectra.Detection" assert len(result.outputs) == 3 # type: ignore class TestJsonDataParameterChanges: """Tests to verify the 'json_data' parameter changes in HTTP requests work correctly.""" def test_detection_note_add_uses_json_data(self, mocker, client): """ Given: - Arguments for adding a detection note. When: - Calling the 'vectra_detection_note_add_command' function. Then: - Assert that the function executes successfully, implying json_data parameter works. """ notes_res = util_load_json(f"{TEST_DATA_DIR}/detection_note_add_response.json") mocker.patch.object(client, "add_detection_note_request", return_value=notes_res) args = { "detection_id": "1", "note": "test_note", } result = vectra_detection_note_add_command(client, args) assert result.outputs_prefix == "Vectra.Detection.Notes" # Verify the mocked method was called client.add_detection_note_request.assert_called() def test_detection_note_update_uses_json_data(self, mocker, client): """ Given: - Arguments for updating a detection note. When: - Calling the 'vectra_detection_note_update_command' function. Then: - Assert that the function executes successfully with json_data parameter. """ notes_res = util_load_json(f"{TEST_DATA_DIR}/detection_note_update_response.json") mocker.patch.object(client, "update_detection_note_request", return_value=notes_res) args = { "detection_id": "1", "note_id": "1", "note": "updated_note", } result = vectra_detection_note_update_command(client, args) assert result.outputs_prefix == "Vectra.Detection.Notes" client.update_detection_note_request.assert_called() def test_http_request_accepts_json_data_parameter(self, client): """ Given: - A VectraEventsDetectionsClient instance. When: - Verifying that the http_request method accepts json_data parameter. Then: - Assert that the method signature includes json_data parameter. """ # Verify that http_request method has json_data parameter import inspect sig = inspect.signature(client.http_request) assert "json_data" in sig.parameters or "json_data" in str(sig) def test_entity_note_add_uses_json_data(self, mocker, client): """ Given: - Arguments for adding an entity note. When: - Calling the 'vectra_entity_note_add_command' function. Then: - Assert that the function executes with json_data parameter. """ notes_res = { "id": 1, "note": "test_note", "tags": [], "created_by": {"id": 1, "username": "test_user"}, } mocker.patch.object(client, "add_entity_note_request", return_value=notes_res) args = { "entity_type": "account", "entity_id": "1", "note": "test_note", } result = vectra_entity_note_add_command(client, args) assert result.outputs_prefix == "Vectra.Entity.Notes" client.add_entity_note_request.assert_called()