jira-v2 Deprecated
Deprecated. Use the Atlassian Jira v3 integration instead
Case Management · Atlassian Jira
Details
| ID | jira-v2 |
|---|---|
| Provider | Atlassian |
| Category | Case Management |
| From Version | 5.0.0 |
| Docker Image | demisto/oauthlib:1.0.0.72372 |
| Supported Modules | Agentix Cortex Cloud Cloud Runtime Security Cloud Posture Security XSIAM EDR |
README
Use the Atlassian Jira v2 integration to manage Jira issues and create Cortex XSOAR incidents from Jira projects.
This integration was integrated and tested with: Jira Cloud, Jira v8.19.1.
For more information about JQL syntax, go to https://www.atlassian.com/software/jira/guides/expand-jira/jql.
Use Cases
- Create, edit, delete, and query Jira issues.
- Get or add to the comments of an issue.
- Add a link or upload an attachment to an issue.
Configure jira-v2 on Cortex XSOAR
- Navigate to Settings > Integrations > Servers & Services.
- Search for jira-v2.
- Authentiction: As of June 2019, basic authentication using passwords for Jira Cloud is no longer supported. Use an API token or OAuth 1.0 instead. As of this writing, Jira Data Center (unlike Jira Cloud) still supports basic authentication.
- Click Add instance to create and configure a new integration instance.
-
______ Basic Authentication ______
To use basic authentication, follow this tutorial to get the API token. Authorizing the use of basic authentication requires:
- Username
- Password (Deprecated)
- API token
-
________ OAuth 1.0 ______
To use OAuth1.0 follow this tutorial to get the Access token. Authorizing the use of OAuth1.0 requires:
- ConsumerKey
- AccessToken
- PrivateKey
Parameter Description Required Jira URL, for example: https://demisto.atlassian.net/ True Username (API or Basic Authentication) False Password (Deprecated - Use API token) False API token False Consumer key (OAuth 1.0) Will be ignored if other required fields are not provided (for example both the Access Token and the Private Key). False Access token Used for both OAuth 1.0 and Personal Access Token authentication methods. False Private key (PKCS8 format) False Query (in JQL) for fetching incidents False Issue index to start fetching incidents from False Trust any certificate (not secure) False Use system proxy settings False Fetch incidents False Mirror incoming incidents False Incident type False Use created field to fetch incidents False Mirror outgoing incidents False File entry tag Choose the tag to add to an entry to mirror it as an attachment in Jira. False Comment entry tag Choose the tag to add to an entry to mirror it as a comment in Jira. False Fetch comments Fetch comments for Jira ticket. False Fetch attachments Fetch attachments for Jira ticket. False -
- Click Test to validate the URLs, token, and connection.
Fetched Incidents Data
When you enable incidents fetching, Cortex XSOAR fetches the first batch of Jira issues from the 10 minutes prior to when the integration was added. After the first batch of fetched issues, Cortex XSOAR fetches new Jira issues as soon as they are generated in Jira. By default, 50 issues are fetched for each call. To fetch older Jira issues, use the query to fetch issues option.
If Fetch comments is enabled, The fetched incident will include the comments in the Jira issue.
If Fetch attachments is enabled, The fetched incident will include the attachments in the Jira issue.
Commands
You can execute these commands from the Cortex XSOAR CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
jira-issue-query
Queries Jira issues.
Base Command
jira-issue-query
Input
| Argument Name | Description | Required |
|---|---|---|
| query | The JQL query string. | Required |
| startAt | The index (integer) of the first issue to return (0-based). | Optional |
| maxResults | The maximum number of users to fetch when searching for a matching user (default is 50). The maximum allowed value is dictated by the Jira property ‘jira.search.views.default.max’. If you specify a value that is higher than this number, your search results will be truncated. | Optional |
| headers | Display the headers in human readable format. | Optional |
| extraFields | Specify the result fields. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Ticket.Id | Unknown | The ID of the ticket. |
| Ticket.Key | Unknown | The key of the ticket. |
| Ticket.Assignee | Unknown | The user assigned to the ticket. |
| Ticket.Creator | Unknown | The user who created the ticket. |
| Ticket.Summary | Unknown | The summary of the ticket. |
| Ticket.Status | Unknown | The status of the ticket. |
| Ticket.Priority | String | The priority of the ticket. |
| Ticket.Description | String | The description of the ticket. |
| Ticket.Labels | String | The labels of the ticket. |
| Ticket.ProjectName | String | The ticket project name. |
| Ticket.DueDate | Date | The due date. |
| Ticket.Created | Date | The time the ticket was created. |
| Ticket.LastSeen | Date | The last time the ticket was viewed. |
| Ticket.LastUpdate | Date | The last time the ticket was updated. |
Command Example
!jira-issue-query query="status=done"
Context Example
{
"Ticket": [
{
"Status": "Done",
"Creator": "{creator}",
"Summary": "HelloBlocked11",
"Assignee": "null(null)",
"Key": "TES-25",
"Id": "12658"
},
{
"Status": "Done",
"Creator": "{creator}",
"Summary": "Test2",
"Assignee": "null(null)",
"Key": "SOC-40",
"Id": "10986"
}
]
}
Human Readable Output
jira-issue-query
| assignee | created | creator | description | duedate | id | issueType | key | labels | priority | project | reporter | status | summary | ticket_link |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| null(null) | 2019-05-04T02:45:09.909+0300 | {creator} | TypeofIssueIdList | 12658 | A task that needs to be done. | TES-25 | Medium | test1 | {creator} | Done | HelloBlocked11 | https://demistodev.atlassian.net/rest/api/latest/issue/12658 | ||
| null(null) | 2019-01-27T15:59:03.134+0200 | {creator} | 10986 | jira.translation.issuetype.bug.name.desc | SOC-40 | Medium | SOC | {creator} | Done | Test2 | https://demistodev.atlassian.net/rest/api/latest/issue/10986 |
jira-get-issue
Fetches an issue from Jira.
Base Command
jira-get-issue
Input
| Argument Name | Description | Required |
|---|---|---|
| issueId | The ID of the issue. | Required |
| headers | Display the headers in human readable format. | Optional |
| getAttachments | If “true”, retrieves the issue’s attachments. Possible values are: true, false. Default is false. | Optional |
| expandLinks | If “true”, expands the issue’s links. Possible values are: true, false. Default is false. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Ticket.Id | Unknown | The ID of the ticket. |
| Ticket.Key | Unknown | The key of ticket. |
| Ticket.Assignee | Unknown | The user assigned to the ticket. |
| Ticket.Creator | Unknown | The user who created the ticket. |
| Ticket.Summary | Unknown | The summary of the ticket. |
| Ticket.Status | Unknown | The status of the ticket. |
| File.Size | Unknown | The size of the file (Jira attachments are saved as files in Cortex XSOAR). |
| File.SHA256 | Unknown | The SHA256 hash of the file (Jira attachments are saved as files in Cortex XSOAR). |
| File.Name | Unknown | The name of the file (Jira attachments are saved as files in Cortex XSOAR). |
| File.SHA1 | Unknown | The SHA1 hash of the file (Jira attachments are saved as files in Cortex XSOAR). |
| Ticket.Priority | String | The priority of the ticket. |
| Ticket.ProjectName | String | The ticket project name. |
| Ticket.DueDate | Date | The due date. |
| Ticket.Created | Date | The time the ticket was created. |
| Ticket.LastSeen | Date | The last time the ticket was viewed. |
| Ticket.LastUpdate | Date | The last time the ticket was updated. |
Command Example
!jira-get-issue issueId=15572 getAttachments=true
Context Example
{
"Ticket": [
{
"Status": "To Do",
"Creator": "{creator}",
"Summary": "Test issue23",
"Assignee": "{assignee}",
"attachment": "",
"Key": "DEM-5415",
"Id": "15572"
}
]
}
Human Readable Output
jira-get-issue
| assignee | attachment | created | creator | description | duedate | id | issueType | key | labels | priority | project | reporter | status | summary | ticket_link |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| null(null) | 2020-01-19T12:34:13.784+0200 | {creator} | lala | 15572 | Request for Action | DEM-5415 | Medium | demistodev | {assignee} | To Do | Test issue23 | https://demistodev.atlassian.net/rest/api/latest/issue/15572 |
jira-create-issue
Creates a new issue in Jira.
Base Command
jira-create-issue
Input
| Argument Name | Description | Required |
|---|---|---|
| issueJson | The issue object (in JSON format). | Optional |
| summary | The summary of the issue. | Required |
| projectKey | The project key with which to associate the issue. | Optional |
| issueTypeName | Select an issue type by name, for example: “Problem”. | Optional |
| issueTypeId | Select an issue type by its numeric ID. | Optional |
| projectName | The project name with which to associate the issue. | Optional |
| description | A description of the issue. | Optional |
| labels | A CSV list of labels. | Optional |
| priority | The priority of the issue, for example: High, Medium. | Optional |
| dueDate | The due date for the issue (in the format: 2018-03-11). | Optional |
| assignee | The name of the assignee. Relevant for Jira Server only. If you are using Jira Cloud, please provide the assignee_id argument instead. | Optional |
| assignee_id | The account ID of the assignee. Use the jira-get-id-by-attribute command to get the user’s account ID. | Optional |
| reporter | The name of the reporter. | Optional |
| reporter_id | The account ID of the reporter. Use the jira-get-id-by-attribute command to get the user’s account ID. | Optional |
| parentIssueKey | The parent issue key (if you’re creating a sub-task). | Optional |
| parentIssueId | The parent issue ID (if you’re creating a sub-task). | Optional |
| environment | A text field for describing the environment in which the issue occurred (for example - environment=”IE9 on Windows 7”). | Optional |
| security | The security level name of the issue, (for example - security=”Anyone”). | Optional |
| components | The components names of the issue, (for example - components=”component1,component2”). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Ticket.Id | Unknown | The ID of the ticket. |
| Ticket.Key | Unknown | The key of the ticket. |
Command Example
!jira-create-issue summary="test SOC issue26" issueTypeId=10008 projectKey=DEM issueJson=`{"fields":{"issuetype":{"name":"Request for Action"}}}`
Context Example
{
"Ticket": [
{
"Id": "15576",
"Key": "DEM-5419"
}
]
}
Human Readable Output
jira-create-issue
| id | key | projectKey | self |
|---|---|---|---|
| 15576 | DEM-5419 | DEM | https://demistodev.atlassian.net/rest/api/latest/issue/15576 |
jira-issue-upload-file
Uploads a file attachment to an issue.
Base Command
jira-issue-upload-file
Input
| Argument Name | Description | Required |
|---|---|---|
| issueId | The ID of the issue. | Required |
| upload | The entry ID to upload. | Optional |
| attachmentName | The attachment name to be displayed in Jira (overrides original file name). | Optional |
Context Output
There is no context output for this command.
Command Example
!jira-issue-upload-file issueId=15572 upload=19@75
Human Readable Output
jira-issue-upload-file
| attachment_link | attachment_name | id | issueId |
|---|---|---|---|
| https://demistodev.atlassian.net/rest/api/2/attachment/13456 | jira_v2_yml.yml | 13456 | 15572 |
jira-issue-add-comment
Adds a new comment to an existing Jira issue.
Base Command
jira-issue-add-comment
Input
| Argument Name | Description | Required |
|---|---|---|
| issueId | The ID of the issue. | Required |
| comment | The comment body. | Required |
| visibility | The roles that can view the comment, for example: Administrators. | Optional |
Context Output
There is no context output for this command.
Command Example
!jira-issue-add-comment issueId=15572 comment="test comment"
Human Readable Output
jira-issue-add-comment
| comment | id | key | ticket_link |
|---|---|---|---|
| test comment | 13779 | admin | https://demistodev.atlassian.net/rest/api/2/issue/15572/comment/13779 |
jira-issue-add-link
Creates (or updates) an issue link.
Base Command
jira-issue-add-link
Input
| Argument Name | Description | Required |
|---|---|---|
| globalId | If a globalId is provided, and a remote issue link exists with that globalId, the remote issue link is updated. | Optional |
| relationship | The object relationship to issue, for example: causes. | Optional |
| url | The URL link. | Required |
| title | The title of the link. | Required |
| summary | The summary of the link. | Optional |
| issueId | The ID of the issue. | Required |
| applicationType | The application type of the linked remote application. E.g., “com.atlassian.confluence”. | Optional |
| applicationName | The application name of the linked remote application. E.g., “My Confluence Instance”. | Optional |
Context Output
There is no context output for this command.
Command Example
!jira-issue-add-link issueId=15572 title=test url=https://www.demisto.com/
Human Readable Output
jira-issue-add-link
| id | ticket_link |
|---|---|
| 13722 | https://demistodev.atlassian.net/rest/api/latest/issue/DEM-5415/remotelink/13722 |
jira-edit-issue
Modifies an issue in Jira.
Base Command
jira-edit-issue
Input
| Argument Name | Description | Required |
|---|---|---|
| issueId | The ID of the issue to edit. | Required |
| issueJson | The issue object (in JSON format). E.g., {“fields”:{“customfield_10037”: “field_value”}}. | Optional |
| summary | The summary of the issue. | Optional |
| description | The description of the issue. | Optional |
| labels | A CSV list of labels. | Optional |
| priority | The issue priority, for example: High, Medium. | Optional |
| dueDate | The due date for the issue (in the format 2018-03-11). | Optional |
| assignee | The name of the assignee. Relevant for Jira Server only. If you are using Jira Cloud, please provide the assignee_id argument instead. | Optional |
| assignee_id | The account ID of the assignee. Use the jira-get-id-by-attribute command to get the user’s account ID. | Optional |
| status | The issue status. | Optional |
| transition | The issue transition. | Optional |
| environment | A text field for describing the environment in which the issue occurred (for example - environment=”IE9 on Windows 7”). | Optional |
| security | The security level name of the issue, (for example - security=”Anyone”). | Optional |
| components | The components names of the issue, (for example - components=”component1,component2”). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Ticket.Id | Unknown | The ID of the ticket. |
| Ticket.Key | Unknown | The key of the ticket. |
| Ticket.Assignee | Unknown | The user assigned to the ticket. |
| Ticket.Creator | Unknown | The user who created the ticket. |
| Ticket.Summary | Unknown | The summary of the ticket. |
| Ticket.Status | Unknown | The status of the ticket. |
Command Example
!jira-edit-issue issueId=DEM-5415 issueJson=`{"fields":{"description":"testing3"}}`
Context Example
{
"Ticket": {
"Assignee": "{assignee}",
"Creator": "{creator}",
"Id": "10044",
"Key": "DEM-5415",
"Status": "To Do",
"Summary": "Phishing Incident Declared",
"attachment": ""
}
}
Human Readable Output
jira-edit-issue
| assignee | attachment | created | creator | description | duedate | id | issueType | key | labels | priority | project | reporter | status | summary | ticket_link |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| {assignee} | 2021-06-02T10:45:15.838-0400 | {creator} | testing3 | 10044 | A small, distinct piece of work. | DEM-5415 | Medium | SomethingGreat | {reporter} | To Do | Phishing Incident Declared | https://somejira.atlassian.net/rest/api/latest/issue/10044 |
Issue #DEM-5415 was updated successfully
jira-get-comments
Returns the comments added to a ticket.
Base Command
jira-get-comments
Input
| Argument Name | Description | Required |
|---|---|---|
| issueId | The ID of the issue from which to get the comments. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Ticket.Comment.Comment | string | The text of the comment. |
| Ticket.Comment.Created | string | The issue creation date. |
| Ticket.Comment.User | string | The user that created the comment. |
Command Example
!jira-get-comments issueId=15572
Context Example
{
"Ticket": {
"Comment": [
{
"Comment": "test comment",
"User": "admin",
"Created": "2020-01-19T12:35:49.194+0200"
}
],
"Id": "15572"
}
}
Human Readable Output
Comments
| Comment | Created | User |
|---|---|---|
| test comment | 2020-01-19T12:35:49.194+0200 | admin |
jira-delete-issue
Deletes an issue in Jira.
Base Command
jira-delete-issue
Input
| Argument Name | Description | Required |
|---|---|---|
| issueIdOrKey | The ID or key of the issue. | Required |
Context Output
There is no context output for this command.
Command Example
!jira-delete-issue issueIdOrKey=DEM-5415
Human Readable Output
Issue deleted successfully.
jira-get-id-offset
Returns the ID offset, for example, the first issue ID.
Base Command
jira-get-id-offset
Input
There are no input arguments for this command.
Context Output
| Path | Type | Description |
|---|---|---|
| Ticket.idOffSet | string | The ID offset. |
Command Example
##### Context Example
{
“Ticket.idOffSet”: “10161”
}
##### Human Readable Output
ID Offset: 10161
### jira-get-id-by-attribute
***
Gets the Account ID for a given user's attribute.
#### Base Command
`jira-get-id-by-attribute`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| attribute | The user's attribute value. Can be Username or Email address. | Required |
| max_results | The maximum number of users to pull when searching for a matching user (default is 50). The maximum allowed value is dictated by the JIRA property 'jira.search.views.default.max'. If you specify a value that is higher than this number, your search results will be truncated. | Optional |
| is_jirav2api | Whether the server is on prem and uses the REST v2 API. | Optional |
#### Context Output
| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Jira.User.Attribute | String | The user's attribute. |
| Jira.User.AccountID | String | The user's Account ID. |
#### Command Example
```!jira-get-id-by-attribute attribute="XSOAR User"```
#### Context Example
```json
{
"Jira": {
"User": {
"AccountID": "5e4ds952052b790c97509a7c",
"Attribute": "XSOAR User"
}
}
}
Human Readable Output
Account ID for attribute: XSOAR User is: 5e4ds952052b790c97509a7c
jira-list-transitions
Lists all possible transitions for a given ticket.
jira-append-to-field
Modifies a specific field in an issue in Jira by appending to it instead of replacing its content.
Field must be either of type string (appending by using ‘,’) or arrayd.
Base Command
jira-append-to-field
Input
| Argument Name | Description | Required |
|---|---|---|
| issueId | The ID of the issue to edit. | Required |
| fieldJson | The field object (in JSON format). For example {“customfield_10037”: “New value”}. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Ticket.Id | Unknown | The ticket ID. |
| Ticket.Key | Unknown | The ticket key. |
| Ticket.Assignee | Unknown | The user assigned to the ticket. |
| Ticket.Creator | Unknown | The user who created the ticket. |
| Ticket.Summary | Unknown | The ticket summary. |
| Ticket.Status | Unknown | The ticket status. |
Command Example
!jira-append-to-field issueId=CIAC-3597 fieldJson={\"customfield_16492\":\"example\"}
Context Example
{
"Ticket": {
"Assignee": "User Name(user@example.com)",
"Created": "2022-07-21T10:52:22.043+0000",
"Creator": "User Name(user@example.com)",
"DueDate": null,
"Custom Field Display Name": "test,example",
"Id": "1179420",
"Key": "TEST-3597",
"Labels": [
"test",
],
"LastSeen": null,
"LastUpdate": "2022-07-27T04:47:24.214+0000",
"Priority": "P5",
"ProjectName": "Project Name",
"Status": "Backlog",
"Summary": "example",
"attachment": ""
}
}
jira-get-specific-field
Gets specific fields from a Jira issue and adds it to context dynamically.
Base Command
jira-get-specific-field
Input
| Argument Name | Description | Required |
|---|---|---|
| issueId | The ID of the issue to edit. | Required |
| components | The fields to retrieve from the issue. For example field=”customfield_164,labels”. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Ticket.Id | Unknown | The ticket ID. |
| Ticket.Key | Unknown | The ticket key. |
| Ticket.Assignee | Unknown | The user assigned to the ticket. |
| Ticket.Creator | Unknown | The user who created the ticket. |
| Ticket.Summary | Unknown | The ticket summary. |
| Ticket.Status | Unknown | The ticket status. |
Command Example
!jira-get-specific-field issueId="TEST-3597" field="labels,customfield_16492"
Context Example
{
"Ticket": {
"Assignee": "User Name(user@example.com)",
"Created": "2022-07-21T10:52:22.043+0000",
"Creator": "User Name(user@example.com)",
"DueDate": null,
"Custom Field Display Name": "test",
"Id": "1179420",
"Key": "TEST-3597",
"Labels": [
"test"
],
"LastSeen": null,
"LastUpdate": "2022-07-27T04:47:24.214+0000",
"Priority": "P5",
"ProjectName": "Project Name",
"Status": "Backlog",
"Summary": "example",
"attachment": ""
}
}
Base Command
jira-list-transitions
Input
| Argument Name | Description | Required |
|---|---|---|
| issueId | The ID of the issue. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Ticket.Transitions.ticketId | Number | The ID of the issue. |
| Ticket.Transitions.transitions | Unknown | A list of all possible transitions. |
Command Example
!jira-list-transitions issueId=18847
Context Example
{
"Ticket": {
"Transitions": {
"ticketId": "18847",
"transitions": ["Backlog", "Selected for Development", "In Progress", "Done", "To Do", "Build Broken"]
}
}
}
Human Readable Output
List Transitions:
| Transition Name |
|---|
| Backlog |
| Selected for Development |
| In Progress |
| Done |
| To Do |
| Build Broken |
get-remote-data
Gets remote data from a remote incident. This method is only used for debugging purposes and will not update the current incident.
Base Command
get-remote-data
Input
| Argument Name | Description | Required |
|---|---|---|
| id | The remote incident ID (issue ID). | Required |
| lastUpdate | UTC timestamp in seconds. The incident is only updated if it was modified after the last update time. Default is 0. | Optional |
Context Output
There is no context output for this command.
get-mapping-fields
Returns the fields to map. This method is only used for debugging purposes.
Base Command
get-mapping-fields
Input
There are no input arguments for this command.
Context Output
There is no context output for this command.
update-remote-system
Gets incident changes. This method is only used for debugging purposes and will not update the current incident.
Base Command
update-remote-system
Input
There are no input arguments for this command.
Context Output
There is no context output for this command.
get-modified-remote-data
Available from Cortex XSOAR version 6.1.0. This command queries for incidents that were modified since the last update. This method is only used for debugging purposes.
Base Command
get-modified-remote-data
Input
There are no input arguments for this command.
Context Output
There is no context output for this command.
jira-issue-assign
Edit the assignee of an existing issue.
Base Command
jira-issue-assign
Input
| Argument Name | Description | Required |
|---|---|---|
| issueId | The ID of the issue to edit. | Required |
| assignee | The name of the assignee. Relevant for Jira Server only, if you are using Jira Cloud, provide the assignee_id argument instead. | Optional |
| assignee_id | The account ID of the assignee. Use the jira-get-id-by-attribute command to get the user’s account ID. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Ticket.Id | String | The ticket ID. |
| Ticket.Key | String | The ticket key. |
| Ticket.Assignee | String | The user assigned to the ticket. |
| Ticket.Creator | String | The user who created the ticket. |
| Ticket.Summary | String | The ticket summary. |
| Ticket.Status | String | The ticket status. |
Command example
!jira-issue-assign issueId=21492 assignee_id=1234
Context Example
{
"Ticket": {
"Assignee": "assignee1(null)",
"Created": "2023-03-01T14:05:49.037+0200",
"Creator": "assignee1(null)",
"Description": null,
"DueDate": null,
"Id": "21492",
"Key": "key",
"Labels": [],
"LastSeen": "2023-03-15T15:40:44.329+0200",
"LastUpdate": "2023-05-03T16:15:32.771+0300",
"Priority": "Medium",
"ProjectName": "test",
"Status": "To Do",
"Summary": "something something",
"attachment": "attachments"
}
}
Human Readable Output
jira-issue-assign
|assignee| attachment |created|creator|description|duedate|id|issueType| key |labels|priority| project |reporter|status|summary| ticket_link |
|————-|—|—|—|—|—|—|—–|—|—|———|—|—|—|——————————————————-|—|
| assignee1(null) | attachments | 2023-03-01T14:05:49.037+0200 | assignee1(null) | | | 21492 | Task (Tasks track small, distinct pieces of work.) | key | | Medium | test | assignee1(null) | To Do | something something | https://test/rest/api/latest/issue/21492 |
Issue #21492 was updated successfully
Configure Incident Mirroring
This feature is compliant with Cortex XSOAR version 6.0 and above.
This part walks you through setting up the Jira integration to mirror incidents from Jira in Cortex XSOAR.
The instructions below include steps for configuring the integration and the incoming and outgoing mappers. However, not every option available in the integration, nor all classification and mapping features are covered.
For information about Classification and Mapping visit: Classification and Mapping (Cortex XSOAR 6.13) or Classification and Mapping (Cortex XSOAR 8 Cloud) or Classification and Mapping (Cortex XSOAR 8.7 On-prem).
When mirroring incidents, you can make changes in Jira, which will be reflected in Cortex XSOAR, or vice versa.
You can also attach files from either of the systems, which will then be available in the other system.
This is made possible by the addition of 3 new functions to the integration, which are applied with the following options:
- External schema support
- Can sync mirror in
- Can sync mirror out

STEP 1 - Modify the incoming mapper
- Navigate to Classification and Mapping and click classifier-mapper-incoming-JiraV2.
- Under the Incident Type dropdown, select Jira Incident.
- Change the mapping according to your needs.
- Save your changes.
5 fields have been added to support the mirroring feature
- dbotMirrorDirection - determines whether mirroring is incoming, outgoing, or both. Default is Both.
- You can choose the mirror direction when configuring the Jira instance using the Incident Mirroring Direction field.
- dbotMirrorId - determines the incident ID in the 3rd party integration. In this case, the Jira ID field.
- dbotMirrorInstance - determines the Jira instance with which to mirror.
- dbotMirrorLastSync - determines the field by which to indicate the last time that the systems synchronized.
- dbotMirrorTags - determines the tags that you need to add in Cortex XSOAR for entries to be pushed to Jira.
- You can set the tags in the instance configuration, using File Entry Tag, and Comment Entry Tag.

STEP 2 - Modify the outgoing mapper
- Under Classification and Mapping, click classifier-mapper-outgoing-Jira.
- Under Select Instance dropdown, select the instance name you want to work with.
The left side of the screen shows the Jira fields to map and the right side of the
screen shows the Cortex XSOAR fields by which you are mapping.
*Note: If Select Instance dropdown is empty, go to the integration’s settings and under Incident Type select Jira Incident and try again. - Under Schema Type, select Jira Incident. The Schema Type represents the Jira entity that
you are mapping to. In our example it is an incident, but it can also be any other kind of ticket that
Jira supports.

- Under the Incident Type dropdown, select Jira Incident.
- On the right side of the screen, under Incident, select the incident based on which you want to
match. - Change the mapping according to your needs.
- Save your changes.
STEP 3 - Configure the following integration fields in order to customize the mirroring feature
- Mirror outgoing incidents: If enabled, any incident data changed in existing fetched incidents will be reflected in the remote Jira server.
- Mirror incoming incidents: If enabled, any incident data changed in the remote Jira server will be reflected in existing fetched incidents.
- Fetch incidents: Should be enabled in order to mirror in and out new incidents.
- Incident type: In order to mirror out changes, provide an incident type that is associated with a layout containing the fields, which values you want to mirror. You can use ‘Jira Incident’, which already has a built-in layout.
- File Entry Tag: Choose a tag to add to an entry in order to mirror it as an attachment in Jira.
- Comment Entry Tag: Choose a tag to add to an entry in order to mirror it as a comment in Jira.
- Fetch Comments: Fetch comments for Jira ticket.
- Fetch Attachments: Fetch attachments for Jira ticket.
STEP 4 - Create an incident in Jira. For the purpose of this use case, it can be a very simple incident
STEP 5 - In Cortex XSOAR, the new ticket will be ingested in approximately one minute
- Add a note to the incident. In the example below, we have written a comment from Cortex XSOAR to Jira.
- Click Actions -> Tags and add the Comment Entry Tag tag that you’ve selected before. If not already modified, the default is: “comment”.
- Add a file to the incident and mark it with the File Entry Tag tag that you’ve selected before. If not already modified, the default is: “attachment”.

- Go back to Incident Info and locate a field you’ve configured in the outgoing mapper that is displayed in the incident’s layout. Change its value and click ‘V’ in the ‘V/X’ menu to save the changes.

- Navigate back to the incident in Jira and within approximately one minute, the changes will be reflected there as well.
- Change a Jira field you’ve configured in the incoming mapper.
- Go back to Cortex XSOAR and within approximately one minute, the changes will be reflected there as well.
- You can make additional changes like closing the incident or changing the description and those will be reflected in both systems.
Notes
- The final ‘source of truth’ of the incident for Cortex XSOAR are the values in Cortex XSOAR.
Meaning, if you change the severity in Cortex XSOAR and then change it back in Jira, the final value that will be presented is the one in Cortex XSOAR.
You can see a list of these fields for each incident under “Context Data” -> “dbotDirtyFields”. - If you wish to mirror in and out Jira’s custom fields, please see the ‘Mirror In And Out Custom Fields’ section below.
- If you wish to change the status of a Jira incident using transitions, please see the ‘Change Ticket’s Status Using Transitions’ section below.
Mirror In And Out Custom Fields
Add a new custom field and add it to the incident type's layout
Add a new custom field and add it to the incident type's layout:
1.Create a new incident field:
- Go to settings -> advanced -> fields -> new field
- Under “Basic Settings”:
a.provide a name
b.Choose field type. For example for a label, you might want to use “Tag” as a type. - Click on “Attributes”:
a.Uncheck the box under: “Add to incident types”
b.for “Add associated type…” Select the incident type you want to work with. In this example I’ll choose “Jira Incident”.

2.Add the new incident field you've just created to the layout associated with the integration's incident type. In this example, the layout is “Jira Incident Layout”:

Add the new field to the incoming mapper
- Go to Settings -> Integrations -> Servers & Services -> in the search bar type “Jira”.
- Open the integration settings by clicking on the button:
or by clicking on “Add instance” if you don't have Jira already configured in your system.
1.If you don't have Jira configured yet, please provide all the needed information for authentication (Jira URL, Username if needed…) and click the “Test” button in order to see that the integration is well set.
2.In addition:- Make sure that under “Incident type” you've selected the incident type you want to work with and it is the same one you've selected once you created the new field. In our example: “Jira Incident”.
- The option “Fetch Incidents” is checked.
- The option “Mirror incoming incidents” is checked.
- The option “Mirror outgoing incidents” is checked.
Finally, click “Done”, and wait for new Jira incidents to be pulled:

-
Once you've finished the setup stage, go to the “Mapper (incoming)” field and select the mapper you want to work with by clicking on “Select”:

- Click on the button:
in order to edit the selected mapper. It will open the “Incident Incoming Mapping Editor”.
1.For “Incident Type”, please provide the incident type you've selected in the integration settings.
2.In “Select Instance” choose the name of your instance. You can find it in the integration settings (you can see a screenshot above), under “Name”.
Once done, You'll see on the right side of the screen a Jira incident.
*Note: If you want to work with a specific incident, in “Get data” select the option: “Upload JSON” and provide the wanted incident as JSON.
In order to get the incident as JSON, you can see the section: “how to get an incident as JSON” below.
On the left side, You'll see all Jira incident fields.

3.Use “Search Cortex XSOAR field…” in order to find your new incident field by searching its name.
4.Once found, click on “Choose data path” and map it to the custom jira field:
a.Find the jira field you want to map to this incident field on the right side, and click on its value.
b.Then you will see the path you've selected under your new added field:

*Note: In this example:”customfield_ 10045” is the Jira field ID we want to map. You can find IDs of custom fields for your field in Jira using this guide:Click Here
*Note: You can also type the path manually. - Click “Save Version”.
Add the new field to the outgoing mapper
- Go to Settings -> Integrations -> Servers & Services -> in the search bar type “Jira”.
- Open the integration settings by clicking on the button:

- go to the “Mapper (outgoing)” field and select the mapper you want to work with by clicking on “Select” (the same process we did with the incoming mapper).
- Click on the button:
in order to edit the selected mapper. It will open the “Incident Outgoing Mapping Editor”. - In “Select Instance” choose the name of your instance. You can find it in the integration settings (you can see a screenshot above), under “Name”.
1.See that “Schema Type” changes automatically to “Jira Incident”.

- Select one incident to work with by specifying it in the “Incident” list (it can be seen in the screenshot above under “Select Instance”).
1.On the right side you are now able to see your selected incident.
2.On the left side you are able to see all the Jira fields you can map in the system. - Search for the Jira field you want to map on the left side. In this example, we want to map customfield_10045

- Map the Jira field to the incident field you've created by clicking on “Select incident field” and type its name*.
*The name of the incident will not include spaces, underscores, capital letters, etc. for example: Jira Transitions will be transformed to “jiratransitions” once saved in the incident's context (you can see the name you need to select by fetching incidents after configuring the mapper in. In the “Incident” field select one of the new incidents that the edited mapper in has worked on. On the right side you should be able to see the new added field and the name you need to provide). - Click the “Save Version” button.
How to get an incident as JSON
- Use the !jira-get-issue command with the raw-response=true parameter. For example:
!jira-get-issue issueId=VIK-183 raw-response=true - Click on “view full JSON in a new tab”

- Click on “Download as a file”.

- Open the downloaded file and put its content inside a list, resulting in a list which contains only one item, the downloaded JSON.
- When selecting “Upload JSON” for “Get data” as mentioned above, you can upload the file using the “Upload JSON” option:

Change Ticket’s Status Using Transitions
- Make sure you’ve configured your instance as described in ‘Configure Incident Mirroring’ above.
- Make sure you’re using the ‘Jira Incident Layout’ for displaying the incident which status you want to change.
The reason this is required is that this layout is structured in a way that both ‘script-JiraChangeTransition’ and ‘script-JiraListTransition’ scripts can work, which is crucial when you want to use transitions. - In order to change an incident status, open the incident you would like to update.
- Under “Incident Info” search for the “Jira Transitions” field.
- Click on “Select” and choose the name of the new status.

- Click on the ‘V’ option in the ‘V/X’ menu.
- You can see the new status under ‘Jira Status’.
- Go to Jira, within approximately one minute you will be able to see the new status there as well.
jira-add-user-to-project
Adds the user to a project.
Base Command
jira-add-user-to-project
Input
| Argument Name | Description | Required |
|---|---|---|
| user_email | User’s email address. | Optional |
| project_key | Project key. | Optional |
| role_name | Role name to assign the user. | Optional |
Context Output
There is no context output for this command.
jira-get-project-role
Gets the information related to the role, including assigned users.
Base Command
jira-get-project-role
Input
| Argument Name | Description | Required |
| — | — | — |
Context Output
There is no context output for this command.
jira-get-organizations
Lists all the organizations.
Base Command
jira-get-organizations
Input
| Argument Name | Description | Required |
| — | — | — |
Context Output
There is no context output for this command.
Configuration parameters
url— Jira URL, for example: https://demisto.atlassian.net/ (required)username— Username (Basic Authentication)password— Password (Deprecated - Use API token)credentials—consumerKey— Consumer key (OAuth 1.0)accessToken— Access TokenprivateKey— Private key (PKCS8 format)query— Query (in JQL) for fetching incidents.idOffset— Issue index to start fetching incidents frominsecure— Trust any certificate (not secure)proxy— Use system proxy settingsisFetch— Fetch incidentsincoming_mirror— Mirror incoming incidentsincidentType— Incident typefetchByCreated— Use created field to fetch incidentsoutgoing_mirror— Mirror outgoing incidentsfile_tag— File entry tagcomment_tag— Comment entry tagfetch_comments— Fetch commentsfetch_attachments— Fetch attachmentsAPItoken— API token (Deprecated)incidentFetchInterval— Incidents Fetch Interval
Commands (22)
-
get-mapping-fieldsReturns the fields to map. This method is only used for debugging.
-
get-modified-remote-dataAvailable from Cortex XSOAR version 6.1.0. This command queries for incidents that were modified since the last update. This method is only used for debugging.
-
get-remote-dataGets remote data from a remote incident. This method is only used for debugging and does not update the current incident.
-
jira-add-user-to-projectAdds the user to a project.
-
jira-append-to-fieldModifies a specific field in an issue in Jira by appending to it instead of replacing its content. Field must be either of type string (appending by using ',') or array.
-
jira-create-issueCreates a new issue in Jira.
-
jira-delete-issueDeletes an issue in Jira.
-
jira-edit-issueModifies an issue in Jira.
-
jira-get-commentsReturns the comments added to a ticket.
-
jira-get-id-by-attributeGets the account ID for a given user attribute.
-
jira-get-id-offsetReturns the ID offset. For example the first issue ID.
-
jira-get-issueFetches an issue from Jira.
-
jira-get-organizationsLists all the organizations.
-
jira-get-project-roleGets the information related to the role, including assigned users.
-
jira-get-specific-fieldGets specific fields from a Jira issue and adds it to context dynamically.
-
jira-issue-add-commentAdds a new comment to an existing Jira issue.
-
jira-issue-add-linkCreates (or updates) an issue link.
-
jira-issue-assignEdit the assignee of an existing issue.
-
jira-issue-queryQueries Jira issues.
-
jira-issue-upload-fileUploads a file attachment to an issue.
-
jira-list-transitionsLists all possible transitions for a given ticket.
-
update-remote-systemGets incident changes. This method is only used for debugging and does not update the current incident.
from optparse import OptionParser from unittest.mock import Mock import demistomock as demisto import pytest from CommonServerPython import * integration_params = { "url": "https://localhost", "APItoken": "token", "username": "test", "password": "1234!", "query": "status=Open", } integration_args_missing_mandatory_project_key_and_name = { "summary": "test", } integration_args_missing_mandatory_name = { "summary": "test", "projectKey": "testKey", } integration_args_missing_mandatory_key = { "summary": "test", "projectName": "testName", } integration_args = { "summary": "test", "projectKey": "testKey", "projectName": "testName", } @pytest.fixture(autouse=True) def set_params(mocker): mocker.patch.object(demisto, "params", return_value=integration_params) @pytest.mark.parametrize( "args", [ integration_args, integration_args_missing_mandatory_name, integration_args_missing_mandatory_key, ], ) def test_create_issue_command_after_fix_mandatory_args_issue(mocker, args): from JiraV2 import create_issue_command mocker.patch.object(demisto, "args", return_value=args) user_data = { "self": "https://demistodev.atlassian.net/rest/api/2/user?accountId=1234", "accountId": "1234", "emailAddress": "admin@demistodev.com", "displayName": "test", "active": True, "timeZone": "Asia/Jerusalem", "locale": "en_US", "groups": {"size": 1, "items": []}, "applicationRoles": {"size": 1, "items": []}, "expand": "groups,applicationRoles", "projects": [{"id": "1234", "key": "testKey", "name": "testName"}], } mocker.patch("JiraV2.jira_req", return_value=user_data) mocker.patch.object(demisto, "results") create_issue_command() assert demisto.results.call_count == 1 @pytest.mark.parametrize( "args", [integration_args_missing_mandatory_project_key_and_name] ) def test_create_issue_command_before_fix_mandatory_args_summary_missing(mocker, args): mocker.patch.object(demisto, "args", return_value=args) mocker.patch.object(demisto, "results") mocker.patch.object(demisto, "debug") mocker.patch.object(demisto, "info") from JiraV2 import create_issue_command with pytest.raises(SystemExit) as e: # when there are missing arguments, an Exception is raised to the user create_issue_command() assert e assert (demisto.results.call_args[0][0]["Contents"] == "You must provide at least one of the following: " "project_key or project_name") def test_issue_query_command_no_issues(mocker): """ Given - Jira issue query command When - Sending HTTP request and getting no issues from the query Then - Verify no error message is thrown to the user """ from JiraV2 import issue_query_command mocker.patch("JiraV2.run_query", return_value={}) human_readable, _, _ = issue_query_command("status=Open AND labels=lies") assert "No issues matched the query" in human_readable def test_issue_query_command_with_results(mocker): """ Given - Jira issue query command When - Sending HTTP request and getting one issues from the query Then - Verify outputs """ from JiraV2 import issue_query_command from test_data.raw_response import QUERY_ISSUE_RESPONSE from test_data.expected_results import QUERY_ISSUE_RESULT mocker.patch("JiraV2.run_query", return_value=QUERY_ISSUE_RESPONSE) _, outputs, _ = issue_query_command("status!=Open", max_results=1) assert outputs == QUERY_ISSUE_RESULT def test_issue_query_command_with_custom_fields_with_results(mocker, requests_mock): """ Given - Jira issue query command and extraFields parameters When - Sending HTTP request and getting one issues from the query Then - Verify outputs """ from JiraV2 import issue_query_command from test_data.raw_response import QUERY_ISSUE_RESPONSE, EXPECTED_RESP from test_data.expected_results import QUERY_ISSUE_RESULT_WITH_CUSTOM_FIELDS requests_mock.get('https://localhost/rest/api/latest/search/', json=QUERY_ISSUE_RESPONSE) mocker.patch("JiraV2.get_custom_field_names", return_value=EXPECTED_RESP) _, outputs, _ = issue_query_command("status!=Open", extra_fields="Owner", max_results=1) assert outputs == QUERY_ISSUE_RESULT_WITH_CUSTOM_FIELDS def test_fetch_incidents_no_incidents(mocker): """ Given - Jira fetch incidents command When - Sending HTTP request and getting no issues from the query Then - Verify no incidents are returned """ from JiraV2 import fetch_incidents mocker.patch.object(demisto, "info") mocker.patch.object(demisto, "debug") mocker.patch("JiraV2.run_query", return_value={}) mocker.patch.object(demisto, 'setLastRun') incidents = fetch_incidents( "status=Open AND labels=lies", id_offset=1, should_get_attachments=False, should_get_comments=False, should_mirror_in=False, should_mirror_out=False, comment_tag="", attachment_tag="", ) assert incidents == [] assert demisto.setLastRun.call_count == 1 lastRun = demisto.setLastRun.call_args[0][0] assert lastRun == {'idOffset': 0, 'lastCreatedTime': ''} def test_fetch_incidents_no_incidents_with_id_offset_in_last_run(mocker): """ Given - Jira fetch incidents command - Last run is populated with idOffset but no lastCreatedTime When - Sending HTTP request and getting no issues from the query Then - Verify no incidents are returned - Last run idOffset is not changed and an empty lastCreatedTime is added """ from JiraV2 import fetch_incidents mocker.patch.object(demisto, "info") mocker.patch.object(demisto, "debug") mocker.patch("JiraV2.run_query", return_value={}) mocker.patch.object(demisto, 'setLastRun') mocker.patch.object(demisto, 'getLastRun', return_value={'idOffset': 30}) incidents = fetch_incidents( "status=Open AND labels=lies", id_offset=1, should_get_attachments=False, should_get_comments=False, should_mirror_in=False, should_mirror_out=False, comment_tag="", attachment_tag="", ) assert incidents == [] assert demisto.setLastRun.call_count == 1 last_run = demisto.setLastRun.call_args[0][0] assert last_run == {'idOffset': 30, 'lastCreatedTime': ''} def test_fetch_incidents_with_incidents_and_id_offset_in_last_run(mocker): """ Given - Jira fetch incidents command - Last run is populated with idOffset but no lastCreatedTime When - Sending HTTP request and getting new issue Then - Verify last run is updated with the ticket id offset and created time """ from JiraV2 import fetch_incidents from test_data.raw_response import QUERY_ISSUE_RESPONSE mocker.patch.object(demisto, "info") mocker.patch.object(demisto, "debug") mocker.patch("JiraV2.run_query", return_value=QUERY_ISSUE_RESPONSE) mocker.patch.object(demisto, 'setLastRun') mocker.patch.object(demisto, 'getLastRun', return_value={'idOffset': 30}) incidents = fetch_incidents( "status=Open AND labels=lies", id_offset=1, should_get_attachments=False, should_get_comments=False, should_mirror_in=False, should_mirror_out=False, comment_tag="", attachment_tag="", ) assert len(incidents) == 1 assert demisto.setLastRun.call_count == 1 last_run = demisto.setLastRun.call_args[0][0] assert last_run == {'idOffset': 12652, 'lastCreatedTime': '2019-05-04T00:44:31.743+0300'} def test_fetch_incidents_with_incidents_and_full_last_run(mocker): """ Given - Jira fetch incidents command - Last run is populated with idOffset and lastCreatedTime When - Sending HTTP request and getting new issue Then - Verify last run is updated with the ticket id offset and are updated """ from JiraV2 import fetch_incidents from test_data.raw_response import QUERY_ISSUE_RESPONSE mocker.patch.object(demisto, "info") mocker.patch.object(demisto, "debug") mocker.patch("JiraV2.run_query", return_value=QUERY_ISSUE_RESPONSE) mocker.patch.object(demisto, 'setLastRun') mocker.patch.object(demisto, 'getLastRun', return_value={'idOffset': 1000, 'lastCreatedTime': '2019-04-04T00:55:22.743+0300'}) incidents = fetch_incidents( "status=Open AND labels=lies", id_offset=1, should_get_attachments=False, should_get_comments=False, should_mirror_in=False, should_mirror_out=False, comment_tag="", attachment_tag="", ) assert len(incidents) == 1 assert demisto.setLastRun.call_count == 1 last_run = demisto.setLastRun.call_args[0][0] assert last_run == {'idOffset': 12652, 'lastCreatedTime': '2019-05-04T00:44:31.743+0300'} def test_module(mocker): """ Given - Jira test module When - Sending HTTP request and getting the user details Then - Verify test module returns ok """ from JiraV2 import test_module as module user_data = { "self": "https://demistodev.atlassian.net/rest/api/2/user?accountId=1234", "accountId": "1234", "emailAddress": "admin@demistodev.com", "displayName": "test", "active": True, "timeZone": "Asia/Jerusalem", "locale": "en_US", "groups": {"size": 1, "items": []}, "applicationRoles": {"size": 1, "items": []}, "expand": "groups,applicationRoles", } mocker.patch("JiraV2.jira_req", return_value=user_data) mocker.patch("JiraV2.run_query", return_value={}) result = module() assert result == "ok" def test_get_remote_data_when_needs_update(mocker): """ Given: - Information regarding a changed incident in Demisto When: - Running get-remote-date and the incident needs to be updated Then: - Returns GetRemoteDataResponse object with the incident's details that needs to be updated """ from JiraV2 import get_remote_data_command from test_data.expected_results import GET_JIRA_ISSUE_RES mocker.patch.object(demisto, "info") mocker.patch.object(demisto, "debug") mocker.patch("JiraV2.get_issue", return_value=("", "", GET_JIRA_ISSUE_RES)) mocker.patch( "JiraV2.get_comments_command", return_value=("No comments were found in the ticket", None, None), ) mocker.patch("JiraV2.get_attachments", return_value="") res = get_remote_data_command({"id": "15", "lastUpdate": "0"}) assert len(res.mirrored_object) != 0 assert res.entries == [] def test_get_remote_data_when_dont_need_update(mocker): """ Given: - Information regarding a changed incident in Demisto When: - Running get-remote-date and the incident was already updated Then: - There aren't change to perform in Demisto """ from JiraV2 import get_remote_data_command from test_data.expected_results import GET_JIRA_ISSUE_RES updated_date = "1996-11-25T16:29:37.277764067Z" GET_JIRA_ISSUE_RES["updated"] = updated_date mocker.patch.object(demisto, "info") mocker.patch.object(demisto, "debug") mocker.patch("JiraV2.get_issue", return_value=("", "", GET_JIRA_ISSUE_RES)) mocker.patch( "JiraV2.get_comments_command", return_value=("No comments were found in the ticket", None, None), ) mocker.patch("JiraV2.get_attachments", return_value="") res = get_remote_data_command( {"id": "15", "lastUpdate": "2050-11-25T16:29:37.277764067Z"} ) assert res.mirrored_object == {'in_mirror_error': ''} assert res.entries == [] def test_update_remote_system_delta(mocker): """ Given: - Information regarding a changed incident in XSOAR When: - An incident's summary was changed. Then: - The issue in Jira has the new summary. """ import JiraV2 from JiraV2 import update_remote_system_command mocker.patch("JiraV2.edit_issue_command", return_value="") mocker.patch.object(demisto, "info") mocker.patch.object(demisto, "debug") res = update_remote_system_command( { "incidentChanged": "17757", "remoteId": "17757", "data": {"summary": "data", "not_changes_key": "not_changes_val"}, "delta": {"summary": "changes", "dbotMirrorDirection": "test"}, } ) assert res == "17757" assert JiraV2.edit_issue_command.call_args[1]['summary'] == 'data' def test_get_mapping_fields(mocker): from JiraV2 import get_mapping_fields_command mocker.patch.object(demisto, "info") mocker.patch.object(demisto, "debug") mocker.patch("JiraV2.get_custom_fields", return_value={}) res = get_mapping_fields_command() assert list(res.scheme_types_mappings[0].fields.keys()) == [ "issueId", "summary", "description", "labels", "priority", "dueDate", "assignee", "status", "assignee_id" ] def test_get_new_attachment_return_result(requests_mock): """ Given: - attachment related to an issue - The date the incident was last updated When: - An incident's attachment was modified\added Then: - The updated\new as fileResult """ from JiraV2 import get_attachments from test_data.expected_results import JIRA_ATTACHMENT from dateparser import parse requests_mock.get('https://localhost/rest/attachment/content/14848', json={}) res = get_attachments(JIRA_ATTACHMENT, parse("1996-11-25T16:29:35.277764067Z")) assert res[0]["File"] == "download.png" def test_get_all_attachment_return_result(requests_mock): """ Given: - attachment related to an issue - The date the incident was last updated When: - An incident's attachment was modified\added Then: - Getting all attachments as fileResult """ from JiraV2 import get_attachments from test_data.expected_results import JIRA_ATTACHMENT_ALL from dateparser import parse for attachment in JIRA_ATTACHMENT_ALL: requests_mock.get(attachment.get('content'), json={}) requests_mock.get(attachment.get('self'), json={'filename': attachment.get('filename')}) res = get_attachments( JIRA_ATTACHMENT_ALL, parse("1996-11-25T16:29:35.277764067Z"), only_new=False ) assert res[0]["File"] == "filename1" assert res[1]["File"] == "filename2" assert res[2]["File"] == "filename3" def test_get_new_attachment_without_return_new_attachment(mocker): """ Given: - attachment related to an issue - The date the incident was last updated When: - An incident's attachment wasn't modified Then: - There is no attachment to update """ from JiraV2 import get_attachments from test_data.expected_results import JIRA_ATTACHMENT from dateparser import parse import pytz class file: def __init__(self): self.content = b"content" file_content = file() mocker.patch("JiraV2.jira_req", return_value=file_content) res = get_attachments( JIRA_ATTACHMENT, parse("2070-11-25T16:29:37.277764067Z").replace(tzinfo=pytz.UTC), ) assert res == [] def test_get_incident_entries(mocker): """ Given: - Jira issue - The date the incident was last updated When: - An incident's attachment or comments are updated in Jira Then: - The new entries are returned """ from JiraV2 import get_incident_entries from test_data.expected_results import GET_JIRA_ISSUE_RES from dateparser import parse import pytz updated_date = "1996-11-25T16:29:37.277764067Z" GET_JIRA_ISSUE_RES["updated"] = updated_date mocker.patch( "JiraV2.get_comments_command", return_value=( "", "", {"comments": [{"updated": "2071-12-21 12:29:05.529000+00:00"}]}, ), ) mocker.patch("JiraV2.get_attachments", return_value="here there is attachment") res = get_incident_entries( GET_JIRA_ISSUE_RES, parse("2070-11-25T16:29:37.277764067Z").replace(tzinfo=pytz.UTC), ) assert len(res["comments"]) > 0 assert res["attachments"] == "here there is attachment" def test_update_remote_system(mocker): """ Given: - Information regarding a changed incident When - An incident was changed in Demisto Then - The remote system is updated and the incident id returns. """ from JiraV2 import update_remote_system_command from test_data.expected_results import ARGS_FROM_UPDATE_REMOTE_SYS mocker.patch.object(demisto, "info") mocker.patch.object(demisto, "debug") mocker.patch.object(demisto, "getFilePath", return_value={"name": "file.png"}) mocker.patch("JiraV2.edit_issue_command", return_value="") mocker.patch("JiraV2.upload_file", return_value="") mocker.patch("JiraV2.add_comment", return_value="") res = update_remote_system_command(ARGS_FROM_UPDATE_REMOTE_SYS) assert res == "17757" def test_fetch_incident_with_getting_attachments_and_comments(mocker): """ Given: - Fetch parameters When - Incidents needs to include both attachments and comments. Then - Returned all fetched incidents with their attachments and comments. """ from JiraV2 import fetch_incidents from test_data.raw_response import QUERY_ISSUE_RESPONSE from test_data.expected_results import GET_JIRA_ISSUE_RES mocker.patch.object(demisto, "info") mocker.patch.object(demisto, "debug") mocker.patch("JiraV2.run_query", return_value=QUERY_ISSUE_RESPONSE) mocker.patch.object( demisto, "params", return_value={ "fetch_attachments": True, "fetch_comments": True, "id_offset": "17803", "query": "status!=done", }, ) mocker.patch( "JiraV2.get_comments_command", return_value=( "", "", {"comments": [{"updated": "2071-12-21 12:29:05.529000+00:00"}]}, ), ) mocker.patch( "JiraV2.get_attachments", return_value=[{"FileID": 1, "File": "name", "Type": "file"}], ) mocker.patch("JiraV2.get_issue", return_value=("", "", GET_JIRA_ISSUE_RES)) res = fetch_incidents( "status!=done", id_offset=1, should_get_attachments=True, should_get_comments=True, should_mirror_in=False, should_mirror_out=False, comment_tag="", attachment_tag="", ) assert list(res[0]["attachment"][0].keys()) == ["path", "name"] assert len(res[0]["labels"][12]["value"]) > 0 def test_fetch_incident_with_getting_attachments(mocker): """ Given: - Fetch parameters When - Incidents needs to include attachments. Then - Returned all fetched incidents with their attachments. """ from JiraV2 import fetch_incidents from test_data.raw_response import QUERY_ISSUE_RESPONSE from test_data.expected_results import GET_JIRA_ISSUE_RES mocker.patch.object(demisto, "info") mocker.patch.object(demisto, "debug") mocker.patch("JiraV2.run_query", return_value=QUERY_ISSUE_RESPONSE) mocker.patch.object( demisto, "params", return_value={ "fetch_attachments": True, "fetch_comments": True, "id_offset": "17803", "query": "status!=done", }, ) mocker.patch( "JiraV2.get_attachments", return_value=[{"FileID": 1, "File": "name", "Type": "file"}], ) mocker.patch("JiraV2.get_issue", return_value=("", "", GET_JIRA_ISSUE_RES)) res = fetch_incidents( "status!=done", id_offset=1, should_get_attachments=True, should_get_comments=False, should_mirror_in=False, should_mirror_out=False, comment_tag="", attachment_tag="", ) assert list(res[0]["attachment"][0].keys()) == ["path", "name"] assert res[0]["labels"][12]["value"] == "[]" def test_fetch_incident_with_getting_comments(mocker): """ Given: - Fetch parameters When - Incidents needs to include comments. Then - Returned all fetched incidents with their comments. """ from JiraV2 import fetch_incidents from test_data.raw_response import QUERY_ISSUE_RESPONSE from test_data.expected_results import GET_JIRA_ISSUE_RES mocker.patch.object(demisto, "info") mocker.patch.object(demisto, "debug") mocker.patch("JiraV2.run_query", return_value=QUERY_ISSUE_RESPONSE) mocker.patch.object( demisto, "params", return_value={ "fetch_attachments": True, "fetch_comments": True, "id_offset": "17803", "query": "status!=done", }, ) mocker.patch( "JiraV2.get_comments_command", return_value=( "", "", {"comments": [{"updated": "2071-12-21 12:29:05.529000+00:00"}]}, ), ) mocker.patch("JiraV2.get_issue", return_value=("", "", GET_JIRA_ISSUE_RES)) res = fetch_incidents( "status!=done", id_offset=1, should_get_attachments=False, should_get_comments=True, should_mirror_in=False, should_mirror_out=False, comment_tag="", attachment_tag="", ) assert res[0]["attachment"] == [] assert len(res[0]["labels"][12]["value"]) > 0 def test_fetch_incident_with_comments_when_exception_is_raised(mocker): """ Given: - Fetch parameters When - Incidents needs to include comments and there is an exception raised. Then - Returned all fetched incidents without their comments. """ from JiraV2 import fetch_incidents from test_data.raw_response import QUERY_ISSUE_RESPONSE mocker.patch.object(demisto, "info") mocker.patch.object(demisto, "debug") mocker.patch("JiraV2.run_query", return_value=QUERY_ISSUE_RESPONSE) mocker.patch.object( demisto, "params", return_value={ "fetch_attachments": True, "fetch_comments": True, "id_offset": "17803", "query": "status!=done", }, ) mocker.patch( "JiraV2.get_comments_command", return_value=( "", "", {"comments": [{"updated": "2071-12-21 12:29:05.529000+00:00"}]}, ), ) mocker.patch( "JiraV2.get_issue", return_value=TimeoutError, ) res = fetch_incidents( "status!=done", id_offset=1, should_get_attachments=False, should_get_comments=True, should_mirror_in=False, should_mirror_out=False, comment_tag="", attachment_tag="", ) assert res[0]["labels"][12]["value"] == "[]" def test_fetch_incident_mirror_direction(mocker): """ Given: - Fetch parameters When - Incidents needs to include Which direction to mirror its data- test 'Both' in this case. Then - Returned all fetched incidents without 'mirror_direction' set to 'Both'. """ from JiraV2 import fetch_incidents from test_data.raw_response import QUERY_ISSUE_RESPONSE from test_data.expected_results import GET_JIRA_ISSUE_RES import json mocker.patch.object(demisto, "info") mocker.patch.object(demisto, "debug") mocker.patch("JiraV2.run_query", return_value=QUERY_ISSUE_RESPONSE) mocker.patch.object( demisto, "params", return_value={ "fetch_attachments": True, "fetch_comments": True, "id_offset": "17803", "query": "status!=done", }, ) mocker.patch( "JiraV2.get_comments_command", return_value=( "", "", {"comments": [{"updated": "2071-12-21 12:29:05.529000+00:00"}]}, ), ) mocker.patch("JiraV2.get_issue", return_value=("", "", GET_JIRA_ISSUE_RES)) res = fetch_incidents( "status!=done", id_offset=1, should_get_attachments=False, should_get_comments=True, should_mirror_in=True, should_mirror_out=True, comment_tag="", attachment_tag="", ) assert json.loads(res[0]["rawJSON"])["mirror_direction"] == "Both" def test_handle_incoming_closing_incident(mocker): """ Given: - Issue with status 'Done' When - Mirror in Data from Jira to Demisto Then - Returned an object for close its incident """ from JiraV2 import handle_incoming_closing_incident from test_data.expected_results import GET_JIRA_ISSUE_RES mocker.patch.object(demisto, "info") mocker.patch.object(demisto, "debug") GET_JIRA_ISSUE_RES["fields"]["status"]["name"] = "Done" res = handle_incoming_closing_incident(GET_JIRA_ISSUE_RES) assert res["Contents"]["dbotIncidentClose"] is True assert res["Contents"]["closeReason"] == 'Issue was marked as "Done"' def test_get_mirror_type_both(): """ Given: - Mirror out is 'True' and mirror in 'True' When - Need to map mirror_in and mirror_out into 'Both' Then - Returned the correct mirror direction """ from JiraV2 import get_mirror_type res = get_mirror_type(True, True) assert res == "Both" def test_get_mirror_type_out(): """ Given: - Mirror out is 'True' and mirror in 'False' When - Need to map mirror_in and mirror_out input to 'Out' Then - Returned the correct mirror direction """ from JiraV2 import get_mirror_type res = get_mirror_type(False, True) assert res == "Out" def test_get_mirror_type_in(): """ Given: - Mirror out is 'False' and mirror in 'True' When - Need to map mirror_in and mirror_out input to 'In' Then - Returned the correct mirror direction """ from JiraV2 import get_mirror_type res = get_mirror_type(True, False) assert res == "In" def test_get_mirror_type_none(): """ Given: - Mirror out is 'False' and mirror in 'False' When - Need to map mirror_in and mirror_out input to None Then - Returned the correct mirror direction """ from JiraV2 import get_mirror_type res = get_mirror_type(False, False) assert res is None def test_edit_issue_status(mocker): """ Given: - New status for an issue When - Need to change a status of an issue Then - An issue has a new status """ from JiraV2 import ( edit_issue_command, edit_status ) mocker.patch("JiraV2.jira_req", return_value=None) mocker.patch("JiraV2.get_issue_fields", return_value=None) mocker.patch("JiraV2.get_issue", return_value=True) mocker.patch( "JiraV2.list_transitions_data_for_issue", return_value={"transitions": [{"name": "To Do", "id": 1, "to": { "self": "https: //demistodev.atlassian.net/rest/api/2/status/10000", "description": "", "iconUrl": "https://demistodev.atlassian.net/images/icons/status_generic.gif", "name": "To Do", "id": "10000", "statusCategory": { "self": "https://demistodev.atlassian.net/rest/api/2/statuscategory/2", "id": 2, "key": "new", "colorName": "blue-gray", "name": "To Do" } }}]}, ) mocked_return_error = mocker.patch("JiraV2.return_error", return_value=None) mocked_edit_transition = mocker.patch("JiraV2.edit_transition", return_value=None) mocked_edit_status = mocker.patch("JiraV2.edit_status", side_effect=edit_status) res = edit_issue_command("1234", status="To Do") assert mocked_return_error.call_count == 0 assert mocked_edit_status.call_count == 1 assert mocked_edit_transition.call_count == 0 assert res is True def test_edit_issue_transition(mocker): """ Given: - New transition for an issue When - Need to change transition type in order to change the issue's status Then - An issue has a new transition """ from JiraV2 import ( edit_issue_command, edit_transition, ) mocker.patch("JiraV2.jira_req", return_value=None) mocker.patch("JiraV2.get_issue_fields", return_value=None) mocker.patch("JiraV2.get_issue", return_value=True) mocker.patch( "JiraV2.list_transitions_data_for_issue", return_value={"transitions": [{"name": "To Do", "id": 1}]}, ) mocked_return_error = mocker.patch("JiraV2.return_error", return_value=None) mocked_edit_transition = mocker.patch( "JiraV2.edit_transition", side_effect=edit_transition ) mocked_edit_status = mocker.patch("JiraV2.edit_status", return_value=None) res = edit_issue_command("1234", transition="To Do") assert mocked_return_error.call_count == 0 assert mocked_edit_status.call_count == 0 assert mocked_edit_transition.call_count == 1 assert res is True def test_edit_issue_when_passing_both_transition_and_status(mocker): """ Given: - Transition and status for an issue When - A user passes to edit_issue command both parameters Then - Error is being returned saying both parameters can't be passed """ from JiraV2 import edit_issue_command mocker.patch("JiraV2.jira_req", return_value=None) mocker.patch("JiraV2.get_issue_fields", return_value=None) mocker.patch("JiraV2.get_issue", return_value=True) mocker.patch( "JiraV2.list_transitions_data_for_issue", return_value={"transitions": [{"name": "To Do", "id": 1}]}, ) mocked_return_error = mocker.patch("JiraV2.return_error", return_value=None) mocked_edit_transition = mocker.patch("JiraV2.edit_transition", return_value=None) mocked_edit_status = mocker.patch("JiraV2.edit_status", return_value=None) edit_issue_command("1234", transition="To Do", status="To Do") assert mocked_return_error.call_count == 1 assert mocked_edit_status.call_count == 0 assert mocked_edit_transition.call_count == 0 def test_list_transitions_command(mocker): """ Given: - issueId When - Need to get a list of all possible transitions Then - Returns a list of transitions """ from JiraV2 import list_transitions_command mocker.patch( "JiraV2.list_transitions_data_for_issue", return_value={"transitions": [{"name": "To Do", "id": 1}]}, ) res = list_transitions_command({"issueId": "123"}) assert res.outputs_key_field == "ticketId" assert res.raw_response == ["To Do"] assert res.outputs == {"ticketId": "123", "transitions": ["To Do"]} def test_get_modified_data_command(mocker): """ Given: - Date string represents the last time we retrieved modified incidents for this integration. When - Before running get_remote_data_command Then - Returns a list of changed incidents """ from JiraV2 import get_modified_remote_data_command mocker.patch.object(demisto, "debug") mocker.patch.object(demisto, "info") mocker.patch("JiraV2.json", return_value={"timeZone": "Asia/Jerusalem"}) class Res: def __init__(self): self.status_code = 200 def json(self): return {"timeZone": "Asia/Jerusalem"} response = Res() response.json() mocker.patch("JiraV2.get_user_info_data", return_value=response) mocker.patch( "JiraV2.issue_query_command", return_value=(None, None, {"issues": [{"id": "123"}]}), ) modified_ids = get_modified_remote_data_command({"lastUpdate": "1"}) assert modified_ids.modified_incident_ids == ["123"] def test_get_modified_data_command_when_getting_exception_for_get_user_info_data( mocker, ): """ Given: - Date string represents the last time we retrieved modified incidents for this integration. When - Having an error in get_user_info_data function. Then - An error is printed via demisto.error and returning an empty modified_incident_ids list """ from JiraV2 import get_modified_remote_data_command mocker.patch.object(demisto, "debug") mocker.patch.object(demisto, "info") mocked_demisto_error = mocker.patch.object(demisto, "error") mocker.patch( "JiraV2.get_user_info_data", side_effect=Mock(side_effect=Exception("Test")) ) modified_ids = get_modified_remote_data_command({"lastUpdate": "0"}) assert mocked_demisto_error.call_count == 1 assert modified_ids.modified_incident_ids == [] def test_get_modified_data_command_when_getting_not_ok_status_code_for_get_user_info_data( mocker, ): """ Given: - Date string represents the last time we retrieved modified incidents for this integration. When - Getting '404' from get_user_info_data function. Then - An error is printed via demisto.error and returning an empty modified_incident_ids list """ from JiraV2 import get_modified_remote_data_command mocker.patch.object(demisto, "debug") mocker.patch.object(demisto, "info") mocker.patch("JiraV2.json", return_value={"timeZone": "Asia/Jerusalem"}) class res: def __init__(self): self.status_code = 404 self.reason = "Not Found" response = res() mocker.patch("JiraV2.get_user_info_data", return_value=response) mocked_demisto_error = mocker.patch.object(demisto, "error") modified_ids = get_modified_remote_data_command({"lastUpdate": "0"}) assert mocked_demisto_error.call_count == 1 assert modified_ids.modified_incident_ids == [] def test_get_comments_command(mocker): """ Given: - IssueId When - Running get_comments_command in order to get an issue's comments. Then - Returns a list of comments for the given issue. """ from JiraV2 import get_comments_command comments = { "comments": [ { "updated": "2071-12-21 12:29:05.529000+00:00", "body": "comment text", "updateAuthor": {"name": "Test"}, "created": "10.12", } ] } mocker.patch("JiraV2.jira_req", return_value=comments) _, outputs, context = get_comments_command(123) assert list(outputs.keys())[0] == "Ticket(val.Id == obj.Id)" assert outputs["Ticket(val.Id == obj.Id)"]["Id"] == 123 assert (outputs["Ticket(val.Id == obj.Id)"]["Comment"][0]["Comment"] == "comment text") assert outputs["Ticket(val.Id == obj.Id)"]["Comment"][0]["User"] == "Test" assert outputs["Ticket(val.Id == obj.Id)"]["Comment"][0]["Created"] == "10.12" assert context == comments def test_get_issue_fields_issue_json_param(): """ Given: - issue_json param When - editing an issue using 'jira-edit-issue' command Then - json as dict """ from JiraV2 import get_issue_fields res = get_issue_fields(issue_json='{"description": "test"}') assert {'description': 'test', 'fields': {}} == res def test_get_issue_fields_issuejson_param(): """ Given: - issueJson param When - Creating a new issue using 'jira-create-issue' command Then - json as dict """ from JiraV2 import get_issue_fields res = get_issue_fields(issueJson='{"description": "test"}') assert {'description': 'test', 'fields': {}} == res def test_get_issue_fields(): from JiraV2 import get_issue_fields issue_fields = get_issue_fields(False, False, **{"components": "Test, Test 1", "security": "Anyone", "environment": "Test"}) assert issue_fields == {'fields': {'components': [{'name': 'Test'}, {'name': 'Test 1'}], 'environment': 'Test', 'security': {'name': 'Anyone'}}} @pytest.mark.parametrize('get_attachments_arg, should_get_attachments', [ ('true', True), ('false', False) ]) def test_get_issue_and_attachments(mocker, get_attachments_arg, should_get_attachments): """ Given: - Case A: That the user has set the get_attachments to 'true' as he wants to download attachments - Case B: That the user has set the get_attachments to 'false' as he does not want to download attachments When - Calling the get issue command Then - Ensure the demisto.results with file data is called - Ensure the demisto.results with file data is not called """ from test_data.raw_response import GET_ISSUE_RESPONSE from JiraV2 import get_issue from requests import Response def jira_req_mock(method: str, resource_url: str, body: str = '', link: bool = False, resp_type: str = 'text', headers: dict = None, files: dict = None): response = Response() response.status_code = 200 response._content = b'{"filename": "filename"}' if resource_url == 'rest/attachment/15451': return response elif resp_type == 'json': return GET_ISSUE_RESPONSE else: return type("RequestObjectNock", (OptionParser, object), {"content": 'Some zip data'}) mocker.patch("JiraV2.jira_req", side_effect=jira_req_mock) demisto_results_mocker = mocker.patch.object(demisto, 'results') get_issue('id', get_attachments=get_attachments_arg) if should_get_attachments: demisto_results_mocker.assert_called_once() else: demisto_results_mocker.assert_not_called() OAUTH1 = { 'url': 'example.com', 'consumerKey': 'example_key', 'accessToken': 'example_token', 'privateKey': 'example_private_key', 'username': '' } PAT = {'url': 'example.com', 'username': '', 'accessToken': 'example_token'} BASIC = {'url': 'example.com', 'username': 'example_user', 'APItoken': 'example_token'} AUTH_CASES = [ (OAUTH1, {}, {'Content-Type': 'application/json', 'X-Atlassian-Token': 'nocheck'}), (OAUTH1, {'X-Atlassian-Token': 'nocheck'}, {'X-Atlassian-Token': 'nocheck'}), (PAT, {}, {'Content-Type': 'application/json', 'Authorization': 'Bearer example_token'}), (PAT, {'X-Atlassian-Token': 'nocheck'}, {'X-Atlassian-Token': 'nocheck', 'Authorization': 'Bearer example_token'}), (BASIC, {}, {'Content-Type': 'application/json'}), (BASIC, {'X-Atlassian-Token': 'nocheck'}, {'X-Atlassian-Token': 'nocheck'}), ] def test_get_project_id_old_version(requests_mock): """ Given: - Jira api version less than 9.0.0. When - Running the create issue command. Then - Ensure only the original api endpoint is being used. """ from JiraV2 import get_project_id first_case_mock = requests_mock.get('https://localhost/rest/api/latest/issue/createmeta', status_code=200, json={"projects": [{"name": "Test_name", "key": "Test_key", "id": "Test_id"}]}) second_case_mock = requests_mock.get('https://localhost/rest/api/latest/project', status_code=200) id = get_project_id(project_name='Test_name') assert id == 'Test_id' assert first_case_mock.called_once and not second_case_mock.called def test_get_project_id(mocker): """ Given: - Jira api version greater or equal to 9.0.0. When - Running the create issue command. Then - Ensure only the new api endpoint is being used. """ from JiraV2 import get_project_id def mock_res(method, endpoint, resp_type): if endpoint == 'rest/api/latest/issue/createmeta': raise DemistoException("Status code: 404\nMessage: Issue Does Not Exist") elif endpoint == 'rest/api/latest/project': return [{"name": "Test_name", "key": "Test_key", "id": "Test_id"}] mocker.patch('JiraV2.jira_req', side_effect=mock_res) id = get_project_id(project_name='Test_name') assert id == 'Test_id' def test_get_project_id_non_english(mocker): """ Given: - Exception (404) contains non english content. When - Running the create issue command. Then - Ensure the new api endpoint is being used. """ from JiraV2 import get_project_id def mock_res(method, endpoint, resp_type): if endpoint == 'rest/api/latest/issue/createmeta': raise DemistoException("Status code: 404\nMessage: La Incidencia no Existe") elif endpoint == 'rest/api/latest/project': return [{"name": "Test_name", "key": "Test_key", "id": "Test_id"}] mocker.patch('JiraV2.jira_req', side_effect=mock_res) id = get_project_id(project_name='Test_name') assert id == 'Test_id' @pytest.mark.parametrize('params, custom_headers, expected_headers', AUTH_CASES) def test_jira_req(mocker, requests_mock, params, custom_headers, expected_headers): """ Given: - Case OAuth authentication: The user is using the default headers for a command - Case OAuth authentication: The user is using custom headers for a command - Case PAT authentication: The user is using the default headers for a command - Case PAT authentication: The user is using custom headers for a command - Case BASIC authentication: The user is using the default headers for a command - Case BASIC authentication: The user is using custom headers for a command When - Running any command, trying to make a request to Jira while using specific authentication. Then - Ensure the authentication headers are correct when using custom headers - Ensure the authentication headers are correct when using default headers """ import JiraV2 class ResponseDummy(): def __init__(self): self.ok = 1 req_mock = mocker.patch.object(JiraV2.SESSION, 'request', return_value=ResponseDummy()) JiraV2.USERNAME = params.get('username') JiraV2.HEADERS = {'Content-Type': 'application/json'} mocker.patch.object(demisto, "params", return_value=params) JiraV2.jira_req(method='get', resource_url=params.get('url'), headers=custom_headers) assert expected_headers == req_mock.call_args[1]['headers'] def test_get_issue_outputs(mocker): """ Given: - The issue ID. When - Running the get issue command. Then - Ensure the outputs as expected """ from test_data.raw_response import GET_ISSUE_RESPONSE from test_data.expected_results import GET_ISSUE_OUTPUTS_RESULT from JiraV2 import get_issue mocker.patch('JiraV2.jira_req', return_value=GET_ISSUE_RESPONSE) _, outputs, _ = get_issue('id') assert outputs == GET_ISSUE_OUTPUTS_RESULT def test_get_custom_field_names(mocker, requests_mock): from JiraV2 import get_custom_field_names from test_data.raw_response import FIELDS_RESPONSE, EXPECTED_RESP mocker.patch.object(demisto, "info") mocker.patch.object(demisto, "debug") requests_mock.get('https://localhost/rest/api/latest/field', json=FIELDS_RESPONSE) res = get_custom_field_names() assert res == EXPECTED_RESP def test_get_attachment_data_request(mocker, requests_mock): """ Given: - An attachment data. When - Running the get_attachment_data command. Then - Ensure the command does not fail due to a wrong url. """ from JiraV2 import get_attachment_data from test_data.raw_response import ATTACHMENTS mocker.patch.object(demisto, "params", return_value=integration_params) requests_mock.get('https://localhost/rest/api/2/attachment/content/16188', json={}) assert get_attachment_data(ATTACHMENTS['cloud_attachment']), 'There was a request to the wrong url' @pytest.mark.parametrize('attachment_to_extract,expected_link', [ ('cloud_attachment', '/rest/api/2/attachment/content/16188'), ('on_prem_attachment', '/secure/attachment/18447/filename')]) def test_get_attachment_data_url_processing(mocker, requests_mock, attachment_to_extract, expected_link): """ Given: - Case a: An attachment data from jira cloud instance. - Case b: An attachment data from jira on prem instance. When - Running the get_attachment_data command. Then - Ensure the filename output is correct, and the req_path correspond to the right type of system. """ from JiraV2 import get_attachment_data from test_data.raw_response import ATTACHMENTS attachment = ATTACHMENTS[attachment_to_extract] url_to_mock = attachment.get('content') request = requests_mock.get(url_to_mock, json={}) mocker.patch.object(demisto, "params", return_value=integration_params) filename, _ = get_attachment_data(attachment) assert filename == 'filename' assert request.last_request.path == expected_link attribute_mock_response_email_exists = [ {'self': 'https://test.atlassian.net', 'accountId': 'TEST-ID', 'accountType': 'atlassian', 'emailAddress': 'some_email@mail.com', 'avatarUrls': {}, 'displayName': 'some user', 'active': True, 'timeZone': 'Asia', 'locale': 'en_US' }] attribute_mock_response_no_email = [ {'self': 'https://test.atlassian.net', 'accountId': 'TEST-ID', 'accountType': 'atlassian', 'emailAddress': '', 'avatarUrls': {}, 'displayName': 'some user', 'active': True, 'timeZone': 'Asia', 'locale': 'en_US' }] attribute_mock_response_no_email_multiple = [ {'self': 'https://test1.atlassian.net', 'accountId': 'TEST-ID1', 'accountType': 'atlassian', 'emailAddress': '', 'avatarUrls': {}, 'displayName': 'some user1', 'active': True, 'timeZone': 'Asia', 'locale': 'en_US' }, {'self': 'https://test2.atlassian.net', 'accountId': 'TEST-ID2', 'accountType': 'atlassian', 'emailAddress': '', 'avatarUrls': {}, 'displayName': 'some user2', 'active': True, 'timeZone': 'Asia', 'locale': 'en_US' } ] @pytest.mark.parametrize('mock_response, expected_output', [(attribute_mock_response_email_exists, 'TEST-ID'), (attribute_mock_response_no_email, 'TEST-ID'), (attribute_mock_response_no_email_multiple, 'Multiple account IDs found')]) def test_get_account_id_from_attribute_valid_attribute_match(mocker, mock_response, expected_output): """ Given: - An email attribute. When - Running the get_account_id_from_attribute command when: 1. email matches the email in the response. 2. email in the response is hidden but there is only one option. 3. email in the response is hidden and there are multiple options. Then - Ensure the attribute was found and the output is correct. """ from JiraV2 import get_account_id_from_attribute mocker.patch('JiraV2.search_user', return_value=mock_response) mocker.patch.object(demisto, "params", return_value=integration_params) res = get_account_id_from_attribute(attribute='some_email@mail.com') if len(mock_response) == 2: # case number three assert expected_output in res else: assert expected_output == res.outputs['AccountID'] def test_get_account_id_from_attribute_attribute_do_not_match(mocker): """ Given: - An email attribute. When - Running the get_account_id_from_attribute command. Then - Ensure the attribute was found but no match for the email. """ from JiraV2 import get_account_id_from_attribute mock_response = [ {'self': 'https://test.atlassian.net', 'accountId': 'TEST-ID', 'accountType': 'atlassian', 'emailAddress': '', 'avatarUrls': {}, 'displayName': 'some user', 'active': True, 'timeZone': 'Asia', 'locale': 'en_US' }] mocker.patch('JiraV2.search_user', return_value=mock_response) mocker.patch.object(demisto, "params", return_value=integration_params) res = get_account_id_from_attribute(attribute='some_email@mail.com') assert res.outputs['AccountID'] == 'TEST-ID' def test_append_to_empty_field_command(mocker): """ Given: - The issue ID, a json of field and new values When - Running the append_to_field_command Then - Ensure appending is working as excpected """ from test_data.raw_response import GET_ISSUE_RESPONSE from JiraV2 import append_to_field_command mocker.patch('JiraV2.jira_req', return_value=GET_ISSUE_RESPONSE) mocker.patch('JiraV2.__get_field_type', return_value='string') mock_update = mocker.patch('JiraV2._update_fields') _, outputs, _ = append_to_field_command('id', field_json='{"labels":"New"}') mock_update.assert_called_with('id', {'labels': 'New'}) def test_append_to_existing_field_command(mocker): """ Given: - The issue ID, a json of field and new values When - Running the append_to_field_command Then - Ensure appending is working as excpected """ from test_data.raw_response import GET_ISSUE_RESPONSE_WITH_LABELS from JiraV2 import append_to_field_command mocker.patch('JiraV2.jira_req', return_value=GET_ISSUE_RESPONSE_WITH_LABELS) mocker.patch('JiraV2.__get_field_type', return_value='array') mock_update = mocker.patch('JiraV2._update_fields') _, outputs, _ = append_to_field_command('id', field_json='{"labels":"New"}') mock_update.assert_called_with('id', {'labels': ['test', 'New']}) @pytest.mark.parametrize( 'assignee, assignee_id, excpected_body_request', [ ("server_assignee", None, '{"name": "server_assignee"}'), (None, "cloud_assignee", '{"accountId": "cloud_assignee"}') ] ) def test_update_issue_assignee_command(mocker, assignee, assignee_id, excpected_body_request): """ Given: - issue id, and assignees for cloud/server jira When - Running the update_issue_assignee_command Then - Ensure the body request is ok for both cloud/server jira """ from test_data.raw_response import GET_ISSUE_RESPONSE from JiraV2 import update_issue_assignee_command jira_req_mocker = mocker.patch('JiraV2.jira_req', side_effect=['', GET_ISSUE_RESPONSE]) assert update_issue_assignee_command(issue_id='19141', assignee=assignee, assignee_id=assignee_id) assert jira_req_mocker.call_args_list[0].args[2] == excpected_body_request def test_test_update_issue_assignee_command_no_assignees(): """ Given: - issue id, without assignee / assignee_id When - Running the update_issue_assignee_command Then - Ensure an exception is raised """ from JiraV2 import update_issue_assignee_command with pytest.raises(DemistoException): update_issue_assignee_command(issue_id='19141', assignee=None, assignee_id=None) def test_get_organizations(mocker): from test_data.raw_response import ORGANIZATIONS from JiraV2 import get_organizations_command mocker.patch.object(demisto, "results") organizations = [ { "id": "23", "name": "TEST" }, { "id": "4", "name": "XSOAR" } ] mocker.patch("JiraV2.jira_req", return_value=ORGANIZATIONS) result = get_organizations_command() assert result.raw_response == organizations def test_get_project_role(mocker): from test_data.raw_response import PROJECT_ROLES, PROJECT_ROLE from JiraV2 import get_project_role_command mocker.patch("JiraV2.get_project_roles", return_value=PROJECT_ROLES) mocker.patch("JiraV2.jira_req", return_value=PROJECT_ROLE) role = get_project_role_command("demisto", "Agent") assert role.get('id') == 11823 def test_get_project_roles(mocker): from test_data.raw_response import PROJECT_ROLES from JiraV2 import get_project_roles mocker.patch("JiraV2.jira_req", return_value=PROJECT_ROLES) roles = get_project_roles("demisto") assert len(roles) == 2