ServiceDeskPlus

Use this integration to manage on-premises and cloud Service Desk Plus requests. The integration allows you to create, update, and delete requests, assign groups and technicians to requests, and link/unlink requests and modify their resolution.

Case Management · Manage Engine Service Desk Plus

Details

IDServiceDeskPlus
ProviderZoho Corporation
CategoryCase Management
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

IT Service Management

Configure ServiceDeskPlus in Cortex

  1. In Cortex XSOAR, navigate to Settings > Integrations > Instances. In Cortex XSIAM, navigate to Settings > Automation & Feed Integrations.
  2. Search for ServiceDeskPlus.
  3. Click Add instance to create and configure a new integration instance.

Instance Creation Flow for Cloud

To create an instance of the Service Desk Plus integration, you need to get a Client ID, Client Secret, and Refresh Token.
Follow the next steps to create an instance:

  1. Select the data center in which your data resides.
  2. Register the app using ZOHO App Registration. Copy the Client ID and Client Secret, then paste them into the integration instance and click Done.
  3. In the registered app, select the Generate Code tab and define the scopes for the app.
  4. In the War Room or Playground, run the !service-desk-plus-generate-refresh-token command and paste the generated code into the Refresh Token parameter.
  5. Click the Test button to validate the instance.
  6. If the test is successful, click Save to save the instance.

Instance Creation Flow for On-Premise

To create an instance of the Service Desk Plus integration, you need to get a On-Premises Server URL and a Technician Key.

Follow the next steps to create an instance:

  1. Enter the On-Premise Server URL.
  2. Enter the Technician Key.
  3. Click the Test button to validate the instance.
  4. If the test is successful, click Save to save the instance.

NOTES

  • For more details about the app authorization process refer to App Authorization
  • The code generated in the app is only valid for a limited time.
  • In order to avoid repeating this process, the created Refresh Token should be saved for future use.
  • For more details about generating a technician key please refer to the help documentation

image

Parameter Description Required
server_url Data Center Location: Select the domain location that is applicable for you application True
technician_key Technician Key False
server_url_on_premise On-Premise Server URL False
client_id Client ID False
client_secret Client Secret False
refresh_token Refresh Token False
isFetch Fetch incidents False
incidentType Incident type False
insecure Trust any certificate (not secure) False
proxy Use system proxy settings False
fetch_status The status of the requests that should be fetched. Multiple status can be entered, separated by a comma. False
fetch_time First fetch time range (<number> <time unit>, e.g., 1 hour, 30 minutes) False
fetch_limit The maximum number of incidents that should be fetched each time False
fetch_filter Use this field to filter the incidents that are being fetched according to any of the request properties. Please see additional information for exact filter format. Overrides the status filter, if given. False
  1. 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.

service-desk-plus-requests-list


View the details of requests. If no parameters are given the details of all requests will be shown.

Base Command

service-desk-plus-requests-list

Input

Argument Name Description Required
request_id The unique request id of the request that should be shown. Optional
start_index Use this to get a list of tasks starting from this index.
e.g: 6
Optional
page_size Use this to mention the number of requests that needs to be returned.
e.g: 15. By default, will return only the first 10 requests.
Optional
search_fields The column name and value to be searched for in the format of a json object. e.g {“subject”:“Change like this”,“priority.name”:“High”} Optional
filter_by The name of the filter that should be used. e.g {“name”:“My_Open”} Optional

Context Output

Path Type Description
ServiceDeskPlus.Request.Requester Unknown The requester of the request
ServiceDeskPlus.Request.CreatedTime Date The time the request was created
ServiceDeskPlus.Request.Template Unknown The template that was used to create the request
ServiceDeskPlus.Request.DisplayId String The display id of the request
ServiceDeskPlus.Request.Id String The unique id of the request
ServiceDeskPlus.Request.Subject String The subject of the request
ServiceDeskPlus.Request.Technician String The technician that was assigned to the request
ServiceDeskPlus.Request.Status String The status of the request
ServiceDeskPlus.Request.DueByTime Date The due date of the request
ServiceDeskPlus.Request.Group String The group to which the request belongs
ServiceDeskPlus.Request.IsServiceRequest Boolean Indicates whether the request is a service request or not
ServiceDeskPlus.Request.CancellationRequested Boolean Indicates whether a cancellation was requested
ServiceDeskPlus.Request.HasNotes Boolean Indicates whether the command has notes or not

Command Example

!service-desk-plus-requests-list start_index=32 page_size=2

Context Example

{
    "ServiceDeskPlus": {
        "Request": [
            {
                "CancellationRequested": false,
                "CreatedTime": "2020-06-30T15:27:33.000Z",
                "DisplayId": "172",
                "HasNotes": false,
                "Id": "123640000000279007",
                "IsServiceRequest": false,
                "Requester": {
                    "email_id": null,
                    "is_technician": false,
                    "sms_mail": null,
                    "phone": null,
                    "name": "First Last",
                    "mobile": null,
                    "id": "123640000000244019",
                    "photo_url": "https://contacts.zoho.com/file?exp=10&ID=-1&t=user&height=60&width=60",
                    "is_vip_user": false,
                    "department": null
                },
                "Status": "Open",
                "Subject": "Update Existing Request",
                "Template": {
                    "id": "123640000000006655",
                    "name": "Default Request"
                }
            },
            {
                "CancellationRequested": false,
                "CreatedTime": "2020-07-01T08:03:12.000Z",
                "DisplayId": "180",
                "HasNotes": false,
                "Id": "123640000000281015",
                "IsServiceRequest": false,
                "Requester": {
                    "department": null,
                    "email_id": null,
                    "id": "123640000000244019",
                    "is_technician": false,
                    "is_vip_user": false,
                    "mobile": null,
                    "name": "First Last",
                    "phone": null,
                    "photo_url": "https://contacts.zoho.com/file?exp=10&ID=-1&t=user&height=60&width=60",
                    "sms_mail": null
                },
                "Status": "Open",
                "Subject": "Create New Request",
                "Template": {
                    "id": "123640000000006655",
                    "name": "Default Request"
                }
            }
        ]
    }
}

Human Readable Output

Requests

CreatedTime Id Requester Status Subject
2020-06-30T15:27:33.000Z 123640000000279007 First Last Open Update Existing Request
2020-07-01T08:03:12.000Z 123640000000281015 First Last Open Create New Request

service-desk-plus-request-delete


Deletes the request with the specified ID. To delete multiple IDs, pass a comma-separated list (string) of the IDs of the requests.

Base Command

service-desk-plus-request-delete

Input

Argument Name Description Required
request_id The id of the request that should be deleted Required

Context Output

There is no context output for this command.

Command Example

!service-desk-plus-request-delete request_id=123640000000279019

Human Readable Output

Successfully deleted request(s) 123640000000279019

service-desk-plus-request-create


Create new requests

Base Command

service-desk-plus-request-create

Input

Argument Name Description Required
subject Subject of this request Required
description Description of this request Optional
request_type Type of this request. Choose one of the listed options or provide a dictionary representing a request_type object. Optional
impact_details Description about the impact of the request; must not exceed 250 characters. Optional
impact Impact of this request. Choose one of the listed options or provide a dictionary representing an impact object. Optional
status Indicates the current status of this request. Choose one of the listed options or provide a dictionary representing a status object. Optional
mode The mode in which the request is created. Choose one of the listed options or provide a dictionary representing a mode object. Optional
level Level of the request. Choose one of the listed options or provide a dictionary representing a level object. Optional
urgency Urgency of the request. Choose one of the listed options or provide a dictionary representing an urgency object. Optional
priority Priority of the request. Choose one of the listed options or provide a dictionary representing a priority object. Optional
service_category Service category to which this request belongs. String representing the category’s name. Optional
requester Indicates the requester of this request. Type the name of the requester as a string or a dictionary representing a requester object. Optional
assets Array of asset objects associated to this request Optional
site Denotes the site to which this request belongs. Type a site name or provide a dictionary representing a site object. Optional
group Group to which this request belongs. Type the name of the group or a dictionary representing a group object. Optional
technician Technician assigned to this request. Type the name of the technician or a dictionary representing a technician object. Optional
category Category to which the request belongs. Fill in the name of the category or a dictionary representing a category object. Optional
subcategory Subcategory to which this request belongs. Fill in the name of the subcategory or a dictionary representing a subcategory object. Optional
item Item of this request. Fill in the item’s name or a dictionary representing an item object. Optional
email_ids_to_notify Array of Email ids, which needs to be notified about the happenings of this request Optional
is_fcr Boolean value indicating if the request has been marked as First Call Resolution Optional
resources Holds the resource data mapped to the request Optional
udf_fields Holds udf fields’ values associated with the request. Input format: A string of the form “key1:value1,key2:value2” or a dictionary of the form “{‘key1’:’val1’,’key2’:’val2’}” Optional

Note:

Fields that represent an object can be filled either by providing the name only or the entire object. For example, the technician parameter can be defined either by filling
technician=”First Last” or technician=”{‘name’:’First Last’, ‘id’:’12345’, ‘email_id’:’email@paloaltonetworks.com’}”

Context Output

Path Type Description
ServiceDeskPlus.Request.Requester Unknown The requester of the request
ServiceDeskPlus.Request.CreatedTime Date The time the request was created
ServiceDeskPlus.Request.Template Unknown The template that was used to create the request
ServiceDeskPlus.Request.DisplayId String The display id of the request
ServiceDeskPlus.Request.Id String The unique id of the request
ServiceDeskPlus.Request.Subject String The subject of the request
ServiceDeskPlus.Request.Technician String The technician that was assigned to the request
ServiceDeskPlus.Request.Status String The status of the request
ServiceDeskPlus.Request.DueByTime Date The due date of the request
ServiceDeskPlus.Request.Group String The group to which the request belongs
ServiceDeskPlus.Request.IsServiceRequest Boolean Indicates whether the request is a service request or not
ServiceDeskPlus.Request.CancellationRequested Boolean Indicates whether a cancellation was requested
ServiceDeskPlus.Request.HasNotes Boolean Indicates whether the command has notes or not

Command Example

!service-desk-plus-request-create subject="Create New Request" requester="{'name':'First Last'}" impact_details="Workflow affected"

Context Example

{
    "ServiceDeskPlus": {
        "Request": {
            "CancellationRequested": false,
            "CreatedBy": {
                "department": null,
                "email_id": "email@paloaltonetworks.com",
                "id": "123640000000142582",
                "is_technician": true,
                "is_vip_user": false,
                "mobile": null,
                "name": "First Last",
                "phone": null,
                "photo_url": "https://contacts.zoho.com/file?exp=10&ID=712874208&t=user&height=60&width=60",
                "sms_mail": null
            },
            "CreatedTime": "2020-07-01T08:17:06.000Z",
            "DisplayId": "181",
            "HasAttachments": false,
            "HasLinkedRequests": false,
            "HasNotes": false,
            "HasProblem": false,
            "HasProject": false,
            "HasRequestInitiatedChange": false,
            "Id": "123640000000276021",
            "ImpactDetails": "Workflow affected",
            "IsEscalated": false,
            "IsFcr": false,
            "IsFirstResponseOverdue": false,
            "IsOverdue": false,
            "IsRead": false,
            "IsReopened": false,
            "IsServiceRequest": false,
            "IsTrashed": false,
            "LastUpdatedTime": "2020-07-01T08:17:06.000Z",
            "Requester": {
                "department": null,
                "email_id": null,
                "id": "123640000000244019",
                "is_technician": false,
                "is_vip_user": false,
                "mobile": null,
                "name": "First Last",
                "phone": null,
                "photo_url": "https://contacts.zoho.com/file?exp=10&ID=-1&t=user&height=60&width=60",
                "sms_mail": null
            },
            "Status": "Open",
            "Subject": "Create New Request",
            "Template": {
                "id": "123640000000006655",
                "name": "Default Request"
            },
            "TimeElapsed": "0",
            "UdfFields": {
                "udf_char2": "def test"
            },
            "UnrepliedCount": 0
        }
    }
}

Human Readable Output

Service Desk Plus request was successfully created

CreatedTime Id Requester Status Subject
2020-07-01T08:17:06.000Z 123640000000276021 First Last Open Create New Request

service-desk-plus-request-update


Update the request with the given request id.

Base Command

service-desk-plus-request-update

Input

Argument Name Description Required
request_id The ID of this request Required
subject Subject of this request Optional
description Description of this request Optional
request_type Type of this request. Choose one of the listed options or provide a dictionary representing a request_type object. Optional
impact Impact of this request. Choose one of the listed options or provide a dictionary representing an impact object. Optional
status Indicates the current status of this request. Choose one of the listed options or provide a dictionary representing a status object. Optional
mode The mode in which the request is created. Choose one of the listed options or provide a dictionary representing a mode object. Optional
level Level of the request. Choose one of the listed options or provide a dictionary representing a level object. Optional
urgency Urgency of the request. Choose one of the listed options or provide a dictionary representing an urgency object. Optional
priority Priority of the request. Choose one of the listed options or provide a dictionary representing a priority object. Optional
service_category Service category to which this request belongs. String representing the category’s name. Optional
requester Indicates the requester of this request. Type the name of the requester as a string or a dictionary representing a requester object. Optional
assets Array of asset objects associated to this request Optional
site Denotes the site to which this request belongs. Type a site name or provide a dictionary representing a site object. Optional
group Group to which this request belongs. Type the name of the group or a dictionary representing a group object. Optional
technician Technician assigned to this request. Type the name of the technician or a dictionary representing a technician object. Optional
category Category to which the request belongs. Fill in the name of the category or a dictionary representing a category object. Optional
subcategory Subcategory to which this request belongs. Fill in the name of the subcategory or a dictionary representing a subcategory object. Optional
item Item of this request. Fill in the item’s name or a dictionary representing an item object. Optional
email_ids_to_notify Array of Email ids, which needs to be notified about the happenings of this request Optional
is_fcr Boolean value indicating if the request has been marked as First Call Resolution Optional
resources Holds the resource data mapped to the request Optional
udf_fields Holds udf fields’ values associated with the request. Input format: A string of the form “key1:value1,key2:value2” or a dictionary of the form “{‘key1’:’val1’,’key2’:’val2’}” Optional
update_reason The reason for updating this request Optional

Context Output

Path Type Description
ServiceDeskPlus.Request.Requester Unknown The requester of the request
ServiceDeskPlus.Request.CreatedTime Date The time the request was created
ServiceDeskPlus.Request.Template Unknown The template that was used to create the request
ServiceDeskPlus.Request.DisplayId String The display id of the request
ServiceDeskPlus.Request.Id String The unique id of the request
ServiceDeskPlus.Request.Subject String The subject of the request
ServiceDeskPlus.Request.Technician String The technician that was assigned to the request
ServiceDeskPlus.Request.Status String The status of the request
ServiceDeskPlus.Request.DueByTime Date The due date of the request
ServiceDeskPlus.Request.Group String The group to which the request belongs
ServiceDeskPlus.Request.IsServiceRequest Boolean Indicates whether the request is a service request or not
ServiceDeskPlus.Request.CancellationRequested Boolean Indicates whether a cancellation was requested
ServiceDeskPlus.Request.HasNotes Boolean Indicates whether the command has notes or not

Command Example

!service-desk-plus-request-update request_id=123640000000284007 subject="Update Existing Request"

Context Example

{
    "ServiceDeskPlus": {
        "Request": {
            "CancellationRequested": false,
            "CreatedBy": {
                "department": null,
                "email_id": "email@paloaltonetworks.com",
                "id": "123640000000142582",
                "is_technician": true,
                "is_vip_user": false,
                "mobile": null,
                "name": "First Last",
                "phone": null,
                "photo_url": "https://contacts.zoho.com/file?exp=10&ID=712874208&t=user&height=60&width=60",
                "sms_mail": null
            },
            "CreatedTime": "2020-07-01T07:41:14.000Z",
            "DisplayId": "177",
            "Group": {
                "deleted": false,
                "id": "123640000000006681",
                "name": "Network",
                "site": null
            },
            "HasAttachments": false,
            "HasLinkedRequests": false,
            "HasNotes": false,
            "HasProblem": false,
            "HasProject": false,
            "HasRequestInitiatedChange": false,
            "Id": "123640000000284007",
            "IsEscalated": false,
            "IsFcr": false,
            "IsFirstResponseOverdue": false,
            "IsOverdue": false,
            "IsRead": false,
            "IsReopened": true,
            "IsServiceRequest": false,
            "IsTrashed": false,
            "LastUpdatedTime": "2020-07-01T08:17:08.000Z",
            "Requester": {
                "department": null,
                "email_id": null,
                "id": "123640000000244019",
                "is_technician": false,
                "is_vip_user": false,
                "mobile": null,
                "name": "First Last",
                "phone": null,
                "photo_url": "https://contacts.zoho.com/file?exp=10&ID=-1&t=user&height=60&width=60",
                "sms_mail": null
            },
            "Resolution": {
                "content": "Resolution Content"
            },
            "RespondedTime": "2020-07-01T08:12:31.000Z",
            "Status": "Open",
            "Subject": "Update Existing Request",
            "Template": {
                "id": "123640000000006655",
                "name": "Default Request"
            },
            "TimeElapsed": "0",
            "UdfFields": {
                "udf_char2": "def test"
            },
            "UnrepliedCount": 0
        }
    }
}

Human Readable Output

Service Desk Plus request was successfully updated

CreatedTime Id Requester Status Subject
2020-07-01T07:41:14.000Z 123640000000284007 First Last Open Update Existing Request

service-desk-plus-request-assign


Assigns the request with the given request id to a technician/group

Base Command

service-desk-plus-request-assign

Input

Argument Name Description Required
request_id The id of the request that should be assigned Required
technician The name of the technician that should be assigned to the request Optional
group The name of the group that should be assigned to the request Optional

Context Output

There is no context output for this command.

Command Example

!service-desk-plus-request-assign request_id=123640000000284007 group="Network"

Context Example

{}

Human Readable Output

Service Desk Plus request 123640000000284007 was successfully assigned

service-desk-plus-request-pickup


Allows the technician to pickup the request with the given request id on his name.

Base Command

service-desk-plus-request-pickup

Input

Argument Name Description Required
request_id The id of the request that should be picked up Required

Context Output

There is no context output for this command.

Command Example

!service-desk-plus-request-pickup request_id=123640000000279001

Context Example

{}

Human Readable Output

Service Desk Plus request 123640000000279001 was successfully picked up

service-desk-plus-linked-request-list


Gets a list with all the linked requests under a request

Base Command

service-desk-plus-linked-request-list

Input

Argument Name Description Required
request_id The request for which the linked requests are requested Required

Context Output

Path Type Description
ServiceDeskPlus.Request.LinkRequests.Comments Unknown The comment that was added to the linked request
ServiceDeskPlus.Request.LinkRequests.LinkedRequest Unknown The linked request information

Command Example

!service-desk-plus-linked-request-list request_id=123640000000284007

Context Example

{
    "ServiceDeskPlus": {
        "Request": {
            "LinkRequests": [
                {
                    "LinkedRequest": {
                        "display_id": "179",
                        "id": "123640000000288001",
                        "subject": "Request for List",
                        "udf_fields": {
                            "udf_char1": null,
                            "udf_char2": "def test",
                            "udf_char3": null
                        }
                    }
                }
            ]
        }
    }
}

Human Readable Output

Linked requests to request 123640000000284007

LinkedRequest
subject: Request for List
id: 123640000000288001
udf_fields: {“udf_char1”: null, “udf_char2”: “def test”, “udf_char3”: null}
display_id: 179

service-desk-plus-request-resolution-add


Adds a resolution to the given request

Base Command

service-desk-plus-request-resolution-add

Input

Argument Name Description Required
request_id The id of the request for which the resolution should be added Required
resolution_content The content of the resolution that should be added to the request Optional
add_to_linked_requests A boolean value indicating whether the same resolution should be added to all linked request of the request Optional

Context Output

There is no context output for this command.

Command Example

!service-desk-plus-request-resolution-add request_id=123640000000284007 resolution_content="Resolution Content"

Context Example

{}

Human Readable Output

Resolution was successfully added to 123640000000284007

service-desk-plus-request-resolutions-list


Gets the resolution to the given request

Base Command

service-desk-plus-request-resolutions-list

Input

Argument Name Description Required
request_id The id of the request for which the resolution is desired Required

Context Output

Path Type Description
ServiceDeskPlus.Request.Resolution.Content Unknown The content of the resolution of the request
ServiceDeskPlus.Request.Resolution.SubmittedBy Unknown The details of who submitted the resolution
ServiceDeskPlus.Request.Resolution.SubmittedOn Unknown The date the resolution was submitted
ServiceDeskPlus.Request.Resolution.ResolutionAttachments Unknown The attachments that were added to the resolution

Command Example

!service-desk-plus-request-resolutions-list request_id=123640000000284007

Context Example

{
    "ServiceDeskPlus": {
        "Request": {
            "Resolution": {
                "Content": "Resolution Content",
                "SubmittedBy": {
                    "department": null,
                    "email_id": "email@paloaltonetworks.com",
                    "id": "123640000000142582",
                    "is_technician": true,
                    "is_vip_user": false,
                    "mobile": null,
                    "name": "First Last",
                    "phone": null,
                    "photo_url": "https://contacts.zoho.com/file?exp=10&ID=712874208&t=user&height=60&width=60",
                    "sms_mail": null
                },
                "SubmittedOn": "2020-07-01T08:12:31.000Z"
            }
        }
    }
}

Human Readable Output

Resolution of request 123640000000284007

Content SubmittedBy SubmittedOn
Resolution Content First Last 2020-07-01T08:12:31.000Z

service-desk-plus-generate-refresh-token


This function generates the refresh token that should be used in the instance configurations

Base Command

service-desk-plus-generate-refresh-token

Input

Argument Name Description Required
code The code received when creating the application Required

Context Output

There is no context output for this command.

Command Example

!service-desk-plus-generate-refresh-token code=1000.ceb0e1b783c1e9647171e46a92bb444c.61adef3ee9d50c4a83e505ceee06d5b4

Context Example

{}

Human Readable Output

###Refresh Token: 1000.75a65edeb42492fedd0215d301d7a136.1ab14n19a900950205b44d033472de49
Please paste the Refresh Token in the instance configuration and save it for future use.

service-desk-plus-link-request-modify


Link or Unlink multiple commands

Base Command

service-desk-plus-link-request-modify

Input

Argument Name Description Required
request_id The id of the request for which the links should be modified Required
action Link / Unlink this request with the given requests Required
linked_requests_id The IDs of the requests that should be linked to the given request. Multiple IDs can be passed, separated by a comma Required
comment The comment that should be added when linking requests (optional). Optional

Context Output

There is no context output for this command.

Command Example

!service-desk-plus-link-request-modify action=Link request_id=123640000000284007 linked_requests_id=123640000000288001

Context Example

{}

Human Readable Output

Request successfully linked

service-desk-plus-request-close


Closes the specified request.

Base Command

service-desk-plus-request-close

Input

Argument Name Description Required
closure_comments The comments that should be added when closing the request Optional
closure_code The closure code to add to the request. Can be “Cancelled”, “Failed”, “Postponed”, “Rejected”, “Success” or “Unable to Reproduce” Optional
requester_ack_comments The requester comments that should be added to the request Optional
requester_ack_resolution Boolean. Optional
request_id The id of the request that should be closed Required

Context Output

There is no context output for this command.

Command Example

!service-desk-plus-request-close request_id=123640000000288001

Context Example

{}

Human Readable Output

Successfully closed request 123640000000288001

service-desk-plus-request-notes-list


Get a request specific note, or list all of its notes.

Base Command

service-desk-plus-request-notes-list

Input

Argument Name Description Required
request_id The ID of the request to retrieve its notes. Required
request_note_id The ID of the note to retrieve it’s data. Optional

Context Output

Path Type Description
ServiceDeskPlus.Request.Note.mark_first_response Boolean Mark first response or not.
ServiceDeskPlus.Request.Note.created_time.display_value Date Created time display value.
ServiceDeskPlus.Request.Note.created_time.value Date Created time value.
ServiceDeskPlus.Request.Note.add_to_linked_requests Boolean Add to linked requests or not.
ServiceDeskPlus.Request.Note.request.display_id String Request display id.
ServiceDeskPlus.Request.Note.request.subject String Request subject.
ServiceDeskPlus.Request.Note.request.id String Request id.
ServiceDeskPlus.Request.Note.notify_technician Boolean Notify technician or not.
ServiceDeskPlus.Request.Note.show_to_requester Boolean Show to requester or not.
ServiceDeskPlus.Request.Note.description String Note description.
ServiceDeskPlus.Request.Note.id Date Note id.
ServiceDeskPlus.Request.Note.created_by.email_id String Email id of the creator.
ServiceDeskPlus.Request.Note.created_by.is_technician Boolean Whether the creator is technician or not.
ServiceDeskPlus.Request.Note.created_by.sms_mail String Creator sms mail.
ServiceDeskPlus.Request.Note.created_by.phone String Creator phone.
ServiceDeskPlus.Request.Note.created_by.name String Creator name.
ServiceDeskPlus.Request.Note.created_by.mobile String Creator mobile.
ServiceDeskPlus.Request.Note.created_by.id Date Creator id.
ServiceDeskPlus.Request.Note.created_by.photo_url String Creator photo url.
ServiceDeskPlus.Request.Note.created_by.is_vip_user Boolean Whether creator is vip user or not.

service-desk-plus-request-notes-add


add a request note.

Base Command

service-desk-plus-request-notes-add

Input

Argument Name Description Required
request_id The ID of the request to retrieve it’s notes. Required
description The note description. Optional
mark_first_response whether to mark first response or not. Possible values are: true, false. Default is false. Optional
add_to_linked_requests whether to add to linked requests or not. Possible values are: true, false. Default is false. Optional
notify_technician whether to notify technician. Possible values are: true, false. Default is false. Optional
show_to_requester whether to show to requester. Possible values are: true, false. Default is false. Optional

Context Output

Path Type Description
ServiceDeskPlus.Request.Note.mark_first_response Boolean mark first response or not.
ServiceDeskPlus.Request.Note.created_time.display_value Date created time display value.
ServiceDeskPlus.Request.Note.created_time.value Date created time value.
ServiceDeskPlus.Request.Note.add_to_linked_requests Boolean add to linked requests or not.
ServiceDeskPlus.Request.Note.request.display_id String request display id.
ServiceDeskPlus.Request.Note.request.subject String request subject.
ServiceDeskPlus.Request.Note.request.id Date request id.
ServiceDeskPlus.Request.Note.notify_technician Boolean notify technician or not.
ServiceDeskPlus.Request.Note.show_to_requester Boolean show to requester or not.
ServiceDeskPlus.Request.Note.description String note description.
ServiceDeskPlus.Request.Note.id Date note id.
ServiceDeskPlus.Request.Note.created_by.email_id String email id of the creator.
ServiceDeskPlus.Request.Note.created_by.is_technician Boolean whether the creator is technician or not.
ServiceDeskPlus.Request.Note.created_by.sms_mail String creator sms mail.
ServiceDeskPlus.Request.Note.created_by.phone String creator phone.
ServiceDeskPlus.Request.Note.created_by.name String creator name.
ServiceDeskPlus.Request.Note.created_by.mobile String creator mobile.
ServiceDeskPlus.Request.Note.created_by.id Date creator id.
ServiceDeskPlus.Request.Note.created_by.photo_url String creator photo url.
ServiceDeskPlus.Request.Note.created_by.is_vip_user Boolean whether creator is vip user or not.

service-desk-plus-request-notes-delete


delete a request note.

Base Command

service-desk-plus-request-notes-delete

Input

Argument Name Description Required
request_id The ID of the request to delete it’s notes. Required
request_note_id The ID of the note to delete. Required

Context Output

There is no context output for this command.

service-desk-plus-request-notes-update


update a request note.

Base Command

service-desk-plus-request-notes-update

Input

Argument Name Description Required
request_note_id The ID of the note to retrieve it’s data. Required
request_id The ID of the request to retrieve it’s notes. Required
description The note description. Required
mark_first_response whether to mark first response or not. Possible values are: true, false. Default is false. Optional
add_to_linked_requests whether to add to linked requests or not. Possible values are: true, false. Default is false. Optional
notify_technician whether to notify technician. Possible values are: true, false. Default is false. Optional
show_to_requester whether to show to requester. Possible values are: true, false. Default is false. Optional

Context Output

Path Type Description
ServiceDeskPlus.Request.Note.mark_first_response Boolean mark first response or not.
ServiceDeskPlus.Request.Note.created_time.display_value Date created time display value.
ServiceDeskPlus.Request.Note.created_time.value Date created time value.
ServiceDeskPlus.Request.Note.add_to_linked_requests Boolean add to linked requests or not.
ServiceDeskPlus.Request.Note.request.display_id String request display id.
ServiceDeskPlus.Request.Note.request.subject String request subject.
ServiceDeskPlus.Request.Note.request.id Date request id.
ServiceDeskPlus.Request.Note.notify_technician Boolean notify technician or not.
ServiceDeskPlus.Request.Note.show_to_requester Boolean show to requester or not.
ServiceDeskPlus.Request.Note.description String note description.
ServiceDeskPlus.Request.Note.id Date note id.
ServiceDeskPlus.Request.Note.created_by.email_id String email id of the creator.
ServiceDeskPlus.Request.Note.created_by.is_technician Boolean whether the creator is technician or not.
ServiceDeskPlus.Request.Note.created_by.sms_mail String creator sms mail.
ServiceDeskPlus.Request.Note.created_by.phone String creator phone.
ServiceDeskPlus.Request.Note.created_by.name String creator name.
ServiceDeskPlus.Request.Note.created_by.mobile String creator mobile.
ServiceDeskPlus.Request.Note.created_by.id Date creator id.
ServiceDeskPlus.Request.Note.created_by.photo_url String creator photo url.
ServiceDeskPlus.Request.Note.created_by.is_vip_user Boolean whether creator is vip user or not.

Configuration parameters

  • server_url — Data Center Location: Select the domain location that is applicable to your application (required)
  • technician_key — Technician Key
  • credentials_technician_key
  • server_url_on_premise — On-Premise Server URL
  • client_id — Client ID
  • client_secret — Client Secret
  • credentials_client — Client ID
  • refresh_token — Refresh Token
  • credentials_refresh_token
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • isFetch — Fetch incidents
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval
  • fetch_status — Incidents status. Supports multi-select.
  • fetch_time — First fetch time
  • fetch_limit — Fetch limit - maximum number of incidents per fetch
  • fetch_filter — Fetch query - see (?). Overrides the status filter, if given.

Commands (16)

  • service-desk-plus-generate-refresh-token

    This function generates the refresh token that should be used in the instance configurations.

  • service-desk-plus-link-request-modify

    Links or unlinks multiple commands.

  • service-desk-plus-linked-request-list

    Gets a list with all the linked requests under the specified request.

  • service-desk-plus-request-assign

    Assigns the specified request to a technician and/or group.

  • service-desk-plus-request-close

    Closes the specified request.

  • service-desk-plus-request-create

    Creates new requests.

  • service-desk-plus-request-delete

    Deletes the request with the specified ID. To delete multiple IDs, pass a comma-separated list (string) of the IDs of the requests.

  • service-desk-plus-request-notes-add

    Adds a request note.

  • service-desk-plus-request-notes-delete

    Deletes a request note.

  • service-desk-plus-request-notes-list

    Get a request specific note, or list all of its notes.

  • service-desk-plus-request-notes-update

    Updates a request note.

  • service-desk-plus-request-pickup

    Allows a technician to pickup the specified with the given request ID on the technician's name.

  • service-desk-plus-request-resolution-add

    Adds a resolution to the specified request.

  • service-desk-plus-request-resolutions-list

    Gets the resolution of the specified request.

  • service-desk-plus-request-update

    Updates the specified request.

  • service-desk-plus-requests-list

    View the details of requests. If no arguments are passed, details of all requests are returned.

import demistomock as demisto
import pytest
from ServiceDeskPlus import (
    Client,
    OAUTH_URL,
    add_resolution_command,
    args_to_query,
    assign_request_command,
    close_request_command,
    create_fetch_list_info,
    create_human_readable,
    create_modify_linked_input_data,
    create_output,
    create_request_command,
    create_requests_list_info,
    create_udf_field,
    delete_request_command,
    fetch_incidents,
    generate_refresh_token,
    get_resolutions_list_command,
    linked_request_command,
    list_requests_command,
    modify_linked_request_command,
    pickup_request_command,
    resolution_human_readable,
    update_request_command,
)
from test_data.response_constants import (
    RESPONSE_CREATE_REQUEST,
    RESPONSE_FETCH_INCIDENTS,
    RESPONSE_GENERATE_REFRESH_TOKEN,
    RESPONSE_LINK_REQUEST,
    RESPONSE_LINKED_REQUEST_LIST,
    RESPONSE_LIST_MULTIPLE_REQUESTS,
    RESPONSE_LIST_SINGLE_REQUEST,
    RESPONSE_NO_RESOLUTION_LIST,
    RESPONSE_RESOLUTION_LIST,
    RESPONSE_UNLINK_REQUEST,
    RESPONSE_UPDATE_REQUEST,
    RESPONSE_GET_NOTES_LIST,
    RESPONSE_GET_NOTE,
    RESPONSE_ADD_NOTE,
    RESPONSE_DELETE_NOTE,
    RESPONSE_UPDATE_NOTE,
)
from test_data.result_constants import (
    EXPECTED_CREATE_REQUEST,
    EXPECTED_LINKED_REQUEST_LIST,
    EXPECTED_LIST_MULTIPLE_REQUESTS,
    EXPECTED_LIST_SINGLE_REQUEST,
    EXPECTED_NO_RESOLUTION_LIST,
    EXPECTED_RESOLUTION_LIST,
    EXPECTED_UPDATE_REQUEST,
    EXPECTED_GET_NOTES_LIST,
    EXPECTED_GET_NOTE,
    EXPECTED_ADD_NOTE,
    EXPECTED_DELETE_NOTE,
    EXPECTED_UPDATE_NOTE,
)

COMMANDS_LIST_WITH_CONTEXT = [
    # Given the create command, different fields that should be used to create the request, the response of the command
    # and the expected result, validate that the output of the command and the expected result are identical
    (
        create_request_command,
        {
            "subject": "Create request test",
            "mode": "E-Mail",
            "requester": "First Last",
            "level": "Tier 1",
            "impact": "Affects Group",
            "priority": "High",
            "status": "On Hold",
            "request_type": "Incident",
            "description": "The description of the request",
            "urgency": "Normal",
            "group": "Network",
        },
        RESPONSE_CREATE_REQUEST,
        EXPECTED_CREATE_REQUEST,
    ),
    # Given the update command, different fields that should be used to create the request, the response of the command
    # and the expected result, validate that the output of the command and the expected result are identical
    (
        update_request_command,
        {"request_id": "123640000000240013", "description": "Update the description", "impact": "Affects Business"},
        RESPONSE_UPDATE_REQUEST,
        EXPECTED_UPDATE_REQUEST,
    ),
    # Given list requests command, the id of the single request that should be returned, validate that the output
    # context of the command is identical to the expected output
    (list_requests_command, {"request_id": "123640000000240013"}, RESPONSE_LIST_SINGLE_REQUEST, EXPECTED_LIST_SINGLE_REQUEST),
    # Given list requests command, page size equal 3 and the response for 3 requests, validate that the output
    # context of the command is identical to the expected output
    (list_requests_command, {"page_size": "3"}, RESPONSE_LIST_MULTIPLE_REQUESTS, EXPECTED_LIST_MULTIPLE_REQUESTS),
    # Given the linked request command, the id of the request that it's links should be checked and the response for the
    # command, validate that the context output of the command is identical to the expected output.
    (linked_request_command, {"request_id": "123640000000246001"}, RESPONSE_LINKED_REQUEST_LIST, EXPECTED_LINKED_REQUEST_LIST),
    # Given the get resolutions list command, the id of the request for which the resolution should be returned and the
    # response in case there IS a resolution for the request, validate the context output of the command
    (get_resolutions_list_command, {"request_id": "123640000000241001"}, RESPONSE_RESOLUTION_LIST, EXPECTED_RESOLUTION_LIST),
    # Given the get resolutions list command, the id of the request for which the resolution should be returned and the
    # response in case there is NO a resolution for the request, validate the context output of the command
    (
        get_resolutions_list_command,
        {"request_id": "123640000000241001"},
        RESPONSE_NO_RESOLUTION_LIST,
        EXPECTED_NO_RESOLUTION_LIST,
    ),
]

COMMANDS_LIST_WITHOUT_CONTEXT = [
    # Given the delete command and the id of the request that should be deleted, validate the human readable output
    (delete_request_command, {"request_id": "1234"}, {}, "### Successfully deleted request(s) ['1234']"),
    # Given the delete command and multiple ids of requests that should be deleted, validate the human readable output
    (delete_request_command, {"request_id": "1234,5678"}, {}, "### Successfully deleted request(s) ['1234', '5678']"),
    # Given the close command and the id of the request that should be closed, validate the human readable output
    (close_request_command, {"request_id": "1234"}, {}, "### Successfully closed request 1234"),
    # Given the assign command and the id of the request that should be assigned, validate the human readable output
    (assign_request_command, {"request_id": "1234"}, {}, "### Service Desk Plus request 1234 was successfully assigned"),
    # Given the pickup command and the id of the request that should be picked up, validate the human readable output
    (pickup_request_command, {"request_id": "1234"}, {}, "### Service Desk Plus request 1234 was successfully picked up"),
    # Given the modify linked command with the 'Link' action and the ids of the requests that should be linked, verify
    # that the human readable indicates that the requests were successfully linked
    (
        modify_linked_request_command,
        {"action": "Link", "request_id": "1234", "linked_requests_id": "5678"},
        RESPONSE_LINK_REQUEST,
        "## Request successfully linked",
    ),
    # Given the modify linked command with the 'Unlink' action and the ids of the requests that should be linked, verify
    # that the human readable indicates that the requests were successfully unlinked
    (
        modify_linked_request_command,
        {"action": "Unlink", "request_id": "1234", "linked_requests_id": "5678"},
        RESPONSE_UNLINK_REQUEST,
        "## The request[s] link are removed successfully.",
    ),
    # Given the add resolution command, the id of the request the resolution should be added to, the resolution content
    # and the add_to_linked_requests flag set to true, validate the human readable output
    (
        add_resolution_command,
        {"request_id": "1234", "resolution_content": "resolution message", "add_to_linked_requests": "true"},
        RESPONSE_UNLINK_REQUEST,
        "### Resolution was successfully added to 1234 and the linked requests",
    ),
    # Given the add resolution command, the id of the request the resolution should be added to, the resolution content
    # and the add_to_linked_requests flag set to true, validate the human readable output
    (
        add_resolution_command,
        {"request_id": "1234", "resolution_content": "resolution message", "add_to_linked_requests": "false"},
        RESPONSE_UNLINK_REQUEST,
        "### Resolution was successfully added to 1234",
    ),
]

REFRESH_TOKEN_COMMAND_CLOUD = [
    # Given the generate refresh token command, a valid code that should be used and the response for this command,
    # validate the human readable output
    (
        generate_refresh_token,
        {"code": "147852369"},
        RESPONSE_GENERATE_REFRESH_TOKEN,
        "### Refresh Token: 987654321\n "
        "Please paste the Refresh Token in"
        " the instance configuration and "
        "save it for future use.",
    ),
    # Given the generate refresh token command, a code and an error message as the response, validate that the human
    # readable is indicating an error.
    (generate_refresh_token, {"code": "147852369"}, {"error": "invalid_code"}, "### Error: invalid_code"),
]


# test commands with context:
@pytest.mark.parametrize("command, args, response, expected_result", COMMANDS_LIST_WITH_CONTEXT)
def test_commands_cloud(command, args, response, expected_result, mocker):
    mocker.patch("ServiceDeskPlus.Client.get_access_token")
    client = Client(
        "server_url",
        "https://accounts.zoho.com/oauth/v2/token",
        "use_ssl",
        "use_proxy",
        "client_id",
        "client_secret",
        "refresh_token",
    )
    mocker.patch.object(client, "http_request", return_value=response)
    result = command(client, args)
    assert expected_result == result[1]


# test commands with context:
@pytest.mark.parametrize("command, args, response, expected_result", COMMANDS_LIST_WITH_CONTEXT)
def test_commands_on_premise(command, args, response, expected_result, mocker):
    client = Client(
        "server_url",
        "https://accounts.zoho.com/oauth/v2/token",
        "use_ssl",
        "use_proxy",
        technician_key="technician_key",
        on_premise=True,
    )
    mocker.patch.object(client, "http_request", return_value=response)
    result = command(client, args)
    assert expected_result == result[1]


# test commands without context:
@pytest.mark.parametrize("command, args, response, expected_result", COMMANDS_LIST_WITHOUT_CONTEXT)
def test_command_hr_cloud(command, args, response, expected_result, mocker):
    mocker.patch("ServiceDeskPlus.Client.get_access_token")
    client = Client(
        "server_url",
        "https://accounts.zoho.com/oauth/v2/token",
        "use_ssl",
        "use_proxy",
        "client_id",
        "client_secret",
        "refresh_token",
    )
    mocker.patch.object(client, "http_request", return_value=response)
    result = command(client, args)
    assert expected_result == result[0]


# test commands without context:
@pytest.mark.parametrize("command, args, response, expected_result", COMMANDS_LIST_WITHOUT_CONTEXT)
def test_command_hr_on_premise(command, args, response, expected_result, mocker):
    client = Client(
        "server_url",
        "https://accounts.zoho.com/oauth/v2/token",
        "use_ssl",
        "use_proxy",
        technician_key="technician_key",
        on_premise=True,
    )
    mocker.patch.object(client, "http_request", return_value=response)
    result = command(client, args)
    assert expected_result == result[0]


@pytest.mark.parametrize("command, args, response, expected_result", REFRESH_TOKEN_COMMAND_CLOUD)
def test_refresh_token_command_cloud(command, args, response, expected_result, mocker):
    mocker.patch("ServiceDeskPlus.Client.get_access_token")
    client = Client(
        "server_url",
        "https://accounts.zoho.com/oauth/v2/token",
        "use_ssl",
        "use_proxy",
        "client_id",
        "client_secret",
        "refresh_token",
    )
    mocker.patch.object(client, "http_request", return_value=response)
    result = command(client, args)
    assert expected_result == result[0]


def test_refresh_token_command_on_premise(mocker):
    """
    Given:
        - on-premise client

    When:
        - run refresh-token command

    Then:
        - Returns an error that this command cannot be executed for on-premise.

    """
    mocker.patch("ServiceDeskPlus.Client.get_access_token")
    client = Client("server_url", "dummy_oauth_url", "use_ssl", "use_proxy", technician_key="technician_key", on_premise=True)
    mocker.patch.object(demisto, "results")
    with pytest.raises(SystemExit) as err:
        generate_refresh_token(client, "args")
    assert err.type is SystemExit
    assert demisto.results.call_count == 1
    # call_args is tuple (args list, kwargs). we only need the first one
    results = demisto.results.call_args[0]
    assert len(results) == 1
    assert "The command 'service-desk-plus-generate-refresh-token' can not be executed on on-premise." in results[0]["Contents"]


# test helper functions:
def test_create_output():
    input = RESPONSE_CREATE_REQUEST.get("request")
    expected_output = EXPECTED_CREATE_REQUEST.get("ServiceDeskPlus(val.ID===obj.ID)").get("Request")
    assert create_output(input) == expected_output


def test_args_to_query():
    assign_input = {"group": "group1", "technician": "tech name"}
    expected_assign_output = {"request": {"group": {"name": "group1"}, "technician": {"name": "tech name"}}}
    assert args_to_query(assign_input) == expected_assign_output

    create_input = {"subject": "request subject", "group": "group1", "impact": "Affects Business", "requester": "name"}
    expected_create_output = {
        "request": {
            "subject": "request subject",
            "group": {"name": "group1"},
            "impact": {"name": "Affects Business"},
            "requester": {"name": "name"},
        }
    }
    assert args_to_query(create_input) == expected_create_output


def test_create_modify_linked_input_data():
    linked_request_id = ["1234"]
    comment = "testing one request"
    expected_output = {"link_requests": [{"linked_request": {"id": "1234"}, "comments": "testing one request"}]}
    assert create_modify_linked_input_data(linked_request_id, comment) == expected_output

    linked_request_id = ["1234", "5678"]
    comment = "testing two request"
    expected_output = {
        "link_requests": [
            {"linked_request": {"id": "1234"}, "comments": "testing two request"},
            {"linked_request": {"id": "5678"}, "comments": "testing two request"},
        ]
    }
    assert create_modify_linked_input_data(linked_request_id, comment) == expected_output

    linked_request_id = ["1234", "5678", "0912"]
    expected_output = {
        "link_requests": [
            {"linked_request": {"id": "1234"}},
            {"linked_request": {"id": "5678"}},
            {"linked_request": {"id": "0912"}},
        ]
    }
    assert create_modify_linked_input_data(linked_request_id, "") == expected_output


def test_create_human_readable():
    input = {
        "CreatedTime": "creation_time",
        "Id": "1234",
        "Requester": {
            "name": "First Last",
            "mobile": None,
            "id": "123640000000244019",
            "photo_url": "url",
            "is_vip_user": False,
            "department": None,
        },
        "Technician": {"email_id": "i@id", "cost_per_hour": "0", "phone": None, "name": "tech1"},
        "Status": "Open",
        "Subject": "test human readable",
    }
    expected_output = {
        "CreatedTime": "creation_time",
        "Id": "1234",
        "Requester": "First Last",
        "Technician": "tech1",
        "Status": "Open",
        "Subject": "test human readable",
    }
    assert create_human_readable(input) == expected_output


def test_resolution_human_readable():
    input = {
        "Content": "res contents",
        "SubmittedOn": "submittion_date",
        "SubmittedBy": {"email_id": "i@id", "phone": None, "name": "submitter"},
    }
    expected_output = {"Content": "res contents", "SubmittedOn": "submittion_date", "SubmittedBy": "submitter"}
    assert resolution_human_readable(input) == expected_output


def test_create_requests_list_info():
    start_index, row_count, search_fields, filter_by = "0", "15", "a, b, c", "filter"
    expected_output = {
        "list_info": {
            "start_index": "0",
            "row_count": "15",
            "search_fields": "a, b, c",
            "filter_by": "filter",
            "sort_field": "created_time",
            "sort_order": "asc",
        }
    }
    assert create_requests_list_info(start_index, row_count, search_fields, filter_by) == expected_output


def test_create_fetch_list_info():
    # Check empty status list:
    time_from, time_to, status, fetch_filter, fetch_limit = "from", "to", [], "", 10
    expected_output = {
        "list_info": {
            "search_criteria": [{"field": "created_time", "values": ["from", "to"], "condition": "between"}],
            "sort_field": "created_time",
            "sort_order": "asc",
            "row_count": 10,
        }
    }
    assert create_fetch_list_info(time_from, time_to, status, fetch_filter, fetch_limit) == expected_output

    # Check one status:
    time_from, time_to, status, fetch_filter, fetch_limit = "from", "to", ["status"], "", 10
    expected_output = {
        "list_info": {
            "search_criteria": [
                {"field": "created_time", "values": ["from", "to"], "condition": "between"},
                {"field": "status.name", "values": ["status"], "condition": "is", "logical_operator": "AND"},
            ],
            "sort_field": "created_time",
            "sort_order": "asc",
            "row_count": 10,
        }
    }
    assert create_fetch_list_info(time_from, time_to, status, fetch_filter, fetch_limit) == expected_output

    # Check multiple status:
    time_from, time_to, status, fetch_filter, fetch_limit = "from", "to", ["status1", "status2"], "", 10
    expected_output = {
        "list_info": {
            "search_criteria": [
                {"field": "created_time", "values": ["from", "to"], "condition": "between"},
                {"field": "status.name", "values": ["status1", "status2"], "condition": "is", "logical_operator": "AND"},
            ],
            "sort_field": "created_time",
            "sort_order": "asc",
            "row_count": 10,
        }
    }
    assert create_fetch_list_info(time_from, time_to, status, fetch_filter, fetch_limit) == expected_output

    time_from, time_to, status, fetch_limit = "from", "to", ["status"], 15
    fetch_filter = "{'field': 'technician.name', 'values': 'tech1,tech2', 'condition': 'is', 'logical_operator':'AND'}"
    expected_output = {
        "list_info": {
            "search_criteria": [
                {"field": "created_time", "values": ["from", "to"], "condition": "between"},
                {"field": "technician.name", "condition": "is", "values": ["tech1", "tech2"], "logical_operator": "AND"},
            ],
            "sort_field": "created_time",
            "sort_order": "asc",
            "row_count": 15,
        }
    }
    assert create_fetch_list_info(time_from, time_to, status, fetch_filter, fetch_limit) == expected_output

    time_from, time_to, status, fetch_limit = "from", "to", ["status"], 20
    fetch_filter = (
        "{'field':'technician.name','values':'tech1,tech2','condition':'is','logical_operator':'AND'},"
        "{'field':'group.name','values':'group1','condition':'is','logical_operator':'AND'}"
    )
    expected_output = {
        "list_info": {
            "search_criteria": [
                {"field": "created_time", "values": ["from", "to"], "condition": "between"},
                {"field": "technician.name", "condition": "is", "values": ["tech1", "tech2"], "logical_operator": "AND"},
                {"field": "group.name", "condition": "is", "values": ["group1"], "logical_operator": "AND"},
            ],
            "sort_field": "created_time",
            "sort_order": "asc",
            "row_count": 20,
        }
    }
    assert create_fetch_list_info(time_from, time_to, status, fetch_filter, fetch_limit) == expected_output


def test_create_udf_field():
    udf_input = "key1:val1"
    expected_output = {"key1": "val1"}
    assert create_udf_field(udf_input) == expected_output

    udf_input = "{'key1':'val1'}"
    expected_output = {"key1": "val1"}
    assert create_udf_field(udf_input) == expected_output

    udf_input = "key1:val1,key2:val2"
    expected_output = {"key1": "val1", "key2": "val2"}
    assert create_udf_field(udf_input) == expected_output

    invalid_udf_inputs = ["key1:val1,key2", "key1,val1", "key1", ":val1"]
    for udf_input in invalid_udf_inputs:
        try:
            create_udf_field(udf_input)
        except Exception as e:
            assert "Illegal udf fields format" in e.args[0]


def test_fetch_incidents_cloud(mocker):
    """
    Unit test
    Given
    - fetch incidents command
    - command args
    - command raw response
    When
    - mock the parse_date_range.
    - mock the date_to_timestamp.
    - mock the create_fetch_list_info.
    - mock the Client's get_requests command.
    Then
    - run the fetch incidents command using the Client.
    Validate the length of the results and the different fields of the fetched incidents.
    """
    mocker.patch("ServiceDeskPlus.Client.get_access_token")
    mocker.patch("ServiceDeskPlus.parse_date_range", return_value=("2020-06-23 04:18:00", "never mind"))
    mocker.patch("ServiceDeskPlus.date_to_timestamp", return_value="1592918317168")
    mocker.patch("ServiceDeskPlus.create_fetch_list_info", return_value={})

    client = Client(
        "server_url",
        "use_ssl",
        "use_proxy",
        "client_id",
        "client_secret",
        "refresh_token",
        fetch_time="1 hour",
        fetch_limit=3,
        fetch_status=["Open"],
    )
    mocker.patch.object(client, "get_requests", return_value=RESPONSE_FETCH_INCIDENTS)
    incidents = fetch_incidents(client)
    assert len(incidents) == 3

    client = Client(
        "server_url",
        "use_ssl",
        "use_proxy",
        "client_id",
        "client_secret",
        "refresh_token",
        fetch_time="1 hour",
        fetch_limit=2,
        fetch_status=["Open"],
    )
    mocker.patch.object(client, "get_requests", return_value=RESPONSE_FETCH_INCIDENTS)
    incidents = fetch_incidents(client)
    assert len(incidents) == 2

    client = Client(
        "server_url",
        "use_ssl",
        "use_proxy",
        "client_id",
        "client_secret",
        "refresh_token",
        fetch_time="1 hour",
        fetch_limit=1,
        fetch_status=["Open"],
    )
    mocker.patch.object(client, "get_requests", return_value=RESPONSE_FETCH_INCIDENTS)
    incidents = fetch_incidents(client)
    assert len(incidents) == 1
    assert incidents[0].get("name") == "Test fetch incidents - 1234"

    client = Client(
        "server_url",
        "use_ssl",
        "use_proxy",
        "client_id",
        "client_secret",
        "refresh_token",
        fetch_time="1 hour",
        fetch_limit=0,
        fetch_status=["Open"],
    )
    mocker.patch.object(client, "get_requests", return_value=RESPONSE_FETCH_INCIDENTS)
    incidents = fetch_incidents(client)
    assert len(incidents) == 0


def test_fetch_incidents_on_premise(mocker):
    """
    Unit test
    Given
    - fetch incidents command
    - command args
    - command raw response
    When
    - mock the parse_date_range.
    - mock the date_to_timestamp.
    - mock the create_fetch_list_info.
    - mock the Client's get_requests command.
    Then
    - run the fetch incidents command using the Client.
    Validate the length of the results and the different fields of the fetched incidents.
    """
    mocker.patch("ServiceDeskPlus.parse_date_range", return_value=("2020-06-23 04:18:00", "never mind"))
    mocker.patch("ServiceDeskPlus.date_to_timestamp", return_value="1592918317168")
    mocker.patch("ServiceDeskPlus.create_fetch_list_info", return_value={})

    client = Client(
        "server_url",
        "dummy_oauth_url",
        "use_ssl",
        "use_proxy",
        technician_key="technician_key",
        on_premise=True,
        fetch_time="1 hour",
        fetch_limit=3,
        fetch_status=["Open"],
    )
    mocker.patch.object(client, "get_requests", return_value=RESPONSE_FETCH_INCIDENTS)
    incidents = fetch_incidents(client)
    assert len(incidents) == 3

    client = Client(
        "server_url",
        "dummy_oauth_url",
        "use_ssl",
        "use_proxy",
        technician_key="technician_key",
        on_premise=True,
        fetch_time="1 hour",
        fetch_limit=2,
        fetch_status=["Open"],
    )
    mocker.patch.object(client, "get_requests", return_value=RESPONSE_FETCH_INCIDENTS)
    incidents = fetch_incidents(client)
    assert len(incidents) == 2

    client = Client(
        "server_url",
        "dummy_oauth_url",
        "use_ssl",
        "use_proxy",
        technician_key="technician_key",
        on_premise=True,
        fetch_time="1 hour",
        fetch_limit=1,
        fetch_status=["Open"],
    )
    mocker.patch.object(client, "get_requests", return_value=RESPONSE_FETCH_INCIDENTS)
    incidents = fetch_incidents(client)
    assert len(incidents) == 1
    assert incidents[0].get("name") == "Test fetch incidents - 1234"

    client = Client(
        "server_url",
        "dummy_oauth_url",
        "use_ssl",
        "use_proxy",
        technician_key="technician_key",
        on_premise=True,
        fetch_time="1 hour",
        fetch_limit=0,
        fetch_status=["Open"],
    )
    mocker.patch.object(client, "get_requests", return_value=RESPONSE_FETCH_INCIDENTS)
    incidents = fetch_incidents(client)
    assert len(incidents) == 0


def test_test_module_cloud(mocker):
    """
    Unit test
    Given
    - test module command
    - command raw response
    When
    - mock the parse_date_range.
    - mock the Client's send_request.
    Then
    - run the test module command using the Client
    Validate the content of the HumanReadable.
    """
    from ServiceDeskPlus import test_module as module

    mocker.patch("ServiceDeskPlus.Client.get_access_token")
    client = Client("server_url", "use_ssl", "use_proxy", "client_id", "client_secret", refresh_token="refresh_token")

    mocker.patch("ServiceDeskPlus.parse_date_range", return_value=("2020-06-23 04:18:00", "never mind"))
    mocker.patch.object(client, "http_request", return_value=RESPONSE_FETCH_INCIDENTS)
    result = module(client)
    assert result == "ok"


def test_test_module_on_premise(mocker):
    """
    Unit test
    Given
    - test module command on premise
    - command raw response
    When
    - mock the parse_date_range.
    - mock the Client's send_request.
    Then
    - run the test module command using the Client
    Validate the content of the HumanReadable.
    """
    from ServiceDeskPlus import test_module as module

    client = Client("server_url", "dummy_oauth_url", "use_ssl", "use_proxy", technician_key="technician_key", on_premise=True)

    mocker.patch("ServiceDeskPlus.parse_date_range", return_value=("2020-06-23 04:18:00", "never mind"))
    mocker.patch.object(client, "http_request", return_value=RESPONSE_FETCH_INCIDENTS)
    result = module(client)
    assert result == "ok"


@pytest.mark.parametrize(
    "region,expected_oauth_url",
    [
        ("United States", "https://accounts.zoho.com/oauth/v2/token"),
        ("Europe", "https://accounts.zoho.eu/oauth/v2/token"),
        ("India", "https://accounts.zoho.in/oauth/v2/token"),
        ("China", "https://accounts.zoho.cn/oauth/v2/token"),
        ("Australia", "https://accounts.zoho.au/oauth/v2/token"),
    ],
)
def test_get_access_token_with_regional_oauth_url(mocker, region, expected_oauth_url):
    """
    Given
    - Client with regional OAuth URL
    When
    - calling get_access_token method
    Then
    - validate that the correct regional OAuth URL is used for token request
    """
    oauth_url = OAUTH_URL[region]

    mocker.patch("ServiceDeskPlus.demisto.getIntegrationContext", return_value={})
    mocker.patch("ServiceDeskPlus.demisto.setIntegrationContext")
    mocker.patch("ServiceDeskPlus.date_to_timestamp", return_value=1234567890)

    mock_response = {"access_token": "test_access_token", "expires_in": 3600}
    mock_http_request = mocker.patch("ServiceDeskPlus.Client.http_request", return_value=mock_response)

    Client(
        url="https://test.com/api/v3/",
        outh_url=oauth_url,
        use_ssl=True,
        use_proxy=False,
        client_id="test_client_id",
        client_secret="test_client_secret",
        refresh_token="test_refresh_token",
    )

    mock_http_request.assert_called_once()
    call_args = mock_http_request.call_args
    assert call_args[1]["full_url"] == expected_oauth_url
    assert call_args[0][0] == "POST"
    assert call_args[1]["url_suffix"] == ""


@pytest.mark.parametrize(
    "region,expected_oauth_url",
    [
        ("United States", "https://accounts.zoho.com/oauth/v2/token"),
        ("Europe", "https://accounts.zoho.eu/oauth/v2/token"),
        ("India", "https://accounts.zoho.in/oauth/v2/token"),
        ("China", "https://accounts.zoho.cn/oauth/v2/token"),
        ("Australia", "https://accounts.zoho.au/oauth/v2/token"),
    ],
)
def test_generate_refresh_token_with_regional_oauth_url(mocker, region, expected_oauth_url):
    """
    Given
    - generate_refresh_token command with regional OAuth URL
    When
    - calling generate_refresh_token with different regional clients
    Then
    - validate that the correct regional OAuth URL is used
    """
    oauth_url = OAUTH_URL[region]
    client = Client(
        url="https://test.com/api/v3/",
        outh_url=oauth_url,
        use_ssl=True,
        use_proxy=False,
        client_id="test_client_id",
        client_secret="test_client_secret",
    )

    mock_response = {"refresh_token": "new_refresh_token"}
    mock_http_request = mocker.patch.object(client, "http_request", return_value=mock_response)

    args = {"code": "test_authorization_code"}
    result = generate_refresh_token(client, args)

    mock_http_request.assert_called_once()
    call_args = mock_http_request.call_args
    assert call_args[1]["full_url"] == expected_oauth_url
    assert call_args[0][0] == "POST"
    assert call_args[1]["url_suffix"] == ""

    assert "new_refresh_token" in result[0]


def test_get_request_notes_list_command(mocker):
    from ServiceDeskPlus import get_request_notes_list_command

    client = Client("server_url", "test_oauth_url", True, False, technician_key="technician_key", on_premise=True)
    mocker.patch.object(client, "http_request", return_value=RESPONSE_GET_NOTES_LIST)
    args = {"request_id": "123"}
    result = get_request_notes_list_command(client, args)
    assert result.outputs == EXPECTED_GET_NOTES_LIST


def test_get_request_note_command(mocker):
    from ServiceDeskPlus import get_request_notes_list_command

    client = Client("server_url", "test_oauth_url", True, False, technician_key="technician_key", on_premise=True)
    mocker.patch.object(client, "http_request", return_value=RESPONSE_GET_NOTE)
    args = {"request_id": "123", "request_note_id": "1"}
    result = get_request_notes_list_command(client, args)
    assert result.outputs == EXPECTED_GET_NOTE


def test_add_request_note_command(mocker):
    from ServiceDeskPlus import add_request_note_command

    client = Client("server_url", "test_oauth_url", True, False, technician_key="technician_key", on_premise=True)
    mocker.patch.object(client, "http_request", return_value=RESPONSE_ADD_NOTE)
    args = {
        "request_id": "123",
        "description": "New Note",
        "mark_first_response": "false",
        "add_to_linked_requests": "false",
        "notify_technician": "false",
        "show_to_requester": "false",
    }
    result = add_request_note_command(client, args)
    assert result.outputs == EXPECTED_ADD_NOTE


def test_delete_request_note_command(mocker):
    from ServiceDeskPlus import delete_request_note_command

    client = Client("server_url", "test_oauth_url", True, False, technician_key="technician_key", on_premise=True)
    mocker.patch.object(client, "http_request", return_value=RESPONSE_DELETE_NOTE)
    args = {"request_id": "123", "request_note_id": "1"}
    result = delete_request_note_command(client, args)
    assert result.outputs == EXPECTED_DELETE_NOTE


def test_update_request_note_command(mocker):
    from ServiceDeskPlus import update_request_note_command

    client = Client("server_url", "test_oauth_url", True, False, technician_key="technician_key", on_premise=True)
    mocker.patch.object(client, "http_request", return_value=RESPONSE_UPDATE_NOTE)
    args = {
        "request_id": "123",
        "request_note_id": "1",
        "description": "Updated Note 1",
        "mark_first_response": "false",
        "add_to_linked_requests": "false",
        "notify_technician": "false",
        "show_to_requester": "false",
    }
    result = update_request_note_command(client, args)
    assert result.outputs == EXPECTED_UPDATE_NOTE