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

IDRedmine
ProviderOpen Source
CategoryUtilities
From Version6.10.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix 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

  1. Navigate to Settings > Integrations > Servers & Services.
  2. Search for Redmine.
  3. 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
  4. 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
  5. 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: helloCustom
2024-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: helloCustom
2024-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: helloCustom
2024-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: Support
3 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 Email 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 Id
  • insecure — Trust any certificate (not secure)

Commands (13)

  • redmine-custom-field-list

    Retrieve 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-delete

    Delete an issue by its ID.

  • redmine-issue-get

    Show an issue by ID.

  • redmine-issue-list

    Display a list of issues.

  • 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=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-add

    Add 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-remove

    Remove a watcher of an issue.

  • redmine-priority-id-list

    Returns a list of priorities (priority name to priority id).

  • redmine-project-list

    Retrieve a list of all projects, including both public and private ones that the user has access to.

  • redmine-status-id-list

    Returns a list of statuses (status name to status id).

  • redmine-tracker-id-list

    Returns 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