Redmine
A project management and issue tracking system that provides a web-based platform for managing projects, tracking tasks, and handling various types of project-related activities.
Utilities · Redmine
Details
| ID | Redmine |
|---|---|
| Provider | Open Source |
| Category | Utilities |
| From Version | 6.10.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
A project management and issue tracking system that provides a web-based platform for managing projects, tracking tasks, and handling various types of project-related activities.
This integration was integrated and tested with version 5.1.2 of Redmine.
Configure Redmine on Cortex XSOAR
- Navigate to Settings > Integrations > Servers & Services.
- Search for Redmine.
-
Click Add instance to create and configure a new integration instance.
Parameter Required Server URL (e.g., https://1.1.1.1) True Trust any certificate (not secure) False API Key True Project id False - Getting your API key:
- Use your server URL to enter your Redmine instance.
- Authenticate with your username and password.
- Navigate to My Account (at the top right corner).
- Click API Access key > Show - This is your API key
- Click Test to validate the URLs, token, and connection.
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.
redmine-issue-create
- Create a new issue.
- The status of the newly created issue will be set to the default status of the Redmine instance.
- When attaching a file to an issue, include the entry ID in the request as file_entry_id=entry ID.
- To create a custom field, navigate to the server URL with administrative privileges, click Administration (located at the top left), select Custom fields, and then proceed to create a new custom field. Once created, you can add values as needed
- To create a category/version, navigate to the server URL > click Settings (top bar) > Versions tab and Issue categories tab.
Base Command
redmine-issue-create
Input
| Argument Name | Description | Required |
|---|---|---|
| project_id | The project ID for this issue. If not specified, the value from the instance configuration will be used. | Optional |
| tracker_id | - A tracker ID (e.g., 1,2,3). - Get the tracker_name to tracker_id mapping by using the command redmine-tracker-id-list. - If an invalid ID was entered, the default tracker ID set in the Redmine instance wil be used. |
Required |
| priority_id | - A priority ID (e.g., 1,2,3). - Get the priority_name to priority_id mapping by using the command redmine-priority-id-list. |
Required |
| subject | The subject for this issue. | Required |
| description | A description for this issue. | Optional |
| category_id | The category ID for this issue. | Optional |
| fixed_version_id | The target version ID for this issue. | Optional |
| assigned_to_id | The ID of the user to assign the issue to. | Optional |
| parent_issue_id | The ID of the parent issue. | Optional |
| custom_fields | The custom field to update. - “The format is {\"customFieldID2\": \"value3\", \"customFieldID1\": [\"value1\",\"value2\"]}.”- Use an array if the field is of multiselect type. - Instruction for each custom field type: - Boolean: use “0” for False and “1” for True. - Date: the format is “yyyy-mm-dd”. - Float: A number with two digits after the decimal point. - Integer: A number. - Key/Value list: Use the Key (ID) of the value. - User: Use the user ID. - Version- use the version ID. |
Optional |
| watcher_user_ids | An array with watcher user IDs for this issue -> 1,2,3. | Optional |
| is_private | Is the issue private?. Possible values are: True, False. | Optional |
| estimated_hours | The number of hours estimated for this issue. | Optional |
| file_entry_id | The entry ID of the file to upload. | Optional |
| file_name | The name of the file to attach. Make sure the file name ends with .jpg/png/txt. | Optional |
| file_description | The description of the file you attached. | Optional |
| file_content_type | The file content type of the file you attached. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Redmine.Issue.id | srt | The ID of the new issue. |
| Redmine.Issue.priority.id | str | The ID of the priority of the issue. |
| Redmine.Issue.tracker.id | str | The ID of the tracker of the issue. |
| Redmine.Issue.project.id | str | The ID of the project of the issue. |
| Redmine.Issue.status.id | str | The ID of the status of the issue. |
| Redmine.Issue.subject | str | The subject of the issue. |
Command example
!redmine-issue-create priority_id=High subject=helloExample tracker_id=1 project_id=1 watcher_user_ids=5,6 custom_fields=1:helloCustom priority_id=1
Context Example
{
"Redmine": {
"Issue": {
"author": {
"id": 6,
"name": "Integration Test"
},
"closed_on": null,
"created_on": "2024-04-07T11:59:34Z",
"custom_fields": [
{
"id": 1,
"name": "Team_of_workers",
"value": "helloCustom"
}
],
"description": null,
"done_ratio": 0,
"due_date": null,
"estimated_hours": null,
"id": "167",
"is_private": false,
"priority": {
"id": 1,
"name": "Low"
},
"project": {
"id": 1,
"name": "Cortex XSOAR"
},
"start_date": "2024-04-07",
"status": {
"id": 1,
"is_closed": false,
"name": "New"
},
"subject": "helloExample",
"total_estimated_hours": null,
"tracker": {
"id": 1,
"name": "Bug"
},
"updated_on": "2024-04-07T11:59:34Z"
}
}
}
Human Readable Output
The issue you created
Id Project Tracker Status Priority Author Created On Subject Start Date Custom Fields 167 Cortex XSOAR Bug New Low Integration Test 2024-04-07T11:59:34Z helloExample 2024-04-07 - name: Team_of_workers
value: helloCustom
redmine-issue-list
Display a list of issues
Base Command
redmine-issue-list
Input
| Argument Name | Description | Required |
|---|---|---|
| page_number | The page number. | Optional |
| page_size | The page size. Default value is 50, if limit is not specified. | Optional |
| limit | The number of issues to display in the response (maximum is 100). If page_number or page_size are specified, this field will be ignored. Default is 25. | Optional |
| sort | A field by which to sort the results. Append “:desc” to invert the order. - Possible values: 1. tracker. 2. status. 3. priority. 4. project. 5. subproject. 6. assigned_to. - For example: sort=tracker:desc. |
Optional |
| include | An array of extra fields to fetch. - Possible values: 1. attachments. 2. relations. |
Optional |
| issue_id | An array of issue IDs to display -> 1,2,3. | Optional |
| project_id | Aa project ID to display issues of this project. If not specified here or in the instance configuration, all projects will be displayed. | Optional |
| subproject_id | A subproject ID to display issues of this subproject (use “project_id=someID” and “subproject_id=!name_of_subproject” to exclude subprojects). | Optional |
| tracker_id | - A tracker ID (e.g., 1,2,3). - Get the tracker_name to tracker_id mapping by using the command redmine-tracker-id-list. |
Optional |
| status | - A status ID (e.g., 1,2,3) or use predefined values. - Possible values: Open/Closed/All from the predefined values or use any of your custom status ids (e.g., 1). |
Optional |
| assigned_to_id | An assigned-to ID to display issues assigned to this user ID. | Optional |
| parent_id | A parent ID to display issues that are under this parent ID. | Optional |
| custom_field | - The custom field to filter by. The format is costumFieldID:Value. - To filter according to the desired custom field, ensure that it is marked as ‘used as a filter’ and ‘searchable’ in your Redmine server settings. - You can only filter one custom field at a time. - Make sure the custom field ID which was entered is valid, or the request won’t fail but will not be filtered correctly. |
Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Redmine.Issue | dict | A list of issues. |
| Redmine.Issue.id | str | A list of issues. |
Command example
!redmine-issue-list limit=2
Context Example
{
"Redmine": {
"Issue": [
{
"author": {
"id": 6,
"name": "Integration Test"
},
"closed_on": null,
"created_on": "2024-03-11T09:16:47Z",
"custom_fields": [
{
"id": 1,
"name": "Team_of_workers",
"value": "helloCustom"
}
],
"description": null,
"done_ratio": 0,
"due_date": null,
"estimated_hours": null,
"id": "130",
"is_private": false,
"priority": {
"id": 3,
"name": "High"
},
"project": {
"id": 1,
"name": "Cortex XSOAR"
},
"spent_hours": 0,
"start_date": "2024-03-11",
"status": {
"id": 1,
"is_closed": false,
"name": "New"
},
"subject": "subjectChanged",
"total_estimated_hours": null,
"total_spent_hours": 0,
"tracker": {
"id": 1,
"name": "Bug"
},
"updated_on": "2024-03-11T09:16:54Z"
},
{
"author": {
"id": 6,
"name": "Integration Test"
},
"closed_on": null,
"created_on": "2024-03-11T09:08:09Z",
"custom_fields": [
{
"id": 1,
"name": "Team_of_workers",
"value": "helloCustom"
}
],
"description": null,
"done_ratio": 0,
"due_date": null,
"estimated_hours": null,
"id": "129",
"is_private": false,
"priority": {
"id": 3,
"name": "High"
},
"project": {
"id": 1,
"name": "Cortex XSOAR"
},
"spent_hours": 0,
"start_date": "2024-03-11",
"status": {
"id": 1,
"is_closed": false,
"name": "New"
},
"subject": "helloExample",
"total_estimated_hours": null,
"total_spent_hours": 0,
"tracker": {
"id": 1,
"name": "Bug"
},
"updated_on": "2024-03-11T09:08:09Z"
}
]
}
}
Human Readable Output
Showing 2 results from page 1
Issues Results
ID Tracker Status Priority Author Subject Start Date done_ratio Is Private Custom Fields Created On updated_on 130 Bug New High Integration Test subjectChanged 2024-03-11 0 false - name: Team_of_workers
value: helloCustom2024-03-11T09:16:47Z 2024-03-11T09:16:54Z 129 Bug New High Integration Test helloExample 2024-03-11 0 false - name: Team_of_workers
value: helloCustom2024-03-11T09:08:09Z 2024-03-11T09:08:09Z
redmine-issue-update
Update an existing issue. When attaching a file to an issue, include the entry ID in the request as file_entry_id=entry ID. To create a custom field, navigate to the server URL with administrative privileges, click ‘Administration (located at the top left), select Custom fields, and proceed to create a new custom field. Once created, you can add values as needed.
Base Command
redmine-issue-update
Input
| Argument Name | Description | Required |
|---|---|---|
| issue_id | The ID of the issue to be updated. | Required |
| project_id | The ID of the project to associate with the issue. If not specified, the value from integration configuration will be taken if specified. | Optional |
| tracker_id | - A tracker ID (e.g., 1,2,3). - Get the tracker_name to tracker_id mapping by using the command redmine-tracker-id-list. - If an invalid ID was entered, tracker_id won’t be changed and won’t raise an error. |
Optional |
| priority_id | - A priority ID (e.g., 1,2,3). - Get the priority_name to priority_id mapping by using the command redmine-priority-id-list. |
Optional |
| status | - A status ID (e.g., 1,2,3). - Get the status_name to status_id mapping by using the command redmine-status-id-list. - If an invalid ID was entered, status_id won’t be changed and won’t raise an error. |
Optional |
| subject | The subject of the issue. | Optional |
| description | The description of the issue. | Optional |
| category_id | The ID of the category to assign to the issue. | Optional |
| fixed_version_id | The ID of the fixed version for the issue. | Optional |
| assigned_to_id | The ID of the user to whom the issue is assigned. | Optional |
| parent_issue_id | The ID of the parent issue, if applicable. | Optional |
| custom_fields | The custom field to update. - “The format is {\"customFieldID2\": \"value3\", \"customFieldID1\": [\"value1\",\"value2\"]}.”- Use an array if the field is of multiselect type. - Instruction for each custom field type: - Boolean: use “0” for False and “1” for True. - Date: the format is “yyyy-mm-dd”. - Float: A number with two digits after the decimal point. - Integer: A number. - Key/Value list: Use the Key (ID) of the value. - User: Use the user ID. - Version- use the version ID. |
Optional |
| watcher_user_ids | A comma-separated list of watcher IDs. -> 1,2,3. | Optional |
| is_private | Is the issue private?. Possible values are: True, False. | Optional |
| estimated_hours | The estimated number of hours to complete the issue. | Optional |
| notes | Additional comments about the update. | Optional |
| private_notes | Specifies if the notes are private. Possible values are: True, False. | Optional |
| file_entry_id | The entry ID of the file to upload. Required if uploading a file. | Optional |
| file_name | The name of the file to upload (should end with .jpg/.png/.txt, etc.). | Optional |
| file_description | The description of the attached file. | Optional |
| file_content_type | The content type of the attached file (image/jpg or image/png or text/txt, etc.). | Optional |
Context Output
There is no context output for this command.
Command example
!redmine-issue-update issue_id=130 subject=subjectChanged
Human Readable Output
Issue with id 130 was successfully updated.
redmine-issue-get
Show an issue by id
Base Command
redmine-issue-get
Input
| Argument Name | Description | Required |
|---|---|---|
| issue_id | The ID of the wanted issue. | Required |
| include | - Fields to add to the response. - Possible values: 1.children. 2.attachments. 3.relations. 4.changesets. 5.journals. 6.watchers. 7.allowed_statuses. - Separate multiple values with comma ONLY. |
Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Redmine.Issue.id | str | The ID of the found issue. |
| Redmine.Issue.priority.id | str | The ID of the priority of the issue. |
| Redmine.Issue.tracker.id | str | The ID of the tracker of the issue. |
| Redmine.Issue.project.id | str | The ID of the project of the issue. |
| Redmine.Issue.status.id | str | The ID of the status of the issue. |
| Redmine.Issue.subject | str | The subject of the issue. |
| Redmine.Issue.watchers.id | str | The watchers of the issue. |
Command example
!redmine-issue-get issue_id=130 include=watchers
Context Example
{
"Redmine": {
"Issue": {
"author": {
"id": 6,
"name": "Integration Test"
},
"closed_on": null,
"created_on": "2024-03-11T09:16:47Z",
"custom_fields": [
{
"id": 1,
"name": "Team_of_workers",
"value": "helloCustom"
}
],
"description": null,
"done_ratio": 0,
"due_date": null,
"estimated_hours": null,
"id": "130",
"is_private": false,
"priority": {
"id": 3,
"name": "High"
},
"project": {
"id": 1,
"name": "Cortex XSOAR"
},
"spent_hours": 0,
"start_date": "2024-03-11",
"status": {
"id": 1,
"is_closed": false,
"name": "New"
},
"subject": "subjectChanged",
"total_estimated_hours": null,
"total_spent_hours": 0,
"tracker": {
"id": 1,
"name": "Bug"
},
"updated_on": "2024-03-11T09:16:54Z",
"watchers": [
{
"id": 5,
"name": "admin tests"
},
{
"id": 6,
"name": "Integration Test"
}
]
}
}
}
Human Readable Output
Issues List
Id Project Tracker Status Priority Author Subject StartDate DoneRatio IsPrivate CustomFields CreatedOn Watchers 130 Cortex XSOAR Bug New High Integration Test subjectChanged 2024-03-11 0 false - name: Team_of_workers
value: helloCustom2024-03-11T09:16:47Z - name: admin tests
- name: Integration Test
redmine-issue-delete
Delete an issue by its ID
Base Command
redmine-issue-delete
Input
| Argument Name | Description | Required |
|---|---|---|
| issue_id | The ID of the about to be deleted issue. | Required |
Context Output
There is no context output for this command.
Command example
!redmine-issue-delete issue_id=130
Human Readable Output
Issue with id 130 was deleted successfully.
redmine-issue-watcher-add
Add a watcher to the specified issue
Base Command
redmine-issue-watcher-add
Input
| Argument Name | Description | Required |
|---|---|---|
| issue_id | The ID of the issue to which you want to add a watcher. | Required |
| watcher_id | The ID of the watcher you want to add to the issue. | Required |
Context Output
There is no context output for this command.
Command example
!redmine-issue-watcher-add issue_id=130 watcher_id=1
Human Readable Output
Watcher with id 1 was added successfully to issue with id 130.
redmine-issue-watcher-remove
Remove a watcher of an issue
Base Command
redmine-issue-watcher-remove
Input
| Argument Name | Description | Required |
|---|---|---|
| issue_id | The ID of the issue from which you want to remove the watcher. | Required |
| watcher_id | The ID of the watcher you want to remove from the issue. | Required |
Context Output
There is no context output for this command.
Command example
!redmine-issue-watcher-remove issue_id=130 watcher_id=1
Human Readable Output
Watcher with id 1 was removed successfully from issue with id 130.
redmine-project-list
Retrieve a list of all projects, including both public and private ones that the user has access to.
Base Command
redmine-project-list
Input
| Argument Name | Description | Required |
|---|---|---|
| include | - Specify which additional fields to include in the response. - Choose from the following options: 1. trackers. 2. issue_categories 3. enabled_modules 4. time_entry_activities 5. issue_custom_fields - Separate multiple values with comma ONLY. |
Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Redmine.Project | Array | Display a list of projects accessible to the user. |
Command example
#### Context Example
```json
{
"Redmine": {
"Project": {
"created_on": "2024-02-29T10:34:23Z",
"custom_fields": [
{
"id": 3,
"name": "second_custom_field",
"value": null
}
],
"description": "",
"homepage": "",
"id": "1",
"identifier": "cortex-xsoar",
"inherit_members": false,
"is_public": "True",
"name": "Cortex XSOAR",
"status": "1",
"updated_on": "2024-02-29T10:34:23Z"
}
}
}
Human Readable Output
Projects List
Id Name Identifier Status IsPublic CreatedOn UpdatedOn 1 Cortex XSOAR cortex-xsoar 1 True 2024-02-29T10:34:23Z 2024-02-29T10:34:23Z
redmine-custom-field-list
Retrieve a list of all custom fields.
Base Command
redmine-custom-field-list
Input
There are no input arguments for this command.
Context Output
| Path | Type | Description |
|---|---|---|
| Redmine.CustomField | dict | Retrieve details of all custom fields. |
| Redmine.CustomField.id | str | Display ids of custom fields. |
| Redmine.CustomField.name | str | Display names of custom fields. |
| Redmine.CustomField.customized_type | str | Display customized_type of custom fields. |
Command example
#### Context Example
```json
{
"Redmine": {
"CustomField": [
{
"customized_type": "issue",
"default_value": "",
"description": "specify the team of workers under this issue",
"field_format": "string",
"id": "1",
"is_filter": "True",
"is_required": "False",
"max_length": null,
"min_length": null,
"multiple": false,
"name": "Team_of_workers",
"regexp": "",
"roles": [],
"searchable": true,
"trackers": [
{
"id": 1,
"name": "Bug"
},
{
"id": 2,
"name": "Feature"
},
{
"id": 3,
"name": "Support"
}
],
"visible": true
},
{
"customized_type": "project",
"default_value": "",
"description": "",
"field_format": "string",
"id": "3",
"is_filter": "False",
"is_required": "False",
"max_length": null,
"min_length": null,
"multiple": false,
"name": "second_custom_field",
"regexp": "",
"searchable": false,
"visible": true
}
]
}
}
Human Readable Output
Custom Fields List
Id Name CustomizedType FieldFormat IsRequired IsFilter Searchable Trackers 1 Team_of_workers issue string False True - id: 1
name: Bug
- id: 2
name: Feature
- id: 3
name: Support3 second_custom_field project string False False
redmine-user-id-list
- Retrieve a list of users with optional filtering options.
- This command requires admin privileges in your Redmine account.
Base Command
redmine-user-id-list
Input
| Argument Name | Description | Required |
|---|---|---|
| status | Specify the status of users to retrieve. Possible values are: Active, Registered, Locked. | Optional |
| name | Search for users matching a specific name (searches in first name, last name, and email). | Optional |
| group_id | Specify the group ID to filter users by. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Redmine.Users | dict | A list of users. |
| Redmine.Users.id | str | A list of users IDs. |
| Redmine.Users.login | str | A list of users login usernames. |
| Redmine.Users.admin | str | A list of users admins permission. |
| Redmine.Users.firstname | str | A list of users first name. |
| Redmine.Users.lastname | str | A list of users last name. |
| Redmine.Users.mail | str | A list of users mails. |
Command example
#### Context Example
```json
{
"Redmine": {
"Users": [
{
"admin": "True",
"created_on": "2024-02-28T19:47:56Z",
"firstname": "admin",
"id": "5",
"last_login_on": "2024-02-29T10:25:08Z",
"lastname": "tests",
"login": "admin",
"mail": "admin@redmine-test.local",
"passwd_changed_on": "2024-02-28T19:49:17Z",
"twofa_scheme": null,
"updated_on": "2024-02-28T19:50:49Z"
},
{
"admin": "True",
"created_on": "2024-02-29T10:27:31Z",
"firstname": "Integration",
"id": "6",
"last_login_on": "2024-02-29T10:55:25Z",
"lastname": "Test",
"login": "demiadmin",
"mail": "demiadmin@redmine-test.local",
"passwd_changed_on": "2024-02-29T10:27:31Z",
"twofa_scheme": null,
"updated_on": "2024-02-29T10:27:31Z"
},
{
"admin": "True",
"created_on": "2024-02-28T18:34:10Z",
"firstname": "UserName",
"id": "1",
"last_login_on": "2024-02-29T09:50:10Z",
"lastname": "LastName",
"login": "user",
"mail": "user@example.com",
"passwd_changed_on": null,
"twofa_scheme": null,
"updated_on": "2024-02-28T18:34:10Z"
}
]
}
}
Human Readable Output
Users List
ID Login Admin First Name Last Name Created On Last Login On 5 admin True admin tests admin@redmine-test.local 2024-02-28T19:47:56Z 2024-02-29T10:25:08Z 6 demiadmin True Integration Test demiadmin@redmine-test.local 2024-02-29T10:27:31Z 2024-02-29T10:55:25Z 1 user True UserName LastName user@example.com 2024-02-28T18:34:10Z 2024-02-29T09:50:10Z
redmine-priority-id-list
Returns a list of priorities (priority_name to priority_id).
Base Command
redmine-priority-id-list
Input
There are no input arguments for this command.
Context Output
| Path | Type | Description |
|---|---|---|
| Redmine.priorities.ID | str | ID of the priority field. |
| Redmine.priorities.Name | str | Name of the priority field. |
Command example
#### Context Example
```json
{
"Redmine": {
"priorities": [
{
"ID": 1,
"Name": "Low"
},
{
"ID": 2,
"Name": "Normal"
},
{
"ID": 3,
"Name": "High"
},
{
"ID": 4,
"Name": "Urgent"
},
{
"ID": 5,
"Name": "Immediate"
}
]
}
}
Human Readable Output
priorities name to id List
ID Name 1 Low 2 Normal 3 High 4 Urgent 5 Immediate
redmine-status-id-list
Returns a list of statuses (status_name to status_id).
Base Command
redmine-status-id-list
Input
There are no input arguments for this command.
Context Output
| Path | Type | Description |
|---|---|---|
| Redmine.statuses.ID | str | ID of the status field. |
| Redmine.Users.Name | str | Name of the status field. |
Command example
#### Context Example
```json
{
"Redmine": {
"statuses": [
{
"ID": 1,
"Name": "help"
},
{
"ID": 2,
"Name": "In Progress"
},
{
"ID": 3,
"Name": "Resolved"
},
{
"ID": 4,
"Name": "Feedback"
},
{
"ID": 5,
"Name": "Closed"
},
{
"ID": 6,
"Name": "Rejected"
}
]
}
}
Human Readable Output
statuses name to id List
ID Name 1 help 2 In Progress 3 Resolved 4 Feedback 5 Closed 6 Rejected
redmine-tracker-id-list
Returns a list of trackers (tracker_name to tracker_id).
Base Command
redmine-tracker-id-list
Input
There are no input arguments for this command.
Context Output
| Path | Type | Description |
|---|---|---|
| Redmine.trackers.ID | str | ID of the tracker field. |
| Redmine.trackers.Name | str | Name of the tracker field. |
Command example
#### Context Example
```json
{
"Redmine": {
"trackers": [
{
"ID": 1,
"Name": "Bug111"
},
{
"ID": 2,
"Name": "Feature"
},
{
"ID": 3,
"Name": "Support"
}
]
}
}
Human Readable Output
trackers name to id List
ID Name 1 Bug111 2 Feature 3 Support
Configuration parameters
url— Server URL (e.g., https://1.1.1.1) (required)credentials— (required)project_id— Project Idinsecure— Trust any certificate (not secure)
Commands (13)
-
redmine-custom-field-listRetrieve a list of all custom fields.
-
redmine-issue-create- Create a new issue. - The status of the newly created issue will be set to the default status of the Redmine instance. - When attaching a file to an issue, include the entry ID in the request as file_entry_id=entry ID. - To create a custom field, navigate to the server URL with administrative privileges, click **Administration** (located at the top left), select **Custom fields**,' and then proceed to create a new custom field. Once created, you can add values as needed. - To create a category/version, navigate to the server URL -> Click on the Settings (top bar) -> Versions tab and Issue categories tab.
-
redmine-issue-deleteDelete an issue by its ID.
-
redmine-issue-getShow an issue by ID.
-
redmine-issue-listDisplay a list of issues.
-
redmine-issue-updateUpdate an existing issue. When attaching a file to an issue, include the entry ID in the request as file_entry_id=the ID you created. To create a custom field, navigate to the server URL with administrative privileges, click **'Administration** (located at the top left), select **Custom fields**, and proceed to create a new custom field. Once created, you can add values as needed.
-
redmine-issue-watcher-addAdd a watcher to the specified issue. Ensure that the watcher_id is accurate, as the Redmine API does not raise an exception for an incorrect watcher_id.
-
redmine-issue-watcher-removeRemove a watcher of an issue.
-
redmine-priority-id-listReturns a list of priorities (priority name to priority id).
-
redmine-project-listRetrieve a list of all projects, including both public and private ones that the user has access to.
-
redmine-status-id-listReturns a list of statuses (status name to status id).
-
redmine-tracker-id-listReturns a list of trackers (tracker name to tracker id).
-
redmine-user-id-list- Retrieve a list of users with optional filtering options. - This command requires admin privileges in your Redmine account.
import pytest from Redmine import Client @pytest.fixture def redmine_client(url: str = "url", verify_certificate: bool = True, proxy: bool = False, auth=("username", "password")): return Client(url, verify_certificate, proxy, auth=auth) """ COMMAND FUNCTIONS TESTS """ def test_create_issue_command(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-issue-create command is executed Then: - The http request is called with the right arguments """ from Redmine import create_issue_command http_request = mocker.patch.object(redmine_client, "_http_request") http_request.return_value = {"issue": {"id": "1"}} args = { "project_id": "1", "issue_id": "1", "subject": "changeFromCode", "priority_id": "1", "tracker_id": "1", "watcher_user_ids": "[1]", "custom_fields": '{"1":"https://test:appear"}', } create_issue_command(redmine_client, args=args) http_request.assert_called_with( "POST", "/issues.json", params={}, json_data={ "issue": { "issue_id": "1", "subject": "changeFromCode", "priority_id": "1", "tracker_id": "1", "custom_fields": [{"id": "1", "value": "https://test:appear"}], "project_id": "1", "watcher_user_ids": [1], } }, headers={"Content-Type": "application/json", "X-Redmine-API-Key": True}, ) def test_create_issue_command_with_multiselect_cf(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed (with multiselect custom field) When: - redmine-issue-create command is executed Then: - The http request is called with the right arguments """ from Redmine import create_issue_command http_request = mocker.patch.object(redmine_client, "_http_request") http_request.return_value = {"issue": {"id": "1"}} args = { "project_id": "1", "issue_id": "1", "subject": "changeFromCode", "priority_id": "1", "tracker_id": "1", "watcher_user_ids": "[1]", "custom_fields": '{"4":["a","b","c"],"1":"hello [], my name is paloalto, paloalto"}', } create_issue_command(redmine_client, args=args) http_request.assert_called_with( "POST", "/issues.json", params={}, json_data={ "issue": { "issue_id": "1", "subject": "changeFromCode", "priority_id": "1", "tracker_id": "1", "custom_fields": [ {"id": "4", "value": ["a", "b", "c"]}, {"id": "1", "value": ("hello [], my name is paloalto, paloalto")}, ], "project_id": "1", "watcher_user_ids": [1], } }, headers={"Content-Type": "application/json", "X-Redmine-API-Key": True}, ) def test_create_issue_command_not_url_cf(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed without list id When: - redmine-issue-create command is executed Then: - The http request is called with the right arguments """ from Redmine import create_issue_command http_request = mocker.patch.object(redmine_client, "_http_request") http_request.return_value = {"issue": {"id": "1"}} args = { "project_id": "1", "issue_id": "1", "subject": "changeFromCode", "tracker_id": "2", "priority_id": "2", "watcher_user_ids": "[1]", "custom_fields": '{"1":"hello"}', } create_issue_command(redmine_client, args=args) http_request.assert_called_with( "POST", "/issues.json", params={}, json_data={ "issue": { "issue_id": "1", "subject": "changeFromCode", "tracker_id": "2", "priority_id": "2", "custom_fields": [{"id": "1", "value": "hello"}], "project_id": "1", "watcher_user_ids": [1], } }, headers={"Content-Type": "application/json", "X-Redmine-API-Key": True}, ) def test_create_issue_command_response(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed without list id When: - redmine-issue-create command is executed Then: - The http request return with the expected response """ from Redmine import create_issue_command args = { "project_id": "1", "issue_id": "1", "subject": "testResponse", "tracker_id": "1", "custom_fields": '{"1":"https://test:appear,,,,"}', } create_issue_request_mock = mocker.patch.object(redmine_client, "create_issue_request") create_issue_request_mock.return_value = { "issue": { "id": "789", "project": {"name": "testing", "id": "1"}, "subject": "testResponse", "tracker": {"name": "bug", "id": "1"}, "custom_fields": {"name": "test", "value": "https://test:appear"}, } } result = create_issue_command(redmine_client, args) assert result.readable_output == ( "### The issue you created:\n|Id|Project|Tracker|Subject|Custom Fields|\n|---|---|---|" "---|---|\n| 789 | testing | bug | testResponse | ***name***: test<br>***value***: " "https://test:appear |\n" ) def test_create_issue_command_with_multiselect_cf_response(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed without list id (multiselect custom field) When: - redmine-issue-create command is executed Then: - The http request return with the expected response """ from Redmine import create_issue_command args = { "project_id": "1", "issue_id": "1", "subject": "testResponse", "tracker_id": "1", "custom_fields": '{"4":["a","b","c"],"1":"hello [], my name is paloalto, paloalto"}', } create_issue_request_mock = mocker.patch.object(redmine_client, "create_issue_request") create_issue_request_mock.return_value = { "issue": { "id": "789", "project": {"name": "testing", "id": "1"}, "subject": "testResponse", "tracker": {"name": "bug", "id": "1"}, "custom_fields": [ {"name": "test", "value": ["a", "b", "c"]}, {"name": "test2", "value": ("hello [], my name is paloalto, paloalto")}, ], } } result = create_issue_command(redmine_client, args) assert result.readable_output == ( "### The issue you created:\n|Id|Project|Tracker|Subject|Custom Fields|\n|---|---|---|---" "|---|\n| 789 | testing | bug | testResponse | **-**\t***name***: test<br>\t**value**:<br>" "\t\t***values***: a, b, c<br>**-**\t***name***: test2<br>\t***value***: hello [], my name" " is paloalto, paloalto |\n" ) def test_create_issue_command_invalid_custom_fields(redmine_client): """ Given: - All relevant arguments for the command that is executed and invalid custom field format When: - redmine-issue-create command is executed Then: - A DemistoException is raised- invalid custom field """ from Redmine import create_issue_command from CommonServerPython import DemistoException args = { "project_id": "1", "custom_fields": '{"1https://test:appear,111"}', "issue_id": "1", "subject": "testSub", "tracker": "bug", "watcher_user_ids": "[1]", "priority": "high", } with pytest.raises(DemistoException) as e: create_issue_command(redmine_client, args) assert e.value.message == ( 'Custom fields not in format, please follow this format: `{"customFieldID2": "value3", ' '"customFieldID1": ["value1","value2"]}` - Please use an array if the field is of multiselect type' ". with error: Expecting ':' delimiter: line 1 column 28 (char 27)" ) def test_create_issue_command_no_token_created_for_file(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-issue-create command is executed Then: - The http request is called with the right arguments """ from Redmine import create_issue_command from CommonServerPython import DemistoException create_file_token_request_mock = mocker.patch.object(redmine_client, "create_file_token_request") create_file_token_request_mock.return_value = {"token": "token123"} args = { "project_id": "1", "status": "New", "file_entry_id": "a.png", "issue_id": "1", "subject": "testSub", "tracker": "Bug", "priority": "1", "watcher_user_ids": "[1]", } with pytest.raises(DemistoException) as e: create_issue_command(redmine_client, args) create_file_token_request_mock.assert_called_with({}, "a.png") assert e.value.message == "Could not upload file with entry id a.png, please try again." def test_create_issue_command_with_file(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-issue-create command is executed Then: - The http request is called with the right arguments """ from Redmine import create_issue_command http_request = mocker.patch.object(redmine_client, "_http_request") http_request.return_value = {"issue": {"id": "1"}} create_file_token_request_mock = mocker.patch.object(redmine_client, "create_file_token_request") create_file_token_request_mock.return_value = {"upload": {"token": "token123"}} args = { "project_id": "1", "file_entry_id": "a.png", "issue_id": "1", "subject": "testSub", "tracker_id": "1", "watcher_user_ids": "[1]", "priority_id": "1", } create_issue_command(redmine_client, args=args) create_file_token_request_mock.assert_called_with({}, "a.png") http_request.assert_called_with( "POST", "/issues.json", params={}, json_data={ "issue": { "issue_id": "1", "subject": "testSub", "uploads": [{"token": "token123"}], "tracker_id": "1", "priority_id": "1", "project_id": "1", "watcher_user_ids": [1], } }, headers={"Content-Type": "application/json", "X-Redmine-API-Key": True}, ) def test_create_issue_command_with_file_response(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-issue-create command is executed Then: - The http request return with the expected response with file """ from Redmine import create_issue_command args = { "project_id": "1", "subject": "testResponse", "tracker_id": "1", "file_entry_id": "139i401hivnaflkm", "file_name": "test file response", "priority_id": "1", } create_file_token_request_mock = mocker.patch.object(redmine_client, "create_file_token_request") create_file_token_request_mock.return_value = {"upload": {"token": "111111"}} create_issue_request_mock = mocker.patch.object(redmine_client, "create_issue_request") create_issue_request_mock.return_value = { "issue": { "id": "789", "project": {"name": "testing", "id": "1"}, "subject": "testResponse", "tracker": {"name": "bug", "id": "1"}, } } result = create_issue_command(redmine_client, args) assert args.get("uploads", {})[0].get("token") == "111111" assert args.get("uploads", {})[0].get("filename") == "test file response" assert result.readable_output == ( "### The issue you created:\n|Id|Project|Tracker|Subject|\n|---|---|---|---|\n| 789 | testing | bug | testResponse |\n" ) def test_create_issue_command_with_file_invalid_token_response(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-issue-create command is executed Then: - Raises an error on token response - not valid """ from Redmine import create_issue_command from CommonServerPython import DemistoException args = { "project_id": "1", "subject": "testResponse", "tracker_id": "1", "file_entry_id": "139i401hivnaflkm", "file_name": "test file response", "priority_id": "1", } create_file_token_request_mock = mocker.patch.object(redmine_client, "create_file_token_request") create_file_token_request_mock.return_value = {"upload": {"tokens": "111111"}} with pytest.raises(DemistoException) as e: create_issue_command(redmine_client, args) assert e.value.message == "Could not upload file with entry id 139i401hivnaflkm, please try again." def test_update_issue_command(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-issue-update command is executed Then: - The http request is called with the right arguments """ from Redmine import update_issue_command http_request = mocker.patch.object(redmine_client, "_http_request") args = { "issue_id": "1", "subject": "changeFromCode", "tracker_id": "1", "status_id": "1", "priority_id": "1", "watcher_user_ids": "[1]", } update_issue_command(redmine_client, args=args) http_request.assert_called_with( "PUT", "/issues/1.json", json_data={ "issue": { "subject": "changeFromCode", "tracker_id": "1", "status_id": "1", "priority_id": "1", "watcher_user_ids": [1], } }, headers={"Content-Type": "application/json", "X-Redmine-API-Key": True}, empty_valid_codes=[204], return_empty_response=True, ) def test_update_issue_command_with_multiselect_cf(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed (with multiselect custom field) When: - redmine-issue-update command is executed Then: - The http request is called with the right arguments """ from Redmine import update_issue_command http_request = mocker.patch.object(redmine_client, "_http_request") args = { "issue_id": "1", "subject": "changeFromCode", "tracker_id": "1", "status_id": "1", "priority_id": "1", "watcher_user_ids": "[1]", "custom_fields": '{"4":["a","b","c"],"1":"hello [], my name is paloalto, paloalto"}', } update_issue_command(redmine_client, args=args) http_request.assert_called_with( "PUT", "/issues/1.json", json_data={ "issue": { "subject": "changeFromCode", "tracker_id": "1", "status_id": "1", "priority_id": "1", "custom_fields": [ {"id": "4", "value": ["a", "b", "c"]}, {"id": "1", "value": ("hello [], my name is paloalto, paloalto")}, ], "watcher_user_ids": [1], } }, headers={"Content-Type": "application/json", "X-Redmine-API-Key": True}, empty_valid_codes=[204], return_empty_response=True, ) def test_update_issue_command_response(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-issue-update command is executed Then: - The http request returns the right response """ from Redmine import update_issue_command update_issue_request_mock = mocker.patch.object(redmine_client, "update_issue_request") args = { "issue_id": "1", "subject": "changefortest", "tracker_id": "1", "status_id": "1", "priority_id": "1", "watcher_user_ids": "[1]", "custom_fields": '{"4":["a","b","c"],"1":"hello [], my name is paloalto, paloalto"}', } update_issue_request_mock.return_value = {} result = update_issue_command(redmine_client, args=args) assert result.readable_output == "Issue with id 1 was successfully updated." def test_update_issue_command_invalid_custom_fields(redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-issue-update command is executed Then: - Raises a custom fields not in format error """ from Redmine import update_issue_command from CommonServerPython import DemistoException args = { "custom_fields": "jnlnj", "issue_id": "1", "subject": "testSub", "tracker_id": "1", "watcher_user_ids": "[1]", "status_id": "1", "priority_id": "high", } with pytest.raises(DemistoException) as e: update_issue_command(redmine_client, args) assert e.value.message == ( 'Custom fields not in format, please follow this format: `{"customFieldID2": "value3", ' '"customFieldID1": ["value1","value2"]}` - Please use an array if the field is of multiselect type' ". with error: Expecting value: line 1 column 1 (char 0)" ) def test_update_issue_command_no_token_created_for_file(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-issue-delete command is executed Then: - The http request is called with the right arguments """ from Redmine import update_issue_command from CommonServerPython import DemistoException create_file_token_request_mock = mocker.patch.object(redmine_client, "create_file_token_request") create_file_token_request_mock.return_value = {"token": "token123"} args = { "status_id": "1", "file_entry_id": "a.png", "issue_id": "1", "subject": "testSub", "tracker_id": "bug", "priority_id": "1", "watcher_user_ids": "[1]", } with pytest.raises(DemistoException) as e: update_issue_command(redmine_client, args) create_file_token_request_mock.assert_called_with({}, "a.png") assert e.value.message == ("Could not upload file with entry id a.png, please try again.") def test_update_issue_command_with_file(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed without list id When: - redmine-issue-update command is executed Then: - The http request is called with the right arguments """ from Redmine import update_issue_command http_request = mocker.patch.object(redmine_client, "_http_request") create_file_token_request_mock = mocker.patch.object(redmine_client, "create_file_token_request") create_file_token_request_mock.return_value = {"upload": {"token": "token123"}} args = {"file_entry_id": "a.png", "issue_id": "1", "subject": "testSub", "tracker_id": "1", "watcher_user_ids": "[1]"} update_issue_command(redmine_client, args=args) create_file_token_request_mock.assert_called_with({}, "a.png") http_request.assert_called_with( "PUT", "/issues/1.json", json_data={ "issue": {"subject": "testSub", "tracker_id": "1", "uploads": [{"token": "token123"}], "watcher_user_ids": [1]} }, headers={"Content-Type": "application/json", "X-Redmine-API-Key": True}, empty_valid_codes=[204], return_empty_response=True, ) def test_field_from_name_to_id_list_command_for_tracker_called_with(mocker, redmine_client): """ Given: - 'trackers' field When: - field_from_name_to_id_list_command func is being called Then: - The http request is called with the right arguments """ from Redmine import field_from_name_to_id_list_command http_request = mocker.patch.object(redmine_client, "_http_request") http_request.return_value = {"trackers": [{"id": "1", "name": "bug"}, {"id": "2", "name": "task"}]} field_from_name_to_id_list_command(redmine_client, "trackers") http_request.assert_called_with("GET", "/trackers.json", headers={"X-Redmine-API-Key": True}) def test_field_from_name_to_id_list_command_for_tracker_invalid_response(mocker, redmine_client): """ Given: - 'trackers' field When: - field_from_name_to_id_list_command func is being called Then: - raise an error """ from Redmine import field_from_name_to_id_list_command from CommonServerPython import DemistoException http_request = mocker.patch.object(redmine_client, "_http_request") http_request.return_value = {"trackerss": [{"id": "1", "name": "bug"}, {"id": "2", "name": "task"}]} with pytest.raises(DemistoException) as e: field_from_name_to_id_list_command(redmine_client, "trackers") assert e.value.message == "Failed to retrieve tracker IDs due to a parsing error." def test_field_from_name_to_id_list_command_for_tracker_check_response(mocker, redmine_client): """ Given: - 'trackers' field When: - field_from_name_to_id_list_command is being called Then: - The expected hr is being shown """ from Redmine import field_from_name_to_id_list_command http_request = mocker.patch.object(redmine_client, "_http_request") http_request.return_value = {"trackers": [{"id": "1", "name": "bug"}, {"id": "2", "name": "task"}]} result = field_from_name_to_id_list_command(redmine_client, "trackers") assert result.readable_output == "### trackers name to id List:\n|ID|Name|\n|---|---|\n| 1 | bug |\n| 2 | task |\n" def test_field_from_name_to_id_list_command_for_status_called_with(mocker, redmine_client): """ Given: - 'statuses' field When: - field_from_name_to_id_list_command is being called Then: - The http request is called with the right arguments """ from Redmine import field_from_name_to_id_list_command http_request = mocker.patch.object(redmine_client, "_http_request") http_request.return_value = {"issue_statuses": [{"id": "1", "name": "new"}, {"id": "2", "name": "progress"}]} field_from_name_to_id_list_command(redmine_client, "statuses") http_request.assert_called_with("GET", "/issue_statuses.json", headers={"X-Redmine-API-Key": True}) def test_field_from_name_to_id_list_command_for_status_invalid_response(mocker, redmine_client): """ Given: - 'statuses' field When: - field_from_name_to_id_list_command is being called Then: - raise an error """ from Redmine import field_from_name_to_id_list_command from CommonServerPython import DemistoException http_request = mocker.patch.object(redmine_client, "_http_request") http_request.return_value = {"statuses": [{"id": "1", "name": "new"}, {"id": "2", "name": "progress"}]} with pytest.raises(DemistoException) as e: field_from_name_to_id_list_command(redmine_client, "statuses") assert e.value.message == "Failed to retrieve status IDs due to a parsing error." def test_field_from_name_to_id_list_command_for_status_check_response(mocker, redmine_client): """ Given: - 'statuses' field When: - field_from_name_to_id_list_command func is being called Then: - The expected hr is being shown """ from Redmine import field_from_name_to_id_list_command http_request = mocker.patch.object(redmine_client, "_http_request") http_request.return_value = {"issue_statuses": [{"id": "1", "name": "new"}, {"id": "2", "name": "progress"}]} result = field_from_name_to_id_list_command(redmine_client, "statuses") assert result.readable_output == "### statuses name to id List:\n|ID|Name|\n|---|---|\n| 1 | new |\n| 2 | progress |\n" def test_field_from_name_to_id_list_command_for_priority_called_with(mocker, redmine_client): """ Given: - 'priorities' field When: - field_from_name_to_id_list_command is being called Then: - The http request is called with the right arguments """ from Redmine import field_from_name_to_id_list_command http_request = mocker.patch.object(redmine_client, "_http_request") http_request.return_value = {"issue_priorities": [{"id": "1", "name": "low"}, {"id": "2", "name": "medium"}]} field_from_name_to_id_list_command(redmine_client, "priorities") http_request.assert_called_with("GET", "/enumerations/issue_priorities.json", headers={"X-Redmine-API-Key": True}) def test_field_from_name_to_id_list_command_for_priority_invalid_response(mocker, redmine_client): """ Given: - 'priorities' field When: - field_from_name_to_id_list_command is being called Then: - raise an error """ from Redmine import field_from_name_to_id_list_command from CommonServerPython import DemistoException http_request = mocker.patch.object(redmine_client, "_http_request") http_request.return_value = {"issue_prioritiess": [{"id": "1", "name": "low"}, {"id": "2", "name": "medium"}]} with pytest.raises(DemistoException) as e: field_from_name_to_id_list_command(redmine_client, "priorities") assert e.value.message == "Failed to retrieve priority IDs due to a parsing error." def test_field_from_name_to_id_list_command_for_priority_check_response(mocker, redmine_client): """ Given: - 'priorities' field When: - field_from_name_to_id_list_command func is being called Then: - The expected hr is being shown """ from Redmine import field_from_name_to_id_list_command http_request = mocker.patch.object(redmine_client, "_http_request") http_request.return_value = {"issue_priorities": [{"id": "1", "name": "low"}, {"id": "2", "name": "medium"}]} result = field_from_name_to_id_list_command(redmine_client, "priorities") assert result.readable_output == "### priorities name to id List:\n|ID|Name|\n|---|---|\n| 1 | low |\n| 2 | medium |\n" def test_get_issues_list_command(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-issue-list command is executed Then: - The http request is called with the right arguments """ from Redmine import get_issues_list_command http_request = mocker.patch.object(redmine_client, "_http_request") args = {"sort": "priority:desc", "limit": "1", "custom_field": "1:https://tests"} get_issues_list_command(redmine_client, args) http_request.assert_called_with( "GET", "/issues.json", params={"status_id": "open", "offset": 0, "limit": 1, "sort": "priority:desc", "cf_1": "https://tests"}, headers={"X-Redmine-API-Key": True}, ) def test_get_issues_list_command_response(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-issue-list command is executed Then: - The expected hr is being shown """ from Redmine import get_issues_list_command get_issues_list_request_mock = mocker.patch.object(redmine_client, "get_issues_list_request") get_issues_list_request_mock.return_value = { "issues": [ { "id": "1", "tracker": {"name": "Bug", "id": "1"}, "status": {"name": "new", "id": "1"}, "priority": {"name": "High", "id": "1"}, "subject": "helloTest", } ] } args = {"sort": "priority:desc", "limit": "1"} result = get_issues_list_command(redmine_client, args) assert result.readable_output == ( "#### Showing 1 results from page 1:\n### Issues Results:\n" "|Id|Tracker|Status|Priority|Subject|\n|---|---|---|---|---|\n" "| 1 | Bug | new | High | helloTest |\n" ) def test_get_issues_list_command_invalid_response(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-issue-list command is executed Then: - raises a response format error """ from Redmine import get_issues_list_command from CommonServerPython import DemistoException get_issues_list_request_mock = mocker.patch.object(redmine_client, "get_issues_list_request") get_issues_list_request_mock.return_value = { "issue": [ { "id": "1", "tracker": {"name": "Bug", "id": "1"}, "status": {"name": "new", "id": "1"}, "priority": {"name": "High", "id": "1"}, "subject": "helloTest", } ] } args = {"sort": "priority:desc", "limit": "1"} with pytest.raises(DemistoException) as e: get_issues_list_command(redmine_client, args) assert e.value.message == "The request succeeded, but a parse error occurred." def test_get_issues_list_command_invalid_custom_field(redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-issue-list command is executed Then: - Raises an exception for invalid custom field """ from Redmine import get_issues_list_command from CommonServerPython import DemistoException with pytest.raises(DemistoException) as e: get_issues_list_command(redmine_client, {"custom_field": "frf2rg2"}) assert e.value.message == ( "Invalid custom field format, please follow the command description. Error: list index out of range." ) def test_get_issues_list_command_use_both_exclude_subproject(redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-issue-list command is executed Then: - Raises an exception for invalid usage of both exclude and subproject fields """ from Redmine import get_issues_list_command from CommonServerPython import DemistoException with pytest.raises(DemistoException) as e: get_issues_list_command(redmine_client, {"subproject_id": "1", "exclude": "2"}) assert e.value.message == "Specify only one of the following, subproject_id or exclude." def test_get_issues_list_command_invalid_status_using_request(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-issue-list command is executed Then: - Raise an error since the status arg is not a number """ from Redmine import get_issues_list_command from CommonServerPython import DemistoException with pytest.raises(DemistoException) as e: mocker.patch.object( redmine_client, "_http_request", return_value={"issue_statuses": [{"id": "1", "name": "New"}, {"id": "2", "name": "Feedback"}]}, ) get_issues_list_command(redmine_client, {"status": "New"}) assert e.value.message == ( "Status argument has to be a predefined value (Open/Closed/All) or a valid status ID but got " 'New. with error: "New" is not a valid number.' ) def test_get_issue_by_id_command(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-issue-show command is executed Then: - The http request is called with the right arguments """ from Redmine import get_issue_by_id_command http_request = mocker.patch.object(redmine_client, "_http_request") http_request.return_value = {"issue": {"id": "1"}} args = {"issue_id": "1", "include": "watchers,attachments"} get_issue_by_id_command(redmine_client, args) http_request.assert_called_with( "GET", "/issues/1.json", params={"include": "watchers,attachments"}, headers={"X-Redmine-API-Key": True} ) def test_get_issue_by_id_command_response(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-issue-show command is executed Then: - The expected hr is being shown """ from Redmine import get_issue_by_id_command get_issue_by_id_request_mock = mocker.patch.object(redmine_client, "get_issue_by_id_request") get_issue_by_id_request_mock.return_value = { "issue": { "id": "1", "tracker": {"name": "Bug", "id": "1"}, "status": {"name": "new", "id": "1"}, "priority": {"name": "High", "id": "1"}, "subject": "helloTest", "watchers": [{"name": "testingWatch", "id": "1"}], } } args = {"issue_id": "1", "include": "watchers,attachments"} result = get_issue_by_id_command(redmine_client, args) assert result.readable_output == ( "### Issues List:\n|Id|Tracker|Status|Priority|Subject|Watchers|\n|---|---|---|---|---|---|" "\n| 1 | Bug | new | High | helloTest | **-** ***name***: testingWatch |\n" ) def test_get_issue_by_id_command_invalid_response(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-issue-show command is executed Then: - raises a response format error """ from Redmine import get_issue_by_id_command from CommonServerPython import DemistoException get_issue_by_id_request_mock = mocker.patch.object(redmine_client, "get_issue_by_id_request") get_issue_by_id_request_mock.return_value = { "id": "1", "tracker": {"name": "Bug", "id": "1"}, "status": {"name": "new", "id": "1"}, "priority": {"name": "High", "id": "1"}, "subject": "helloTest", "watchers": {"name": "testingWatch", "id": "1"}, } args = {"issue_id": "1", "include": "watchers,attachments"} with pytest.raises(DemistoException) as e: get_issue_by_id_command(redmine_client, args) assert e.value.message == "The request succeeded, but a parse error occurred." def test_get_issue_by_id_command_invalid_include_argument(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-issue-show command is executed Then: - No issue id raises a DemistoException """ from Redmine import get_issue_by_id_command from CommonServerPython import DemistoException args = {"sort": "priority:desc", "limit": "1", "include": "beikbfqi"} with pytest.raises(DemistoException) as e: get_issue_by_id_command(redmine_client, args) assert e.value.message == ( "The 'include' argument should only contain values from ['children', 'attachments', 'relations', " "'changesets', 'journals', 'watchers', 'allowed_statuses'], separated by commas. " "These values are not in options {'beikbfqi'}." ) def test_delete_issue_by_id_command_response(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-issue-delete command is executed Then: - The expected hr is being shown """ from Redmine import delete_issue_by_id_command delete_issue_by_id_request_mock = mocker.patch.object(redmine_client, "delete_issue_by_id_request") delete_issue_by_id_request_mock.return_value = {} args = {"issue_id": "41"} result = delete_issue_by_id_command(redmine_client, args) assert result.readable_output == "Issue with id 41 was deleted successfully." def test_delete_issue_by_id_command(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-issue-delete command is executed Then: - The http request is called with the right arguments """ from Redmine import delete_issue_by_id_command http_request = mocker.patch.object(redmine_client, "_http_request") args = {"issue_id": "41"} delete_issue_by_id_command(redmine_client, args) http_request.assert_called_with( "DELETE", "/issues/41.json", headers={"Content-Type": "application/json", "X-Redmine-API-Key": True}, empty_valid_codes=[200, 204, 201], return_empty_response=True, ) def test_delete_issue_by_id_command_invalid_issue_id(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-issue-delete command is executed Then: - No issue id raises a DemistoException """ from Redmine import add_issue_watcher_command from CommonServerPython import DemistoException args = {"issue_id": "-1"} http_request = mocker.patch.object(redmine_client, "_http_request") http_request.side_effect = DemistoException( "Invalid ID for one or more fields that request IDs. Please make sure all IDs are correct." ) with pytest.raises(DemistoException) as e: add_issue_watcher_command(redmine_client, args) assert e.value.message == "Invalid ID for one or more fields that request IDs. Please make sure all IDs are correct." def test_add_issue_watcher_command_response(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-issue-watcher-add command is executed Then: - The http request returns the right response """ from Redmine import add_issue_watcher_command add_issue_watcher_request_mock = mocker.patch.object(redmine_client, "add_issue_watcher_request") add_issue_watcher_request_mock.return_value = {} args = {"issue_id": "1", "watcher_id": "1"} result = add_issue_watcher_command(redmine_client, args) assert result.readable_output == "Watcher with id 1 was added successfully to issue with id 1." def test_add_issue_watcher_command(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-issue-watcher-add command is executed Then: - The http request is called with the right arguments """ from Redmine import add_issue_watcher_command http_request = mocker.patch.object(redmine_client, "_http_request") args = {"issue_id": "1", "watcher_id": "1"} add_issue_watcher_command(redmine_client, args) http_request.assert_called_with( "POST", "/issues/1/watchers.json", params={"user_id": "1"}, headers={"Content-Type": "application/json", "X-Redmine-API-Key": True}, empty_valid_codes=[200, 204, 201], return_empty_response=True, ) def test_add_issue_watcher_command_invalid_issue_watcher_id(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-issue-watcher-add command is executed Then: - No issue id raises a DemistoException """ from Redmine import add_issue_watcher_command from CommonServerPython import DemistoException args = {"issue_id": "-1", "watcher_id": "-20"} http_request = mocker.patch.object(redmine_client, "_http_request") http_request.side_effect = DemistoException( "Invalid ID for one or more fields that request IDs. Please make sure all IDs are correct." ) with pytest.raises(DemistoException) as e: add_issue_watcher_command(redmine_client, args) assert e.value.message == "Invalid ID for one or more fields that request IDs. Please make sure all IDs are correct." def test_remove_issue_watcher_command(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-issue-watcher-remove command is executed Then: - The http request is called with the right arguments """ from Redmine import remove_issue_watcher_command http_request = mocker.patch.object(redmine_client, "_http_request") args = {"issue_id": "1", "watcher_id": "1"} remove_issue_watcher_command(redmine_client, args) http_request.assert_called_with( "DELETE", "/issues/1/watchers/1.json", headers={"Content-Type": "application/json", "X-Redmine-API-Key": True}, empty_valid_codes=[200, 204, 201], return_empty_response=True, ) def test_remove_issue_watcher_command_response(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-issue-watcher-remove command is executed Then: - The expected hr is being shown """ from Redmine import remove_issue_watcher_command remove_issue_watcher_request_mock = mocker.patch.object(redmine_client, "remove_issue_watcher_request") remove_issue_watcher_request_mock.return_value = {} args = {"issue_id": "1", "watcher_id": "1"} result = remove_issue_watcher_command(redmine_client, args) assert result.readable_output == "Watcher with id 1 was removed successfully from issue with id 1." def test_remove_issue_watcher_command_invalid_issue_watcher_id(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-issue-watcher-remove command is executed Then: - No issue id raises a DemistoException """ from Redmine import remove_issue_watcher_command from CommonServerPython import DemistoException args = {"issue_id": "-1", "watcher_id": "-20"} http_request = mocker.patch.object(redmine_client, "_http_request") http_request.side_effect = DemistoException( "Invalid ID for one or more fields that request IDs. Please make sure all IDs are correct." ) with pytest.raises(DemistoException) as e: remove_issue_watcher_command(redmine_client, args) assert e.value.message == "Invalid ID for one or more fields that request IDs. Please make sure all IDs are correct." def test_get_project_list_command(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-project-list command is executed Then: - The http request is called with the right arguments """ from Redmine import get_project_list_command http_request = mocker.patch.object(redmine_client, "_http_request") http_request.return_value = {"projects": [{"id": "1", "status": "active", "is_public": "true"}]} args = {"include": "time_entry_activities"} get_project_list_command(redmine_client, args) http_request.assert_called_with( "GET", "/projects.json", params={"include": "time_entry_activities"}, headers={"X-Redmine-API-Key": True} ) def test_get_project_list_command_response(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-project-list command is executed Then: - The http request returns the right response """ from Redmine import get_project_list_command get_project_list_request_mock = mocker.patch.object(redmine_client, "get_project_list_request") get_project_list_request_mock.return_value = { "projects": [ { "id": "1", "name": "testProject", "issue_custom_fields": {"id": "1", "name": "custom"}, "status": "open", "is_public": True, } ] } args = {"include": "issue_custom_fields"} result = get_project_list_command(redmine_client, args) assert result.readable_output == ( "### Projects List:\n|Id|Name|Status|IsPublic|IssueCustomFields|\n|---|---|---|---|---|" "\n| 1 | testProject | open | True | ***id***: 1<br>***name***: custom |\n" ) def test_get_project_list_command_invalid_include(redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-project-list command is executed Then: - No issue id raises a DemistoException """ from Redmine import get_project_list_command from CommonServerPython import DemistoException args = {"include": "time_entry_activities,jissue_categories"} with pytest.raises(DemistoException) as e: get_project_list_command(redmine_client, args) assert e.value.message == ( "The 'include' argument should only contain values from ['trackers', 'issue_categories', " "'enabled_modules', 'time_entry_activities', 'issue_custom_fields'], separated by commas." " These values are not in options {'jissue_categories'}." ) def test_get_project_list_command_invalid_response(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-user-id-list command is executed Then: - The http request is called with the right arguments """ from Redmine import get_project_list_command from CommonServerPython import DemistoException args = {"status_id": "open"} mocker.patch.object(Client, "get_project_list_request", return_value={"projectsss": {}}) with pytest.raises(DemistoException) as e: get_project_list_command(redmine_client, args) assert e.value.message == "The request succeeded, but a parse error occurred." def test_get_custom_fields_command(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-custom-field-list command is executed Then: - The http request is called with the right arguments """ from Redmine import get_custom_fields_command http_request = mocker.patch.object(redmine_client, "_http_request") http_request.return_value = {"custom_fields": [{"id": "1", "is_required": True, "is_filter": False}]} get_custom_fields_command(redmine_client, {}) http_request.assert_called_with("GET", "/custom_fields.json", headers={"X-Redmine-API-Key": True}) def test_get_custom_fields_command_response(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-custom-field-list command is executed Then: - The http request returns the right response """ from Redmine import get_custom_fields_command get_custom_fields_request_mocker = mocker.patch.object(redmine_client, "get_custom_fields_request") get_custom_fields_request_mocker.return_value = { "custom_fields": [ {"id": "1", "name": "custom_test", "is_required": False, "is_filter": True, "trackers": {"name": "Bug", "id": "1"}} ] } result = get_custom_fields_command(redmine_client, {}) assert result.readable_output == ( "### Custom Fields List:\n|Id|Name|IsRequired|IsFilter|Trackers|\n|---|---|---|---|---|\n" "| 1 | custom_test | False | True | ***name***: Bug<br>***id***: 1 |\n" ) def test_get_custom_fields_command_invalid_response(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-custom-field-list command is executed Then: - raises a response format error """ from Redmine import get_custom_fields_command from CommonServerPython import DemistoException get_custom_fields_request_mocker = mocker.patch.object(redmine_client, "get_custom_fields_request") get_custom_fields_request_mocker.return_value = { "custom_fieldsss": [ {"id": "1", "name": "custom_test", "is_required": False, "is_filter": True, "trackers": {"name": "Bug", "id": "1"}} ] } args = {} with pytest.raises(DemistoException) as e: get_custom_fields_command(redmine_client, args) assert e.value.message == "The request succeeded, but a parse error occurred." def test_get_users_command(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-user-id-list command is executed Then: - The http request is called with the right arguments """ from Redmine import get_users_command http_request = mocker.patch.object(redmine_client, "_http_request") get_users_command(redmine_client, {"status": "Active"}) http_request.assert_called_with("GET", "/users.json", params={"status": "1"}, headers={"X-Redmine-API-Key": True}) def test_get_users_command_response(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-user-id-list command is executed Then: - The expected hr is being shown """ from Redmine import get_users_command get_users_request_mock = mocker.patch.object(redmine_client, "get_users_request") get_users_request_mock.return_value = { "users": [{"id": "1", "login": "admin", "admin": True, "firstname": "test", "lastname": "response"}] } result = get_users_command(redmine_client, {}) assert result.readable_output == ( "### Users List:\n|Id|Login|Admin|Firstname|Lastname|\n|---|---|---|---|---|\n| 1 | admin | True | test | response |\n" ) def test_get_users_command_invalid_response(mocker, redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-user-id-list command is executed Then: - Raise a parse error """ from Redmine import get_users_command from CommonServerPython import DemistoException args = {"status_id": "open"} mocker.patch.object(Client, "get_users_request", return_value={"usersss": {}}) with pytest.raises(DemistoException) as e: get_users_command(redmine_client, args) assert e.value.message == "The request succeeded, but a parse error occurred." def test_get_users_command_status_invalid(redmine_client): """ Given: - All relevant arguments for the command that is executed When: - redmine-user-id-list command is executed Then: - Raise an error since the status is invalid """ from Redmine import get_users_command from CommonServerPython import DemistoException with pytest.raises(DemistoException) as e: get_users_command(redmine_client, {"status": "new"}) assert e.value.message == "Invalid status value- please use the predefined options only." """ HELPER FUNCTIONS TESTS """ @pytest.mark.parametrize("page_size, page_number, expected_output", [(1, 10, "#### Showing 1 results from page 10:\n")]) def test_create_paging_header(page_size, page_number, expected_output): """ Given: - All relevant arguments for the command that is executed When: - redmine-user-id-list command is executed Then: - The http request is called with the right arguments """ from Redmine import create_paging_header assert create_paging_header(page_size, page_number) == expected_output @pytest.mark.parametrize("args, expected_output", [({"page_number": "2", "page_size": "20"}, (20, 20, 2))]) def test_adjust_paging_to_request(args, expected_output): """ Given: - All relevant arguments for the command that is executed When: - adjust_paging_to_request function is executed Then: - The http request is called with the right arguments """ from Redmine import adjust_paging_to_request assert adjust_paging_to_request(args["page_number"], args["page_size"], None) == expected_output