Details
| ID | SlackV2 |
|---|---|
| Provider | Slack |
| Category | Messaging and Conferencing |
| From Version | 5.0.0 |
| Docker Image | demisto/slack:1.0.0.42956 |
| Supported Modules | Agentix Cortex Cloud Cloud Runtime Security Cloud Posture Security XSIAM EDR |
README
Deprecated. Use the Slack v3 integration instead.
Send messages and notifications to your Slack Team. This integration was integrated and tested with version 4.0.1 of Slack, and is available from Cortex XSOAR version 5.0.
Use Cases
- Mirror Cortex XSOAR investigations War Room to Slack channels and vice-versa.
- Send notifications, message and files from Cortex XSOAR to Slack.
- Get notifications in Slack about events in Cortex XSOAR.
- Manage Cortex XSOAR incidents via direct messages to the Cortex XSOAR bot.
- Manage Slack channels (create, edit, filter, invite, kick, close).
Detailed Description
- To allow us access to Slack, the Cortex XSOAR app has to be added to the relevant workspace. Do so by clicking on the following link.
- After adding the Cortex XSOAR app, you will get an Access Token and Bot Token, which should be inserted in the integration instance configuration's corresponding fields.
Configure SlackV2 on Cortex XSOAR
- Navigate to Settings > Integrations > Servers & Services.
- Search for SlackV2.
-
Click Add instance to create and configure a new integration instance.
- Name: a textual name for the integration instance.
- Slack API access token: A token received by adding the application (Starts with xoxp).
- Slack API bot token: A token received by adding the application (Starts with xoxb).
- Dedicated Slack channel to receive notifications
- Send notifications about incidents to the dedicated channel
- Minimum incident severity to send messages to slack by
- Type of incidents created in Slack
- Allow external users to create incidents via DM
- Use system proxy settings
- Trust any certificate (not secure)
- Bot display name in Slack (Cortex XSOAR by default)
- Bot icon in Slack - Image URL (Demisto icon by default)
- Maximum time to wait for a rate limited call in seconds - 60 by default
- Number of objects to return in each paginated call - 200 by default
- Proxy URL to use in Slack API calls
- Click Test to validate the new instance.
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.
- mirror-investigation: mirror-investigation
- send-notification: send-notification
- close-channel: close-channel
- slack-send-file: slack-send-file
- slack-set-channel-topic: slack-set-channel-topic
- slack-create-channel: slack-create-channel
- slack-invite-to-channel: slack-invite-to-channel
- slack-kick-from-channel: slack-kick-from-channel
- slack-rename-channel: slack-rename-channel
- slack-get-user-details: slack-get-user-details
- slack-filter-channels: slack-filter-channels
1. mirror-investigation
Mirrors the investigation between Slack and the Cortex XSOAR War Room.
Base Command
mirror-investigation
Input
</tr> </tbody> </table>
Context Output
There are no context output for this command.
Command Example
!mirror-investigation type=all autoclose=true direction=Both channelName=my-mirror channelTopic=my-incident
Human Readable Output
Investigation mirrored successfully, channel: my-mirror
2. send-notification
Sends a message to a user, group, or channel.
Base Command
send-notification
Input
| Argument Name | Description | Required |
|---|---|---|
| type | The mirroring type. Can be "all", which mirrors everything, "chat", which mirrors only chats (not commands), or "none", which stops all mirroring. | Optional |
| autoclose | Whether the channel is auto-closed when an investigation is closed. Can be "true" or "false". Default is "true". | Optional |
| direction | The mirroring direction. Can be "FromDemisto", "ToDemisto", or "Both". Default value is "Both". | Optional |
| mirrorTo | The channel type. Can be "channel" or "group". The default value is "group". | Optional |
| channelName | The name of the channel. The default is "incident-".</td> | Optional |
| channelTopic | The topic of the channel. | Optional |
| kickAdmin | Whether to remove the Slack administrator (channel creator) from the mirrored channel. | Optional |
| Argument Name | Description | Required |
|---|---|---|
| message | The message content. When mentioning another slack user, make sure to do so in the following format: <@user_name>. | Optional |
| to | The user to whom to send the message. Can be either the username or email address. | Optional |
| channel | The name of the Slack channel to which to send the message. | Optional |
| entry | An entry ID to send as a link. | Optional |
| ignoreAddURL | Whether to include a URL to the relevant component in Cortex XSOAR. Can be "true" or "false". Default value is "false". | Optional |
| threadID | The ID of the thread to which to reply - can be retrieved from a previous send-notification command. | Optional |
| blocks | A JSON string of Slack blocks to send in the message. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Slack.Thread.ID | String | b'The Slack thread ID.' |
Command Example
!send-notification channel=general message="I love Cortex XSOAR"
Context Example
{
"Slack.Thread": {
"ID": "1567407432.000500"
}
}
Human Readable Output
Message sent to Slack successfully. Thread ID is: 1567407432.000500
3. close-channel
Archives a Slack channel.
Base Command
close-channel
Input
| Argument Name | Description | Required |
|---|---|---|
| channel | The name of the channel to archive. If not provided, the mirrored investigation channel is archived (if the channel exists). | Optional |
Context Output
There are no context output for this command.
Command Example
!close-channel
Human Readable Output
Channel successfully archived.
4. slack-send-file
Sends a file to a user, channel, or group. If not specified, the file is sent to the mirrored investigation channel (if the channel exists).
Base Command
slack-send-file
Input
| Argument Name | Description | Required |
|---|---|---|
| file | The ID of the file entry to send. | Required |
| to | The user to whom to send the file. Can be the username or the email address. | Optional |
| group | The name of the Slack group (private channel) to which to send the file. | Optional |
| channel | The name of the Slack channel to which to send the file. | Optional |
| threadID | The ID of the thread to which to reply - can be retrieved from a previous send-notification command. | Optional |
| comment | A comment to add to the file. | Optional |
Context Output
There are no context output for this command.
Command Example
!slack-send-file file=1@2 channel=general
Human Readable Output
File sent to Slack successfully.
5. slack-set-channel-topic
Sets the topic for a channel.
Base Command
slack-set-channel-topic
Input
| Argument Name | Description | Required |
|---|---|---|
| channel | The channel name. If not specified, the topic of the mirrored investigation channel is set (if the channel exists). | Optional |
| topic | The topic for the channel. | Required |
Context Output
There are no context output for this command.
Command Example
!slack-set-channel-topic channel=general topic="Cortex XSOAR rocks"
Human Readable Output
Topic successfully set.
6. slack-create-channel
Creates a channel in Slack.
Base Command
slack-create-channel
Input
| Argument Name | Description | Required |
|---|---|---|
| type | The channel type. Can be "private" or "public". | Optional |
| name | The name of the channel. | Required |
| users | A CSV list of user names or email addresses to invite to the channel. For example: "user1, user2...". | Optional |
Context Output
There are no context output for this command.
Command Example
!slack-create-channel name=my-channel topic=cool-topic type=private users=demisto_integration
Human Readable Output
Successfully created the channel my-channel.
7. slack-invite-to-channel
Invites users to join a channel.
Base Command
slack-invite-to-channel
Input
| Argument Name | Description | Required |
|---|---|---|
| users | A CSV list of usernames or email addresses to invite to join the channel. For example: "user1, user2...". | Required |
| channel | The name of the channel to which to invite the users. If the name of the channel is not specified, the name of the mirrored investigation channel is used (if the channel exists). | Optional |
Context Output
There are no context output for this command.
Command Example
!slack-invite-to-channel channel=my-channel users=cool-user
Human Readable Output
Successfully invited users to the channel.
8. slack-kick-from-channel
Removes users from the specified channel.
Base Command
slack-kick-from-channel
Input
| Argument Name | Description | Required |
|---|---|---|
| users | A CSV list of usernames or email addresses to remove from the a channel. For example: "user1, user2..." | Required |
| channel | The name of the channel from which to remove the users. If the name of the channel is not specified, the mirrored investigation channel is used (if the channel exists). | Optional |
Context Output
There are no context output for this command.
Command Example
!slack-kick-from-channel channel=my-channel users=cool-user
Human Readable Output
Successfully kicked users from the channel.
9. slack-rename-channel
Renames a channel in Slack.
Base Command
slack-rename-channel
Input
| Argument Name | Description | Required |
|---|---|---|
| name | The new name of the channel. | Required |
| channel | The current name of the channel. If the name of the channel is not specified, the mirrored investigation channel is used (if the channel exists). | Optional |
Context Output
There are no context output for this command.
Command Example
!slack-rename-channel channel=my-channel name=your-channel
Human Readable Output
Channel renamed successfully.
10. slack-get-user-details
Get details about a specified user.
Base Command
slack-get-user-details
Input
| Argument Name | Description | Required |
|---|---|---|
| user | The Slack user (username or email). | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Slack.User.ID | String | b'The ID of the user.' |
| Slack.User.Username | String | b'The username of the user.' |
| Slack.User.Name | String | b'The actual name of the user.' |
| Slack.User.DisplayName | String | b'The display name of the user.' |
| Slack.User.Email | String | b'The email address of the user.' |
Command Example
!slack-get-user-details user="cool_user"
Context Example
{
"Slack.User": {
"ID": "UXXXXXXXX",
"Name": "Cool User",
"Username": "cool_user",
"Email": "cool_user@coolorg.com"
}
}
Human Readable Output
Details for Slack user: cool_user
| ID | Username | Name | |
|---|---|---|---|
| UXXXXXXXX | cool_user | Cool User | cool_user@coolorg.com |
11. slack-filter-channels
Get channels matching provided criteria.
Base Command
slack-filter-channels
Input
| Argument Name | Description | Required |
|---|---|---|
| name | The name of a channel or a regex pattern | Optional |
| creator | The member ID who created the channel | Optional |
| is_archived | True or False | Optional |
| is_general | True or False | Optional |
| is_private | True or False | Optional |
| limit | The maximum number of channels to return. Default is "20" | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Slack.Channel.ID | String | The channel ID |
| Slack.Channel.Name | String | The channel name |
| Slack.Channel.Created | Number | The channel creation timestamp |
| Slack.Channel.Creator | String | The channel creator's member ID |
| Slack.Channel.IsArchived | Boolean | Has the channel been archived? |
| Slack.Channel.IsGeneral | Boolean | Is the channel the general channel? |
| Slack.Channel.IsPrivate | Boolean | Is the channel a private channel? |
Command Example
!slack-filter-channels name="^general$"
Context Example
{
"Slack.Channel": {
"ID": "CXXXXXXXX",
"Name": "general",
"Created": 1449252889,
"Creator": "UXXXXXXXX",
"IsArchived": false,
"IsGeneral": true,
"IsPrivate": false,
}
}
Human Readable Output
Results
| ID | Name | Created | Creator | IsArchived | IsGeneral | IsPrivate |
|---|---|---|---|---|---|---|
| CXXXXXXXX | general | 1449252889 | UXXXXXXXX | false | true | false |
Additional Information
Change the name of the Cortex XSOAR App
You can change the name and icon of the Cortex XSOAR app in direct messages using the integration configuration settings (parameters). In order to change the name of the application itself, do the following:
- Go to the app in the **Apps** section in Slack

- In the app, go to **About > Settings**:

- Scroll down and click the pencil icon to change the name.

Direct messages
You can send direct messages to the Cortex XSOAR app on Slack using the following commands:
list incidents [page x] - lists the current incidents in Cortex XSOAR. Requires user permissions in Cortex XSOAR.
list my incidents [page x] - lists the current incidents assigned to you in Cortex XSOAR. Requires user permissions in Cortex XSOAR.
list my tasks [page x] - lists the current tasks assigned to you in Cortex XSOAR. Requires user permissions in Cortex XSOAR.
list closed incidents - lists the closed incidents in Cortex XSOAR. Requires user permissions in Cortex XSOAR.
new incident [details] - creates a new incident in Cortex XSOAR. Requires user permissions in Cortex XSOAR, or that the `Allow external users to create incidents via DM` parameter is enabled.
mirror [incident-id] - mirrors an incident in Cortex XSOAR to a Slack channel. Requires user permissions in Cortex XSOAR for the specified incident.
Notifications
The integration sends notifications as they are configured in the notification settings (User Preferences in Cortex XSOAR), and to the dedicated channel configured for incident notifications (according to the integration configuration). If a dedicated channel for incident notifications is configured, the following notifications will be sent there:
- Incident opened
- Incident updated
- Investigation closed
- Investigation deleted
- Incident SLA changed
- Task completed
Blocks and interactive components
The integration supports sending "blocks" to Slack. Blocks are a series of components that can be combined to create visually rich and compellingly interactive messages. In the integration, they can be sent as an array of JSON. More information about that here. You can experiment with and build your own blocks here. The integration also allows some level of interactivity. When a user interacts with an element in a Slack message, Slack sends a request with the relevant information. This request is processed and stored by a dedicated endpoint outside of Cortex XSOAR in the address:https://oproxy.demisto.ninja
The integration allows polling this endpoint for user interactions that contain entitlement strings, which are used to perform actions in Cortex XSOAR by external users. See the SlackAsk script for an example.
This means that in order to enable interactivity using the integration, connection to this endpoint has to be enabled.
The following information is sent to the dedicated endpoint in the request:
Headers
- Current Cortex XSOAR content version
- Current Cortex XSOAR server version
- The name of the integration
- Team name in Slack - for identification
- Team ID in Slack - for identification
- Cortex XSOAR license ID - for identification
Body
- Entitlement - the unique entitlement string to allow interaction with Cortex XSOAR.
Important! The interactions work only with the Cortex XSOAR Integration bot - the only application that's allowed to send requests to the dedicated endpoint(for security reasons). They will not work with another application.
Known Limitations
- Due to limitations of the `aiohttp` library, only http proxies are supported.
- Channels are created by the Slack user who authorized the application. Thus, this user will be in every channel that the integration creates. You cannot kick this user, but they can leave.
- The integration can only manage channels that the application is a member of. Otherwise those channels will not be found.
- Currently, the integration does not support working without verifying SSL certificates. The parameter applies only to the endpoint for interactive responses.
Troubleshooting
If messages are not mirrored in Cortex XSOAR, or direct messages are not handled properly, check the integration status on the integration page:
Configuration parameters
cred_access_token—cred_bot_token—access_token— Slack API access tokenbot_token— Slack API bot tokenincidentNotificationChannel— Dedicated Slack channel to receive notificationsnotify_incidents— Send notifications about incidents to the dedicated channelmin_severity— Minimum incident severity to send messages to slack byincidentType— Type of incidents created in Slackallow_incidents— Allow external users to create incidents via DMproxy— Use system proxy settingsunsecure— Trust any certificate (not secure)longRunning— Long running instance. Required for investigation mirroring and direct messages.bot_name— Bot display name in Slack (Cortex XSOAR by default)bot_icon— Bot icon in Slack - Image URL (Cortex XSOAR icon by default)max_limit_time— Maximum time to wait for a rate limiting call in secondspaginated_count— Number of objects to return in each paginated callproxy_url— Proxy URL to use in Slack API callsfiltered_tags— Comma separated list of tags To filter the messages sent from demisto.
Commands (15)
-
close-channelDeprecatedArchives a Slack channel.
-
mirror-investigationDeprecatedMirrors the investigation between Slack and the Demisto War Room.
-
send-notificationDeprecatedSends a message to a user, group, or channel.
-
slack-close-channelDeprecatedDeprecated. Use the "close-channel" command instead.
-
slack-create-channelDeprecatedCreates a channel in Slack.
-
slack-filter-channelsDeprecatedReturns a list of channels matching criteria
-
slack-get-integration-contextReturns the integration context as a file. Use this command for debug purposes only.
-
slack-get-user-detailsDeprecatedGet details about a specified user.
-
slack-invite-to-channelDeprecatedInvites users to join a channel.
-
slack-kick-from-channelDeprecatedRemoves users from the specified channel.
-
slack-mirror-investigationDeprecatedDeprecated. Use the "mirror-investigation" command instead.
-
slack-rename-channelDeprecatedRenames a channel in Slack.
-
slack-sendDeprecatedDeprecated. Use the "send-notification" command instead.
-
slack-send-fileDeprecatedSends a file to a user, channel, or group. If not specified, the file is sent to the mirrored investigation channel (if the channel exists).
-
slack-set-channel-topicDeprecatedSets the topic for a channel.
import asyncio import json as js import threading import pytest import slack from slack.web.slack_response import SlackResponse from CommonServerPython import * import datetime USERS = """[{ "id": "U012A3CDE", "team_id": "T012AB3C4", "name": "spengler", "deleted": false, "color": "9f69e7", "real_name": "spengler", "tz": "America/Los_Angeles", "tz_label": "Pacific Daylight Time", "tz_offset": -25200, "profile": { "avatar_hash": "ge3b51ca72de", "status_text": "Print is dead", "status_emoji": ":books:", "real_name": "Egon Spengler", "display_name": "spengler", "real_name_normalized": "Egon Spengler", "display_name_normalized": "spengler", "email": "spengler@ghostbusters.example.com", "team": "T012AB3C4" }, "is_admin": true, "is_owner": false, "is_primary_owner": false, "is_restricted": false, "is_ultra_restricted": false, "is_bot": false, "updated": 1502138686, "is_app_user": false, "has_2fa": false }, { "id": "U07QCRPA4", "team_id": "T0G9PQBBK", "name": "glinda", "deleted": false, "color": "9f69e7", "real_name": "Glinda Southgood", "tz": "America/Los_Angeles", "tz_label": "Pacific Daylight Time", "tz_offset": -25200, "profile": { "avatar_hash": "8fbdd10b41c6", "first_name": "Glinda", "last_name": "Southgood", "title": "Glinda the Good", "phone": "", "skype": "", "real_name": "Glinda Southgood", "real_name_normalized": "Glinda Southgood", "display_name": "Glinda the Fairly Good", "display_name_normalized": "Glinda the Fairly Good", "email": "Glenda@south.oz.coven" }, "is_admin": true, "is_owner": false, "is_primary_owner": false, "is_restricted": false, "is_ultra_restricted": false, "is_bot": false, "updated": 1480527098, "has_2fa": false }]""" CONVERSATIONS = """[{ "id": "C012AB3CD", "name": "general", "is_channel": true, "is_group": false, "is_im": false, "created": 1449252889, "creator": "U012A3CDE", "is_archived": false, "is_general": true, "unlinked": 0, "name_normalized": "general", "is_shared": false, "is_ext_shared": false, "is_org_shared": false, "pending_shared": [], "is_pending_ext_shared": false, "is_member": true, "is_private": false, "is_mpim": false, "topic": { "value": "Company-wide announcements and work-based matters", "creator": "", "last_set": 0 }, "purpose": { "value": "This channel is for team-wide communication and announcements. All team members are in this channel.", "creator": "", "last_set": 0 }, "previous_names": [], "num_members": 4 }, { "id": "C061EG9T2", "name": "random", "is_channel": true, "is_group": false, "is_im": false, "created": 1449252889, "creator": "U061F7AUR", "is_archived": false, "is_general": false, "unlinked": 0, "name_normalized": "random", "is_shared": false, "is_ext_shared": false, "is_org_shared": false, "pending_shared": [], "is_pending_ext_shared": false, "is_member": true, "is_private": false, "is_mpim": false, "topic": { "value": "Non-work banter and water cooler conversation", "creator": "", "last_set": 0 }, "purpose": { "value": "A place for non-work-related flimflam.", "creator": "", "last_set": 0 }, "previous_names": [], "num_members": 4 }]""" BOT = """{ "ok": true, "url": "https://subarachnoid.slack.com/", "team": "Subarachnoid Workspace", "user": "grace", "team_id": "T12345678", "user_id": "W12345678" }""" MIRRORS = """ [{ "channel_id":"GKQ86DVPH", "channel_name": "incident-681", "channel_topic": "incident-681", "investigation_id":"681", "mirror_type":"all", "mirror_direction":"both", "mirror_to":"group", "auto_close":true, "mirrored":true }, { "channel_id":"GKB19PA3V", "channel_name": "group2", "channel_topic": "cooltopic", "investigation_id":"684", "mirror_type":"all", "mirror_direction":"both", "mirror_to":"group", "auto_close":true, "mirrored":true }, { "channel_id":"GKB19PA3V", "channel_name": "group2", "channel_topic": "cooltopic", "investigation_id":"692", "mirror_type":"all", "mirror_direction":"both", "mirror_to":"group", "auto_close":true, "mirrored":true }, { "channel_id":"GKNEJU4P9", "channel_name": "group3", "channel_topic": "incident-713", "investigation_id":"713", "mirror_type":"all", "mirror_direction":"both", "mirror_to":"group", "auto_close":true, "mirrored":true }, { "channel_id":"GL8GHC0LV", "channel_name": "group5", "channel_topic": "incident-734", "investigation_id":"734", "mirror_type":"all", "mirror_direction":"both", "mirror_to":"group", "auto_close":true, "mirrored":true }] """ BLOCK_JSON = [ {"type": "section", "text": {"type": "mrkdwn", "text": "text"}}, { "type": "actions", "elements": [ { "type": "button", "text": {"type": "plain_text", "emoji": True, "text": "yes"}, "style": "primary", "value": '{"entitlement": "e95cb5a1-e394-4bc5-8ce0-508973aaf298@22|43", "reply": "Thanks bro"}', }, { "type": "button", "text": {"type": "plain_text", "emoji": True, "text": "no"}, "style": "danger", "value": '{"entitlement": "e95cb5a1-e394-4bc5-8ce0-508973aaf298@22|43", "reply": "Thanks bro"}', }, ], }, ] PAYLOAD_JSON = r""" { "type":"block_actions", "team":{ "id":"T9XJ4RGNQ", "domain":"dombo60" }, "user":{ "id":"U012A3CDE", "username":"spengler", "name":"spengler", "team_id":"T9XJ4RGNQ" }, "api_app_id":"AMU4M2QL8", "token":"dummy-token", "container":{ "type":"message", "message_ts":"1567945126.000100", "channel_id":"DMGSNFCSX", "is_ephemeral":false }, "trigger_id":"754598374743.337616866772.8c4b2dc28ca7fd4c8941247c1a01c7dd", "channel":{ "id":"DMGSNFCSX", "name":"directmessage" }, "message":{ "type":"message", "subtype":"bot_message", "text":"This content can't be displayed.", "ts":"1567945126.000100", "username":"BlockTest", "bot_id":"BMWFS6KSA", "blocks":[ { "type":"section", "block_id":"F9iYK", "text":{ "type":"mrkdwn", "text":"Hopa this is a test. ", "verbatim":false }, "accessory":{ "type":"button", "text":{ "type":"plain_text", "text":"Eyy", "emoji":true }, "value":"{\"entitlement\": \"e95cb5a1-e394-4bc5-8ce0-508973aaf298@22|43\", \"reply\": \"Thanks bro\"}", "action_id":"W9J" } } ] }, "response_url":"hooks.slack.com", "actions":[ { "action_id":"W9J", "block_id":"F9iYK", "text":{ "type":"plain_text", "text":"Eyy", "emoji":true }, "value":"{\"entitlement\": \"e95cb5a1-e394-4bc5-8ce0-508973aaf298@22|43\", \"reply\": \"Thanks bro\"}", "type":"button", "action_ts":"1567949681.728426" } ] } """ SLACK_RESPONSE = SlackResponse(client=None, http_verb="", api_url="", req_args={}, data={"ts": "cool"}, headers={}, status_code=0) SLACK_RESPONSE_2 = SlackResponse( client=None, http_verb="", api_url="", req_args={}, data={"cool": "cool"}, headers={}, status_code=0 ) def test_exception_in_invite_to_mirrored_channel(mocker): import Slack from Slack import check_for_mirrors new_user = { "name": "perikles", "profile": { "email": "perikles@acropoli.com", }, "id": "U012B3CUI", } def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): users = {"members": js.loads(USERS)} users["members"].append(new_user) return users # Set mirrors = js.loads(MIRRORS) mirrors.append( { "channel_id": "new_group", "channel_name": "channel", "investigation_id": "999", "mirror_type": "all", "mirror_direction": "both", "mirror_to": "group", "auto_close": True, "mirrored": False, } ) set_integration_context({"mirrors": js.dumps(mirrors), "users": USERS, "conversations": CONVERSATIONS, "bot_id": "W12345678"}) mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object( demisto, "mirrorInvestigation", return_value=[ {"email": "spengler@ghostbusters.example.com", "username": "spengler"}, {"email": "perikles@acropoli.com", "username": "perikles"}, ], ) mocker.patch.object(Slack, "invite_to_mirrored_channel", side_effect=Exception) mocker.patch.object(demisto, "error") check_for_mirrors() assert demisto.setIntegrationContext.call_count != 0 assert demisto.error.call_args[0][0] == "Could not invite investigation users to the mirrored channel: " def get_integration_context(): return INTEGRATION_CONTEXT def set_integration_context(integration_context): global INTEGRATION_CONTEXT INTEGRATION_CONTEXT = integration_context RETURN_ERROR_TARGET = "Slack.return_error" @pytest.fixture(autouse=True) def setup(mocker): from Slack import init_globals mocker.patch.object(demisto, "info") mocker.patch.object(demisto, "debug") set_integration_context({"mirrors": MIRRORS, "users": USERS, "conversations": CONVERSATIONS, "bot_id": "W12345678"}) init_globals() @pytest.mark.asyncio async def test_get_slack_name_user(mocker): from Slack import get_slack_name # Set async def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "users.info": user = params["user"] if user != "alexios": return {"user": js.loads(USERS)[0]} elif method == "conversations.info": return {"channel": js.loads(CONVERSATIONS)[0]} return None mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext") mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) # Assert # User in integration context user_id = "U012A3CDE" name = await get_slack_name(user_id, slack.WebClient) assert name == "spengler" assert slack.WebClient.api_call.call_count == 0 # User not in integration context unknown_user = "USASSON" name = await get_slack_name(unknown_user, slack.WebClient) assert name == "spengler" assert slack.WebClient.api_call.call_count == 1 # User does not exist nonexisting_user = "alexios" name = await get_slack_name(nonexisting_user, slack.WebClient) assert name == "" assert slack.WebClient.api_call.call_count == 1 @pytest.mark.asyncio async def test_get_slack_name_channel(mocker): from Slack import get_slack_name # Set async def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "users.info": user = params["user"] if user != "alexios": return {"user": js.loads(USERS)[0]} elif method == "conversations.info": return {"channel": js.loads(CONVERSATIONS)[0]} return None mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext") mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) # Assert # Channel in integration context channel_id = "C012AB3CD" name = await get_slack_name(channel_id, slack.WebClient) assert name == "general" assert slack.WebClient.api_call.call_count == 0 # Channel not in integration context unknown_channel = "CSASSON" name = await get_slack_name(unknown_channel, slack.WebClient) assert name == "general" assert slack.WebClient.api_call.call_count == 1 # Channel doesn't exist nonexisting_channel = "lulz" name = await get_slack_name(nonexisting_channel, slack.WebClient) assert name == "" assert slack.WebClient.api_call.call_count == 1 @pytest.mark.asyncio async def test_clean_message(mocker): from Slack import clean_message # Set async def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "users.info": return {"user": js.loads(USERS)[0]} elif method == "conversations.info": return {"channel": js.loads(CONVERSATIONS)[0]} return None mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) user_message = "Hello <@U012A3CDE>!" channel_message = "Check <#C012AB3CD>" link_message = "Go to <https://www.google.com/lulz>" # Arrange clean_user_message = await clean_message(user_message, slack.WebClient) clean_channel_message = await clean_message(channel_message, slack.WebClient) clean_link_message = await clean_message(link_message, slack.WebClient) # Assert assert clean_user_message == "Hello spengler!" assert clean_channel_message == "Check general" assert clean_link_message == "Go to https://www.google.com/lulz" class TestGetConversationByName: @staticmethod def set_conversation_mock(mocker, get_context=get_integration_context): mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(slack.WebClient, "api_call", return_value={"channels": js.loads(CONVERSATIONS)}) def test_get_conversation_by_name_exists_in_context(self, mocker): """ Given: - Conversation to find When: - Conversation exists in context Then: - Check if the right conversation returned - Check that no API command was called. """ from Slack import get_conversation_by_name self.set_conversation_mock(mocker) conversation_name = "general" conversation = get_conversation_by_name(conversation_name) # Assertions assert conversation_name == conversation["name"] assert slack.WebClient.api_call.call_count == 0 def test_get_conversation_by_name_exists_in_api_call(self, mocker): """ Given: - Conversation to find When: - Conversation not exists in context, but do in the API Then: - Check if the right conversation returned - Check that a API command was called. """ def get_context(): return {} from Slack import get_conversation_by_name self.set_conversation_mock(mocker, get_context=get_context) conversation_name = "general" conversation = get_conversation_by_name(conversation_name) # Assertions assert conversation_name == conversation["name"] assert slack.WebClient.api_call.call_count == 1 # Find that 'general' conversation has been added to context conversations = json.loads(demisto.setIntegrationContext.call_args[0][0]["conversations"]) filtered = list(filter(lambda c: c["name"] == conversation_name, conversations)) assert filtered, "Could not find the 'general' conversation in the context" def test_get_conversation_by_name_not_exists(self, mocker): """ Given: - Conversation to find When: - Conversation do not exists. Then: - Check no conversation was returned. - Check that a API command was called. """ from Slack import get_conversation_by_name self.set_conversation_mock(mocker) conversation_name = "no exists" conversation = get_conversation_by_name(conversation_name) assert not conversation assert slack.WebClient.api_call.call_count == 1 def test_get_user_by_name(mocker): from Slack import get_user_by_name # Set def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): users = {"members": js.loads(USERS)} new_user = { "name": "perikles", "profile": { "email": "perikles@acropoli.com", }, "id": "U012B3CUI", } users["members"].append(new_user) return users mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) # Assert # User name exists in integration context username = "spengler" user = get_user_by_name(username) assert user["id"] == "U012A3CDE" assert slack.WebClient.api_call.call_count == 0 # User email exists in integration context email = "spengler@ghostbusters.example.com" user = get_user_by_name(email) assert user["id"] == "U012A3CDE" assert slack.WebClient.api_call.call_count == 0 # User name doesn't exist in integration context username = "perikles" user = get_user_by_name(username) assert user["id"] == "U012B3CUI" assert slack.WebClient.api_call.call_count == 1 set_integration_context({"mirrors": MIRRORS, "users": USERS, "conversations": CONVERSATIONS, "bot_id": "W12345678"}) # User email doesn't exist in integration context email = "perikles@acropoli.com" user = get_user_by_name(email) assert user["id"] == "U012B3CUI" assert slack.WebClient.api_call.call_count == 2 # User doesn't exist username = "alexios" user = get_user_by_name(username) assert user == {} assert slack.WebClient.api_call.call_count == 3 def test_get_user_by_name_paging(mocker): from Slack import get_user_by_name # Set def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if len(params) == 1: return {"members": js.loads(USERS), "response_metadata": {"next_cursor": "dGVhbTpDQ0M3UENUTks="}} else: return {"members": [{"id": "U248918AB", "name": "alexios"}], "response_metadata": {"next_cursor": ""}} mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) # Arrange user = get_user_by_name("alexios") args = slack.WebClient.api_call.call_args_list first_args = args[0][1] second_args = args[1][1] # Assert assert len(first_args["params"]) == 1 assert first_args["params"]["limit"] == 200 assert len(second_args["params"]) == 2 assert second_args["params"]["cursor"] == "dGVhbTpDQ0M3UENUTks=" assert user["id"] == "U248918AB" assert slack.WebClient.api_call.call_count == 2 def test_mirror_investigation_new_mirror(mocker): from Slack import mirror_investigation # Set def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "users.list": return {"members": js.loads(USERS)} if method == "conversations.create": if "is_private" not in json: return {"channel": {"id": "new_channel", "name": "incident-999"}} return {"channel": {"id": "new_group", "name": "incident-999"}} else: return {} mocker.patch.object(demisto, "args", return_value={}) mocker.patch.object(demisto, "investigation", return_value={"id": "999", "users": ["spengler", "alexios"]}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(demisto, "demistoUrls", return_value={"server": "https://www.eizelulz.com:8443"}) mocker.patch.object(demisto, "results") mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) new_mirror = { "channel_id": "new_group", "channel_name": "incident-999", "channel_topic": "incident-999", "investigation_id": "999", "mirror_type": "all", "mirror_direction": "both", "mirror_to": "group", "auto_close": True, "mirrored": False, } # Arrange mirror_investigation() success_results = demisto.results.call_args_list[0][0] new_context = demisto.setIntegrationContext.call_args[0][0] new_mirrors = js.loads(new_context["mirrors"]) new_conversations = js.loads(new_context["conversations"]) our_conversation_filter = list(filter(lambda c: c["id"] == "new_group", new_conversations)) our_conversation = our_conversation_filter[0] our_mirror_filter = list(filter(lambda m: m["investigation_id"] == "999", new_mirrors)) our_mirror = our_mirror_filter[0] # Assert calls = slack.WebClient.api_call.call_args_list groups_call = [c for c in calls if c[0][0] == "conversations.create"] invite_call = [c for c in calls if c[0][0] == "conversations.invite"] topic_call = [c for c in calls if c[0][0] == "conversations.setTopic"] chat_call = [c for c in calls if c[0][0] == "chat.postMessage"] message_args = chat_call[0][1]["json"] assert len(groups_call) == 1 assert len(invite_call) == 1 assert len(topic_call) == 1 assert len(chat_call) == 1 assert success_results[0] == "Investigation mirrored successfully, channel: incident-999" assert message_args["channel"] == "new_group" assert ( message_args["text"] == "This channel was created to mirror incident 999." " \n View it on: https://www.eizelulz.com:8443#/WarRoom/999" ) assert len(our_conversation_filter) == 1 assert len(our_mirror_filter) == 1 assert our_conversation == {"id": "new_group", "name": "incident-999"} assert our_mirror == new_mirror def test_mirror_investigation_new_mirror_with_name(mocker): from Slack import mirror_investigation # Set def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "users.list": return {"members": js.loads(USERS)} if method == "conversations.create": if "is_private" not in json: return {"channel": {"id": "new_channel", "name": "coolname"}} return {"channel": {"id": "new_group", "name": "coolname"}} else: return {} mocker.patch.object(demisto, "args", return_value={"channelName": "coolname"}) mocker.patch.object(demisto, "investigation", return_value={"id": "999", "users": ["spengler", "alexios"]}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(demisto, "demistoUrls", return_value={"server": "https://www.eizelulz.com:8443"}) mocker.patch.object(demisto, "results") mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) new_mirror = { "channel_id": "new_group", "channel_name": "coolname", "channel_topic": "incident-999", "investigation_id": "999", "mirror_type": "all", "mirror_direction": "both", "mirror_to": "group", "auto_close": True, "mirrored": False, } # Arrange mirror_investigation() success_results = demisto.results.call_args_list[0][0] new_context = demisto.setIntegrationContext.call_args[0][0] new_mirrors = js.loads(new_context["mirrors"]) new_conversations = js.loads(new_context["conversations"]) our_conversation_filter = list(filter(lambda c: c["id"] == "new_group", new_conversations)) our_conversation = our_conversation_filter[0] our_mirror_filter = list(filter(lambda m: m["investigation_id"] == "999", new_mirrors)) our_mirror = our_mirror_filter[0] # Assert calls = slack.WebClient.api_call.call_args_list groups_call = [c for c in calls if c[0][0] == "conversations.create"] users_call = [c for c in calls if c[0][0] == "users.list"] invite_call = [c for c in calls if c[0][0] == "conversations.invite"] topic_call = [c for c in calls if c[0][0] == "conversations.setTopic"] chat_call = [c for c in calls if c[0][0] == "chat.postMessage"] message_args = chat_call[0][1]["json"] assert len(groups_call) == 1 assert len(users_call) == 0 assert len(invite_call) == 1 assert len(topic_call) == 1 assert len(chat_call) == 1 assert success_results[0] == "Investigation mirrored successfully, channel: coolname" assert message_args["channel"] == "new_group" assert ( message_args["text"] == "This channel was created to mirror incident 999." " \n View it on: https://www.eizelulz.com:8443#/WarRoom/999" ) assert len(our_conversation_filter) == 1 assert len(our_mirror_filter) == 1 assert our_conversation == {"id": "new_group", "name": "coolname"} assert our_mirror == new_mirror def test_mirror_investigation_new_mirror_with_topic(mocker): from Slack import mirror_investigation # Set def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "users.list": return {"members": js.loads(USERS)} if method == "conversations.create": if "is_private" not in json: return {"channel": {"id": "new_channel", "name": "coolname"}} return {"channel": {"id": "new_group", "name": "coolname"}} else: return {} mocker.patch.object(demisto, "args", return_value={"channelName": "coolname", "channelTopic": "cooltopic"}) mocker.patch.object(demisto, "investigation", return_value={"id": "999", "users": ["spengler", "alexios"]}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(demisto, "demistoUrls", return_value={"server": "https://www.eizelulz.com:8443"}) mocker.patch.object(demisto, "results") mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) new_mirror = { "channel_id": "new_group", "channel_name": "coolname", "channel_topic": "cooltopic", "investigation_id": "999", "mirror_type": "all", "mirror_direction": "both", "mirror_to": "group", "auto_close": True, "mirrored": False, } # Arrange mirror_investigation() success_results = demisto.results.call_args_list[0][0] new_context = demisto.setIntegrationContext.call_args[0][0] new_mirrors = js.loads(new_context["mirrors"]) new_conversations = js.loads(new_context["conversations"]) our_conversation_filter = list(filter(lambda c: c["id"] == "new_group", new_conversations)) our_conversation = our_conversation_filter[0] our_mirror_filter = list(filter(lambda m: m["investigation_id"] == "999", new_mirrors)) our_mirror = our_mirror_filter[0] calls = slack.WebClient.api_call.call_args_list groups_call = [c for c in calls if c[0][0] == "conversations.create"] users_call = [c for c in calls if c[0][0] == "users.list"] invite_call = [c for c in calls if c[0][0] == "conversations.invite"] topic_call = [c for c in calls if c[0][0] == "conversations.setTopic"] chat_call = [c for c in calls if c[0][0] == "chat.postMessage"] message_args = chat_call[0][1]["json"] topic_args = topic_call[0][1]["json"] # Assert assert len(groups_call) == 1 assert len(users_call) == 0 assert len(invite_call) == 1 assert len(topic_call) == 1 assert len(chat_call) == 1 assert success_results[0] == "Investigation mirrored successfully, channel: coolname" assert message_args["channel"] == "new_group" assert ( message_args["text"] == "This channel was created to mirror incident 999." " \n View it on: https://www.eizelulz.com:8443#/WarRoom/999" ) assert topic_args["channel"] == "new_group" assert topic_args["topic"] == "cooltopic" assert len(our_conversation_filter) == 1 assert len(our_mirror_filter) == 1 assert our_conversation == {"id": "new_group", "name": "coolname"} assert our_mirror == new_mirror def test_mirror_investigation_existing_mirror_error_type(mocker): from Slack import mirror_investigation # Set def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "users.list": # noqa: RET503 return {"members": js.loads(USERS)} mocker.patch.object( demisto, "args", return_value={"type": "chat", "autoclose": "false", "direction": "FromDemisto", "mirrorTo": "channel"} ) return_error_mock = mocker.patch(RETURN_ERROR_TARGET, side_effect=InterruptedError()) mocker.patch.object(demisto, "investigation", return_value={"id": "681", "users": ["spengler"]}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(demisto, "results") mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) # Arrange with pytest.raises(InterruptedError): mirror_investigation() err_msg = return_error_mock.call_args[0][0] calls = slack.WebClient.api_call.call_args_list channels_call = [c for c in calls if c[0][0] == "conversations.create"] users_call = [c for c in calls if c[0][0] == "users.list"] invite_call = [c for c in calls if c[0][0] == "conversations.invite"] topic_call = [c for c in calls if c[0][0] == "conversations.setTopic"] # Assert assert len(topic_call) == 0 assert len(users_call) == 0 assert len(invite_call) == 0 assert len(channels_call) == 0 assert return_error_mock.call_count == 1 assert err_msg == "Cannot change the Slack channel type from Demisto." def test_mirror_investigation_existing_mirror_error_name(mocker): from Slack import mirror_investigation # Set def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "users.list": # noqa: RET503 return {"members": js.loads(USERS)} mocker.patch.object(demisto, "args", return_value={"channelName": "eyy"}) return_error_mock = mocker.patch(RETURN_ERROR_TARGET, side_effect=InterruptedError()) mocker.patch.object(demisto, "investigation", return_value={"id": "681", "users": ["spengler"]}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(demisto, "results") mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) # Arrange with pytest.raises(InterruptedError): mirror_investigation() err_msg = return_error_mock.call_args[0][0] calls = slack.WebClient.api_call.call_args_list channels_call = [c for c in calls if c[0][0] == "conversations.create"] users_call = [c for c in calls if c[0][0] == "users.list"] invite_call = [c for c in calls if c[0][0] == "conversations.invite"] # Assert assert len(invite_call) == 0 assert len(channels_call) == 0 assert len(users_call) == 0 assert return_error_mock.call_count == 1 assert err_msg == "Cannot change the Slack channel name." def test_mirror_investigation_existing_investigation(mocker): from Slack import mirror_investigation # Set def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "users.list": # noqa: RET503 return {"members": js.loads(USERS)} mocker.patch.object( demisto, "args", return_value={"type": "chat", "autoclose": "false", "direction": "FromDemisto", "mirrorTo": "group"} ) mocker.patch.object(demisto, "investigation", return_value={"id": "681", "users": ["spengler"]}) mocker.patch.object(demisto, "results") mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) new_mirror = { "channel_id": "GKQ86DVPH", "investigation_id": "681", "channel_name": "incident-681", "channel_topic": "incident-681", "mirror_type": "chat", "mirror_direction": "FromDemisto", "mirror_to": "group", "auto_close": False, "mirrored": False, } # Arrange mirror_investigation() calls = slack.WebClient.api_call.call_args_list channels_call = [c for c in calls if c[0][0] == "conversations.create"] users_call = [c for c in calls if c[0][0] == "users.list"] invite_call = [c for c in calls if c[0][0] == "conversations.invite"] topic_call = [c for c in calls if c[0][0] == "conversations.setTopic"] # Assert assert len(channels_call) == 0 assert len(users_call) == 0 assert len(invite_call) == 0 assert len(topic_call) == 0 success_results = demisto.results.call_args_list[0][0] assert success_results[0] == "Investigation mirrored successfully, channel: incident-681" new_context = demisto.setIntegrationContext.call_args[0][0] new_mirrors = js.loads(new_context["mirrors"]) our_mirror_filter = list(filter(lambda m: m["investigation_id"] == "681", new_mirrors)) our_mirror = our_mirror_filter[0] assert len(our_mirror_filter) == 1 assert our_mirror == new_mirror def test_mirror_investigation_existing_channel(mocker): from Slack import mirror_investigation # Set def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "users.list": # noqa: RET503 return {"members": js.loads(USERS)} mocker.patch.object( demisto, "args", return_value={ "channelName": "group3", "type": "chat", "autoclose": "false", "direction": "FromDemisto", "mirrorTo": "group", }, ) mocker.patch.object(demisto, "investigation", return_value={"id": "999", "users": ["spengler"]}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(demisto, "results") mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) new_mirror = { "channel_id": "GKNEJU4P9", "channel_name": "group3", "investigation_id": "999", "channel_topic": "incident-713, incident-999", "mirror_type": "chat", "mirror_direction": "FromDemisto", "mirror_to": "group", "auto_close": False, "mirrored": False, } # Arrange mirror_investigation() calls = slack.WebClient.api_call.call_args_list groups_call = [c for c in calls if c[0][0] == "groups.create"] channels_call = [c for c in calls if c[0][0] == "channels.create"] users_call = [c for c in calls if c[0][0] == "users.list"] invite_call = [c for c in calls if c[0][0] == "conversations.invite"] topic_call = [c for c in calls if c[0][0] == "conversations.setTopic"] # Assert assert len(groups_call) == 0 assert len(channels_call) == 0 assert len(users_call) == 0 assert len(invite_call) == 0 assert len(topic_call) == 1 success_results = demisto.results.call_args_list[0][0] assert success_results[0] == "Investigation mirrored successfully, channel: group3" new_context = demisto.setIntegrationContext.call_args[0][0] new_mirrors = js.loads(new_context["mirrors"]) our_mirror_filter = list(filter(lambda m: m["investigation_id"] == "999", new_mirrors)) our_mirror = our_mirror_filter[0] assert len(our_mirror_filter) == 1 assert our_mirror == new_mirror def test_mirror_investigation_existing_channel_remove_mirror(mocker): from Slack import mirror_investigation # Set def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "users.list": # noqa: RET503 return {"members": js.loads(USERS)} mirrors = js.loads(MIRRORS) mirrors.append( { "channel_id": "GKB19PA3V", "channel_name": "group2", "channel_topic": "cooltopic", "investigation_id": "999", "mirror_type": "all", "mirror_direction": "both", "mirror_to": "group", "auto_close": True, "mirrored": True, } ) set_integration_context({"mirrors": js.dumps(mirrors), "users": USERS, "conversations": CONVERSATIONS, "bot_id": "W12345678"}) mocker.patch.object(demisto, "investigation", return_value={"id": "999", "users": ["spengler"]}) mocker.patch.object(demisto, "args", return_value={"type": "none"}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(demisto, "results") mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) new_mirror = { "channel_id": "GKB19PA3V", "channel_name": "group2", "channel_topic": "cooltopic", "investigation_id": "999", "mirror_type": "none", "mirror_direction": "both", "mirror_to": "group", "auto_close": True, "mirrored": False, } # Arrange mirror_investigation() calls = slack.WebClient.api_call.call_args_list channels_call = [c for c in calls if c[0][0] == "conversations.create"] users_call = [c for c in calls if c[0][0] == "users.list"] invite_call = [c for c in calls if c[0][0] == "conversations.invite"] topic_call = [c for c in calls if c[0][0] == "conversations.setTopic"] # Assert assert len(channels_call) == 0 assert len(users_call) == 0 assert len(invite_call) == 0 assert len(topic_call) == 0 success_results = demisto.results.call_args_list[0][0] assert success_results[0] == "Investigation mirrored successfully, channel: group2" new_context = demisto.setIntegrationContext.call_args[0][0] new_mirrors = js.loads(new_context["mirrors"]) our_mirror_filter = list(filter(lambda m: m["investigation_id"] == "999", new_mirrors)) our_mirror = our_mirror_filter[0] assert len(our_mirror_filter) == 1 assert our_mirror == new_mirror def test_mirror_investigation_existing_channel_with_topic(mocker): from Slack import mirror_investigation # Set def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "users.list": # noqa: RET503 return {"members": js.loads(USERS)} mocker.patch.object( demisto, "args", return_value={ "channelName": "group2", "type": "chat", "autoclose": "false", "direction": "FromDemisto", "mirrorTo": "group", }, ) mocker.patch.object(demisto, "investigation", return_value={"id": "999", "users": ["spengler"]}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(demisto, "results") mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) new_mirror = { "channel_id": "GKB19PA3V", "channel_name": "group2", "channel_topic": "cooltopic", "investigation_id": "999", "mirror_type": "chat", "mirror_direction": "FromDemisto", "mirror_to": "group", "auto_close": False, "mirrored": False, } # Arrange mirror_investigation() calls = slack.WebClient.api_call.call_args_list channels_call = [c for c in calls if c[0][0] == "conversations.create"] users_call = [c for c in calls if c[0][0] == "users.list"] invite_call = [c for c in calls if c[0][0] == "conversations.invite"] topic_call = [c for c in calls if c[0][0] == "conversations.setTopic"] # Assert assert len(channels_call) == 0 assert len(users_call) == 0 assert len(invite_call) == 0 assert len(topic_call) == 0 success_results = demisto.results.call_args_list[0][0] assert success_results[0] == "Investigation mirrored successfully, channel: group2" new_context = demisto.setIntegrationContext.call_args[0][0] new_mirrors = js.loads(new_context["mirrors"]) our_mirror_filter = list(filter(lambda m: m["investigation_id"] == "999", new_mirrors)) our_mirror = our_mirror_filter[0] assert len(our_mirror_filter) == 1 assert our_mirror == new_mirror def test_check_for_mirrors(mocker): from Slack import check_for_mirrors new_user = { "name": "perikles", "profile": { "email": "perikles@acropoli.com", }, "id": "U012B3CUI", } def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): users = {"members": js.loads(USERS)} users["members"].append(new_user) return users # Set mirrors = js.loads(MIRRORS) mirrors.append( { "channel_id": "new_group", "channel_name": "channel", "investigation_id": "999", "mirror_type": "all", "mirror_direction": "both", "mirror_to": "group", "auto_close": True, "mirrored": False, } ) set_integration_context({"mirrors": js.dumps(mirrors), "users": USERS, "conversations": CONVERSATIONS, "bot_id": "W12345678"}) new_mirror = { "channel_id": "new_group", "channel_name": "channel", "investigation_id": "999", "mirror_type": "all", "mirror_direction": "both", "mirror_to": "group", "auto_close": True, "mirrored": True, } mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object( demisto, "mirrorInvestigation", return_value=[ {"email": "spengler@ghostbusters.example.com", "username": "spengler"}, {"email": "perikles@acropoli.com", "username": "perikles"}, ], ) # Arrange check_for_mirrors() calls = slack.WebClient.api_call.call_args_list users_call = [c for c in calls if c[0][0] == "users.list"] invite_call = [c for c in calls if c[0][0] == "conversations.invite"] mirror_id = demisto.mirrorInvestigation.call_args[0][0] mirror_type = demisto.mirrorInvestigation.call_args[0][1] auto_close = demisto.mirrorInvestigation.call_args[0][2] new_context = demisto.setIntegrationContext.call_args[0][0] new_mirrors = js.loads(new_context["mirrors"]) new_users = js.loads(new_context["users"]) our_mirror_filter = list(filter(lambda m: m["investigation_id"] == "999", new_mirrors)) our_mirror = our_mirror_filter[0] our_user_filter = list(filter(lambda u: u["id"] == "U012B3CUI", new_users)) our_user = our_user_filter[0] invited_users = [c[1]["json"]["users"] for c in invite_call] channel = [c[1]["json"]["channel"] for c in invite_call] # Assert assert len(users_call) == 1 assert len(invite_call) == 2 assert invited_users == ["U012A3CDE", "U012B3CUI"] assert channel == ["new_group", "new_group"] assert demisto.setIntegrationContext.call_count == 1 assert len(our_mirror_filter) == 1 assert our_mirror == new_mirror assert len(our_user_filter) == 1 assert our_user == new_user assert mirror_id == "999" assert mirror_type == "all:both" assert auto_close is True def test_check_for_mirrors_no_updates(mocker): from Slack import check_for_mirrors # Set mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) # Arrange check_for_mirrors() # Assert assert demisto.getIntegrationContext.call_count == 1 assert demisto.setIntegrationContext.call_count == 0 def test_check_for_mirrors_email_user_not_matching(mocker): from Slack import check_for_mirrors def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): users = {"members": js.loads(USERS)} new_user = { "name": "nope", "profile": { "email": "perikles@acropoli.com", }, "id": "U012B3CUI", } users["members"].append(new_user) return users # Set mirrors = js.loads(MIRRORS) mirrors.append( { "channel_id": "new_group", "channel_name": "channel", "investigation_id": "999", "mirror_type": "all", "mirror_direction": "both", "mirror_to": "group", "auto_close": True, "mirrored": False, } ) set_integration_context({"mirrors": js.dumps(mirrors), "users": USERS, "conversations": CONVERSATIONS, "bot_id": "W12345678"}) mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object( demisto, "mirrorInvestigation", return_value=[ {"email": "spengler@ghostbusters.example.com", "username": "spengler"}, {"email": "perikles@acropoli.com", "username": "perikles"}, ], ) # Arrange check_for_mirrors() calls = slack.WebClient.api_call.call_args_list users_call = [c for c in calls if c[0][0] == "users.list"] invite_call = [c for c in calls if c[0][0] == "conversations.invite"] invited_users = [c[1]["json"]["users"] for c in invite_call] channel = [c[1]["json"]["channel"] for c in invite_call] assert demisto.setIntegrationContext.call_count == 1 # Assert assert len(users_call) == 1 assert len(invite_call) == 2 assert invited_users == ["U012A3CDE", "U012B3CUI"] assert channel == ["new_group", "new_group"] def test_check_for_mirrors_email_not_matching(mocker): from Slack import check_for_mirrors def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): users = {"members": js.loads(USERS)} new_user = { "name": "perikles", "profile": { "email": "bruce.wayne@pharmtech.zz", }, "id": "U012B3CUI", } users["members"].append(new_user) return users # Set mirrors = js.loads(MIRRORS) mirrors.append( { "channel_id": "new_group", "channel_name": "channel", "investigation_id": "999", "mirror_type": "all", "mirror_direction": "both", "mirror_to": "group", "auto_close": True, "mirrored": False, } ) set_integration_context({"mirrors": js.dumps(mirrors), "users": USERS, "conversations": CONVERSATIONS, "bot_id": "W12345678"}) mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object( demisto, "mirrorInvestigation", return_value=[ {"email": "spengler@ghostbusters.example.com", "username": "spengler"}, {"email": "", "username": "perikles"}, ], ) # Arrange check_for_mirrors() calls = slack.WebClient.api_call.call_args_list users_call = [c for c in calls if c[0][0] == "users.list"] invite_call = [c for c in calls if c[0][0] == "conversations.invite"] invited_users = [c[1]["json"]["users"] for c in invite_call] channel = [c[1]["json"]["channel"] for c in invite_call] # Assert assert len(users_call) == 1 assert len(invite_call) == 2 assert invited_users == ["U012A3CDE", "U012B3CUI"] assert channel == ["new_group", "new_group"] assert demisto.setIntegrationContext.call_count == 1 def test_check_for_mirrors_user_email_not_matching(mocker): from Slack import check_for_mirrors def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): users = {"members": js.loads(USERS)} new_user = { "name": "perikles", "profile": { "email": "perikles@acropoli.com", }, "id": "U012B3CUI", } users["members"].append(new_user) return users # Set mirrors = js.loads(MIRRORS) mirrors.append( { "channel_id": "new_group", "channel_name": "channel", "investigation_id": "999", "mirror_type": "all", "mirror_direction": "both", "mirror_to": "group", "auto_close": True, "mirrored": False, } ) set_integration_context({"mirrors": js.dumps(mirrors), "users": USERS, "conversations": CONVERSATIONS, "bot_id": "W12345678"}) mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object( demisto, "mirrorInvestigation", return_value=[ {"email": "spengler@ghostbusters.example.com", "username": "spengler"}, {"email": "bruce.wayne@pharmtech.zz", "username": "123"}, ], ) mocker.patch.object(demisto, "results") # Arrange check_for_mirrors() calls = slack.WebClient.api_call.call_args_list users_call = [c for c in calls if c[0][0] == "users.list"] invite_call = [c for c in calls if c[0][0] == "conversations.invite"] invited_users = [c[1]["json"]["users"] for c in invite_call] channel = [c[1]["json"]["channel"] for c in invite_call] error_results = demisto.results.call_args_list[0][0] # Assert assert demisto.setIntegrationContext.call_count == 1 assert error_results[0]["Contents"] == "User 123 not found in Slack" assert len(users_call) == 2 assert len(invite_call) == 1 assert invited_users == ["U012A3CDE"] assert channel == ["new_group"] @pytest.mark.asyncio async def test_slack_loop_should_exit(mocker): from Slack import slack_loop # Set class MyFuture: @staticmethod def done(): return True @staticmethod def exception(): return None async def yeah_im_not_going_to_run(time): return "sup" mocker.patch.object(asyncio, "sleep", side_effect=yeah_im_not_going_to_run) with pytest.raises(InterruptedError): mocker.patch.object(slack.RTMClient, "start", side_effect=[MyFuture()]) # Exits the while True mocker.patch.object(slack.RTMClient, "stop", side_effect=InterruptedError()) # Arrange await slack_loop() # Assert assert slack.RTMClient.start.call_count == 1 @pytest.mark.asyncio async def test_handle_dm_create_demisto_user(mocker): import Slack # Set async def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "conversations.open": return {"channel": {"id": "ey"}} else: return "sup" async def fake_translate(message: str, user_name: str, user_email: str, demisto_user: dict): return "sup" mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "findUser", return_value={"id": "demisto_id"}) mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) mocker.patch.object(Slack, "translate_create", side_effect=fake_translate) user = js.loads(USERS)[0] # Arrange await Slack.handle_dm(user, "open 123 incident", slack.WebClient) await Slack.handle_dm(user, "new incident abu ahmad", slack.WebClient) await Slack.handle_dm(user, "incident create 817", slack.WebClient) await Slack.handle_dm(user, "incident open", slack.WebClient) await Slack.handle_dm(user, "incident new", slack.WebClient) await Slack.handle_dm(user, "create incident name=abc type=Access", slack.WebClient) # Assert assert Slack.translate_create.call_count == 6 incident_string = Slack.translate_create.call_args[0][0] user_name = Slack.translate_create.call_args[0][1] user_email = Slack.translate_create.call_args[0][2] demisto_user = Slack.translate_create.call_args[0][3] assert demisto_user == {"id": "demisto_id"} assert user_name == "spengler" assert user_email == "spengler@ghostbusters.example.com" assert incident_string == "create incident name=abc type=Access" @pytest.mark.asyncio async def test_handle_dm_nondemisto_user_shouldnt_create(mocker): import Slack # Set async def fake_translate(message: str, user_name: str, user_email: str, demisto_user: dict): return "sup" async def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "conversations.open": return {"channel": {"id": "ey"}} else: return "sup" mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "findUser", return_value=None) mocker.patch.object(Slack, "translate_create", side_effect=fake_translate) mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) user = js.loads(USERS)[0] # Arrange await Slack.handle_dm(user, "create incident abc", slack.WebClient) # Assert assert Slack.translate_create.call_count == 0 @pytest.mark.asyncio async def test_handle_dm_nondemisto_user_should_create(mocker): import Slack mocker.patch.object(demisto, "params", return_value={"allow_incidents": "true"}) Slack.init_globals() # Set async def fake_translate(message: str, user_name: str, user_email: str, demisto_user: dict): return "sup" async def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "conversations.open": return {"channel": {"id": "ey"}} else: return "sup" mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "findUser", return_value=None) mocker.patch.object(Slack, "translate_create", side_effect=fake_translate) mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) user = js.loads(USERS)[0] # Arrange await Slack.handle_dm(user, "create incident abc", slack.WebClient) # Assert assert Slack.translate_create.call_count == 1 demisto_user = Slack.translate_create.call_args[0][3] assert demisto_user is None @pytest.mark.asyncio async def test_handle_dm_non_create_nonexisting_user(mocker): from Slack import handle_dm # Set async def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "conversations.open": return {"channel": {"id": "ey"}} else: return "sup" mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "findUser", return_value=None) mocker.patch.object(demisto, "directMessage", return_value=None) mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) user = js.loads(USERS)[0] # Arrange await handle_dm(user, "wazup", slack.WebClient) message = demisto.directMessage.call_args[0][0] username = demisto.directMessage.call_args[0][1] email = demisto.directMessage.call_args[0][2] allow = demisto.directMessage.call_args[0][3] # Assert assert message == "wazup" assert username == "spengler" assert email == "spengler@ghostbusters.example.com" assert allow is False @pytest.mark.asyncio async def test_handle_dm_empty_message(mocker): from Slack import handle_dm # Set async def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "conversations.open": return {"channel": {"id": "ey"}} elif method == "chat.postMessage": text = json["text"] if not text: # noqa: RET503 raise InterruptedError else: return None mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "findUser", return_value=None) mocker.patch.object(demisto, "directMessage", return_value=None) mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) user = js.loads(USERS)[0] # Arrange await handle_dm(user, "wazup", slack.WebClient) calls = slack.WebClient.api_call.call_args_list chat_call = [c for c in calls if c[0][0] == "chat.postMessage"] message_args = chat_call[0][1]["json"] # Assert assert message_args["text"] == "Sorry, I could not perform the selected operation." @pytest.mark.asyncio async def test_handle_dm_create_with_error(mocker): import Slack # Set async def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "conversations.open": return {"channel": {"id": "ey"}} else: return "sup" mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "findUser", return_value={"id": "demisto_id"}) mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) mocker.patch.object(Slack, "translate_create", side_effect=InterruptedError("omg")) user = js.loads(USERS)[0] # Arrange await Slack.handle_dm(user, "open 123 incident", slack.WebClient) # Assert assert Slack.translate_create.call_count == 1 demisto_user = Slack.translate_create.call_args[0][3] incident_string = Slack.translate_create.call_args[0][0] calls = slack.WebClient.api_call.call_args_list chat_call = [c for c in calls if c[0][0] == "chat.postMessage"] message_args = chat_call[0][1]["json"] assert demisto_user == {"id": "demisto_id"} assert incident_string == "open 123 incident" assert message_args == {"channel": "ey", "text": "Failed creating incidents: omg"} @pytest.mark.asyncio async def test_translate_create(mocker): import Slack # Set async def this_doesnt_create_incidents(incidents_json, user_name, email, demisto_id): return {"id": "new_incident", "name": "New Incident"} mocker.patch.object(Slack, "create_incidents", side_effect=this_doesnt_create_incidents) mocker.patch.object(demisto, "demistoUrls", return_value={"server": "https://www.eizelulz.com:8443"}) demisto_user = {"id": "demisto_user"} json_message = "create incident json={“name”: “xyz”, “role”: “Analyst”}" wrong_json_message = 'create incident json={"name": "xyz"} name=abc' name_message = "create incident name=eyy" name_type_message = "create incident name= eyy type= Access" type_name_message = "create incident type= Access name= eyy" type_message = "create incident type= Phishing" success_message = ( "Successfully created incident New Incident.\n" " View it on: https://www.eizelulz.com:8443#/WarRoom/new_incident" ) # Arrange json_data = await Slack.translate_create(json_message, "spengler", "spengler@ghostbusters.example.com", demisto_user) wrong_json_data = await Slack.translate_create( wrong_json_message, "spengler", "spengler@ghostbusters.example.com", demisto_user ) name_data = await Slack.translate_create(name_message, "spengler", "spengler@ghostbusters.example.com", demisto_user) name_type_data = await Slack.translate_create( name_type_message, "spengler", "spengler@ghostbusters.example.com", demisto_user ) type_name_data = await Slack.translate_create( type_name_message, "spengler", "spengler@ghostbusters.example.com", demisto_user ) type_data = await Slack.translate_create(type_message, "spengler", "spengler@ghostbusters.example.com", demisto_user) create_args = Slack.create_incidents.call_args_list json_args = create_args[0][0][0] name_args = create_args[1][0][0] name_type_args = create_args[2][0][0] type_name_args = create_args[3][0][0] # Assert assert Slack.create_incidents.call_count == 4 assert json_args == [{"name": "xyz", "role": "Analyst"}] assert name_args == [{"name": "eyy"}] assert name_type_args == [{"name": "eyy", "type": "Access"}] assert type_name_args == [{"name": "eyy", "type": "Access"}] assert json_data == success_message assert wrong_json_data == "No other properties other than json should be specified." assert name_data == success_message assert name_type_data == success_message assert type_name_data == success_message assert type_data == "Please specify arguments in the following manner: name=<name> type=[type] or json=<json>." @pytest.mark.asyncio async def test_translate_create_newline_json(mocker): # Set import Slack async def this_doesnt_create_incidents(incidents_json, user_name, email, demisto_id): return {"id": "new_incident", "name": "New Incident"} mocker.patch.object(Slack, "create_incidents", side_effect=this_doesnt_create_incidents) mocker.patch.object(demisto, "demistoUrls", return_value={"server": "https://www.eizelulz.com:8443"}) demisto_user = {"id": "demisto_user"} json_message = """``` create incident json={ "name":"xyz", "details": "1.1.1.1,8.8.8.8" ``` }""" success_message = ( "Successfully created incident New Incident.\n" " View it on: https://www.eizelulz.com:8443#/WarRoom/new_incident" ) # Arrange json_data = await Slack.translate_create(json_message, "spengler", "spengler@ghostbusters.example.com", demisto_user) create_args = Slack.create_incidents.call_args json_args = create_args[0][0] # Assert assert Slack.create_incidents.call_count == 1 assert json_args == [{"name": "xyz", "details": "1.1.1.1,8.8.8.8"}] assert json_data == success_message @pytest.mark.asyncio async def test_create_incidents_no_labels(mocker): from Slack import create_incidents # Set mocker.patch.object(demisto, "createIncidents", return_value="nice") incidents = [{"name": "xyz", "details": "1.1.1.1,8.8.8.8"}] incidents_with_labels = [ { "name": "xyz", "details": "1.1.1.1,8.8.8.8", "labels": [ {"type": "Reporter", "value": "spengler"}, {"type": "ReporterEmail", "value": "spengler@ghostbusters.example.com"}, {"type": "Source", "value": "Slack"}, ], } ] # Arrange data = await create_incidents(incidents, "spengler", "spengler@ghostbusters.example.com", "demisto_user") incident_arg = demisto.createIncidents.call_args[0][0] user_arg = demisto.createIncidents.call_args[1]["userID"] assert incident_arg == incidents_with_labels assert user_arg == "demisto_user" assert data == "nice" @pytest.mark.asyncio async def test_create_incidents_with_labels(mocker): from Slack import create_incidents # Set mocker.patch.object(demisto, "createIncidents", return_value="nice") incidents = [ { "name": "xyz", "details": "1.1.1.1,8.8.8.8", "labels": [ {"type": "Reporter", "value": "spengler"}, {"type": "ReporterEmail", "value": "spengler@ghostbusters.example.com"}, ], } ] incidents_with_labels = [ { "name": "xyz", "details": "1.1.1.1,8.8.8.8", "labels": [ {"type": "Reporter", "value": "spengler"}, {"type": "ReporterEmail", "value": "spengler@ghostbusters.example.com"}, {"type": "Source", "value": "Slack"}, ], } ] # Arrange data = await create_incidents(incidents, "spengler", "spengler@ghostbusters.example.com", "demisto_user") incident_arg = demisto.createIncidents.call_args[0][0] user_arg = demisto.createIncidents.call_args[1]["userID"] assert incident_arg == incidents_with_labels assert user_arg == "demisto_user" assert data == "nice" @pytest.mark.asyncio async def test_get_user_by_id_async_user_exists(mocker): from Slack import get_user_by_id_async # Set async def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "users.info": # noqa: RET503 return {"user": js.loads(USERS)[0]} mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) user_id = "U012A3CDE" # Arrange user = await get_user_by_id_async(slack.WebClient, user_id) # Assert assert slack.WebClient.api_call.call_count == 0 assert demisto.setIntegrationContext.call_count == 0 assert user["name"] == "spengler" @pytest.mark.asyncio async def test_get_user_by_id_async_user_doesnt_exist(mocker): from Slack import get_user_by_id_async # Set async def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "users.info": # noqa: RET503 return {"user": js.loads(USERS)[0]} mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(demisto, "setIntegrationContext") mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) user_id = "XXXXXXX" # Arrange user = await get_user_by_id_async(slack.WebClient, user_id) # Assert assert slack.WebClient.api_call.call_count == 1 assert demisto.setIntegrationContext.call_count == 1 assert user["name"] == "spengler" @pytest.mark.asyncio async def test_handle_text(mocker): import Slack # Set async def fake_clean(text, client): return "מה הולך" mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(demisto, "addEntry") mocker.patch.object(Slack, "clean_message", side_effect=fake_clean) user = js.loads(USERS)[0] investigation_id = "999" text = "מה הולך" # Arrange await Slack.handle_text(slack.WebClient, investigation_id, text, user) entry_args = demisto.addEntry.call_args[1] # Assert assert demisto.addEntry.call_count == 1 assert entry_args["id"] == "999" assert entry_args["entry"] == "מה הולך" assert entry_args["username"] == "spengler" assert entry_args["email"] == "spengler@ghostbusters.example.com" assert entry_args["footer"] == "\n**From Slack**" def test_check_for_answers_no_proxy(mocker, requests_mock): import Slack # Set mocker.patch.object(demisto, "handleEntitlementForUser") mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(Slack, "get_current_utc_time", return_value=datetime.datetime(2019, 9, 26, 18, 38, 25)) mocker.patch.object(Slack, "add_info_headers") requests_mock.post("https://oproxy.demisto.ninja/slack-poll", json={"payload": PAYLOAD_JSON}) integration_context = get_integration_context() integration_context["questions"] = js.dumps( [ { "thread": "cool", "entitlement": "e95cb5a1-e394-4bc5-8ce0-508973aaf298@22|43", "reply": "Thanks bro", "expiry": "3000-09-26 18:38:25", "sent": "2019-09-26 18:38:25", "default_response": "NoResponse", "last_poll_time": "2019-09-26 18:34:25", } ] ) set_integration_context(integration_context) # Arrange Slack.check_for_answers() result_args = demisto.handleEntitlementForUser.call_args_list[0][0] # Assert assert demisto.handleEntitlementForUser.call_count == 1 assert demisto.setIntegrationContext.call_count == 1 assert requests_mock._adapter.last_request.proxies == OrderedDict() assert result_args[0] == "22" assert result_args[1] == "e95cb5a1-e394-4bc5-8ce0-508973aaf298" assert result_args[2] == "spengler@ghostbusters.example.com" assert result_args[3] == "Eyy" assert result_args[4] == "43" # Should delete the question assert demisto.getIntegrationContext()["questions"] == js.dumps([]) def test_check_for_answers_proxy(mocker, requests_mock): import Slack # Set mocker.patch.object(Slack, "handle_proxy", return_value={"https": "https_proxy", "http": "http_proxy"}) Slack.init_globals() proxy_dict = OrderedDict() proxy_dict["https"] = "https_proxy" proxy_dict["http"] = "http_proxy" mocker.patch.object(demisto, "handleEntitlementForUser") mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(Slack, "add_info_headers") mocker.patch.object(Slack, "get_current_utc_time", return_value=datetime.datetime(2019, 9, 26, 18, 38, 25)) requests_mock.post("https://oproxy.demisto.ninja/slack-poll", json={"payload": PAYLOAD_JSON}) integration_context = get_integration_context() integration_context["questions"] = js.dumps( [ { "thread": "cool", "entitlement": "e95cb5a1-e394-4bc5-8ce0-508973aaf298@22|43", "reply": "Thanks bro", "expiry": "3000-09-26 18:38:25", "sent": "2019-09-26 18:38:25", "default_response": "NoResponse", "last_poll_time": "2019-09-26 18:34:25", } ] ) set_integration_context(integration_context) # Arrange Slack.check_for_answers() result_args = demisto.handleEntitlementForUser.call_args_list[0][0] # Assert assert demisto.handleEntitlementForUser.call_count == 1 assert demisto.setIntegrationContext.call_count == 1 assert requests_mock._adapter.last_request.proxies == proxy_dict assert result_args[0] == "22" assert result_args[1] == "e95cb5a1-e394-4bc5-8ce0-508973aaf298" assert result_args[2] == "spengler@ghostbusters.example.com" assert result_args[3] == "Eyy" assert result_args[4] == "43" # Should delete the question assert demisto.getIntegrationContext()["questions"] == js.dumps([]) def test_check_for_answers_continue(mocker, requests_mock): import Slack # Set mocker.patch.object(demisto, "handleEntitlementForUser") mocker.patch.object(demisto, "error") mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(Slack, "add_info_headers") mocker.patch.object(Slack, "get_current_utc_time", return_value=datetime.datetime(2019, 9, 26, 18, 38, 25)) requests_mock.post( "https://oproxy.demisto.ninja/slack-poll", [ {"json": {}, "status_code": 200}, {"json": "error", "status_code": 401}, {"json": {"payload": PAYLOAD_JSON}, "status_code": 200}, ], ) integration_context = get_integration_context() integration_context["questions"] = js.dumps( [ { "thread": "notcool", "entitlement": "4404dae8-2d45-46bd-85fa-64779c12abe8@30|44", "reply": "Thanks bro", "expiry": "3000-09-26 18:38:25", "sent": "2019-09-26 18:38:25", "default_response": "NoResponse", "last_poll_time": "2019-09-26 18:34:25", }, { "thread": "notcool2", "entitlement": "4404dae8-2d45-46bd-85fa-64779c12abe7@30|44", "reply": "Thanks bro", "expiry": "3000-09-26 18:38:25", "sent": "2019-09-26 18:38:25", "default_response": "NoResponse", "last_poll_time": "2019-09-26 18:34:25", }, { "thread": "cool", "entitlement": "e95cb5a1-e394-4bc5-8ce0-508973aaf298@22|43", "reply": "Thanks bro", "expiry": "3000-09-26 18:38:25", "sent": "2019-09-26 18:38:25", "default_response": "NoResponse", "last_poll_time": "2019-09-26 18:34:25", }, ] ) set_integration_context(integration_context) # Arrange Slack.check_for_answers() result_args = demisto.handleEntitlementForUser.call_args_list[0][0] # Assert assert demisto.handleEntitlementForUser.call_count == 1 assert demisto.setIntegrationContext.call_count == 1 assert demisto.error.call_count == 1 assert result_args[0] == "22" assert result_args[1] == "e95cb5a1-e394-4bc5-8ce0-508973aaf298" assert result_args[2] == "spengler@ghostbusters.example.com" assert result_args[3] == "Eyy" assert result_args[4] == "43" # Should delete the question assert demisto.getIntegrationContext()["questions"] == js.dumps( [ { "thread": "notcool", "entitlement": "4404dae8-2d45-46bd-85fa-64779c12abe8@30|44", "reply": "Thanks bro", "expiry": "3000-09-26 18:38:25", "sent": "2019-09-26 18:38:25", "default_response": "NoResponse", "last_poll_time": "2019-09-26 18:38:25", }, { "thread": "notcool2", "entitlement": "4404dae8-2d45-46bd-85fa-64779c12abe7@30|44", "reply": "Thanks bro", "expiry": "3000-09-26 18:38:25", "sent": "2019-09-26 18:38:25", "default_response": "NoResponse", "last_poll_time": "2019-09-26 18:38:25", }, ] ) @pytest.mark.parametrize( "sent, expected_minutes", [(None, 1), ("2019-09-26 18:37:25", 1), ("2019-09-26 18:10:25", 2), ("2019-09-26 17:38:24", 5), ("2019-09-25 18:10:25", 5)], ) def test_get_poll_minutes(sent, expected_minutes): from Slack import get_poll_minutes # Set current = datetime.datetime(2019, 9, 26, 18, 38, 25) # Arrange minutes = get_poll_minutes(current, sent) # Assert assert minutes == expected_minutes def test_check_for_answers_no_answer(mocker, requests_mock): import Slack # Set mocker.patch.object(demisto, "handleEntitlementForUser") mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(Slack, "add_info_headers") mocker.patch.object(Slack, "get_current_utc_time", return_value=datetime.datetime(2019, 9, 26, 18, 38, 25)) requests_mock.post("https://oproxy.demisto.ninja/slack-poll", json={}) integration_context = get_integration_context() integration_context["questions"] = js.dumps( [ { "thread": "cool", "entitlement": "e95cb5a1-e394-4bc5-8ce0-508973aaf298@22|43", "reply": "Thanks bro", "expiry": "3000-09-26 18:38:25", "sent": "2019-09-26 18:38:25", "default_response": "NoResponse", "last_poll_time": "2019-09-26 18:34:25", }, { "thread": "notcool", "entitlement": "4404dae8-2d45-46bd-85fa-64779c12abe8@30|44", "reply": "Thanks bro", "expiry": "3000-09-26 18:38:25", "sent": "2019-09-26 18:38:25", "default_response": "NoResponse", "last_poll_time": "2019-09-26 18:34:25", }, ] ) set_integration_context(integration_context) # Arrange Slack.check_for_answers() # Assert assert demisto.handleEntitlementForUser.call_count == 0 assert demisto.setIntegrationContext.call_count == 1 # Should not delete the question assert demisto.getIntegrationContext()["questions"] == js.dumps( [ { "thread": "cool", "entitlement": "e95cb5a1-e394-4bc5-8ce0-508973aaf298@22|43", "reply": "Thanks bro", "expiry": "3000-09-26 18:38:25", "sent": "2019-09-26 18:38:25", "default_response": "NoResponse", "last_poll_time": "2019-09-26 18:38:25", }, { "thread": "notcool", "entitlement": "4404dae8-2d45-46bd-85fa-64779c12abe8@30|44", "reply": "Thanks bro", "expiry": "3000-09-26 18:38:25", "sent": "2019-09-26 18:38:25", "default_response": "NoResponse", "last_poll_time": "2019-09-26 18:38:25", }, ] ) def test_check_for_answers_no_polling(mocker, requests_mock): import Slack # Set mocker.patch.object(demisto, "handleEntitlementForUser") mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(Slack, "add_info_headers") mocker.patch.object(Slack, "get_current_utc_time", return_value=datetime.datetime(2019, 9, 26, 18, 38, 25)) requests_mock.post("https://oproxy.demisto.ninja/slack-poll", json={}) integration_context = get_integration_context() integration_context["questions"] = js.dumps( [ { "thread": "cool", "entitlement": "e95cb5a1-e394-4bc5-8ce0-508973aaf298@22|43", "reply": "Thanks bro", "expiry": "3000-09-26 18:38:25", "sent": "2019-09-26 18:38:25", "default_response": "NoResponse", "last_poll_time": "2019-09-26 18:38:00", }, { "thread": "notcool", "entitlement": "4404dae8-2d45-46bd-85fa-64779c12abe8@30|44", "reply": "Thanks bro", "expiry": "3000-09-26 18:38:25", "sent": "2019-09-26 18:38:25", "default_response": "NoResponse", "last_poll_time": "2019-09-26 18:38:01", }, ] ) set_integration_context(integration_context) # Arrange Slack.check_for_answers() # Assert assert demisto.handleEntitlementForUser.call_count == 0 assert demisto.setIntegrationContext.call_count == 0 assert demisto.getIntegrationContext.call_count == 1 # Should not delete the question assert demisto.getIntegrationContext()["questions"] == js.dumps( [ { "thread": "cool", "entitlement": "e95cb5a1-e394-4bc5-8ce0-508973aaf298@22|43", "reply": "Thanks bro", "expiry": "3000-09-26 18:38:25", "sent": "2019-09-26 18:38:25", "default_response": "NoResponse", "last_poll_time": "2019-09-26 18:38:00", }, { "thread": "notcool", "entitlement": "4404dae8-2d45-46bd-85fa-64779c12abe8@30|44", "reply": "Thanks bro", "expiry": "3000-09-26 18:38:25", "sent": "2019-09-26 18:38:25", "default_response": "NoResponse", "last_poll_time": "2019-09-26 18:38:01", }, ] ) def test_check_for_answers_no_answer_expires(mocker, requests_mock): import Slack # Set mocker.patch.object(demisto, "handleEntitlementForUser") mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(Slack, "add_info_headers") mocker.patch.object(Slack, "get_current_utc_time", return_value=datetime.datetime(2019, 9, 26, 18, 38, 25)) requests_mock.post("https://oproxy.demisto.ninja/slack-poll", json={}) integration_context = get_integration_context() integration_context["questions"] = js.dumps( [ { "thread": "cool", "entitlement": "e95cb5a1-e394-4bc5-8ce0-508973aaf298@22|43", "reply": "Thanks bro", "expiry": "3000-09-26 18:38:25", "default_response": "NoResponse", "last_poll_time": "2019-09-26 18:34:25", }, { "thread": "notcool", "entitlement": "4404dae8-2d45-46bd-85fa-64779c12abe8@30|44", "reply": "Thanks bro", "expiry": "2019-09-26 18:35:25", "default_response": "NoResponse", "last_poll_time": "2019-09-26 18:34:25", }, ] ) set_integration_context(integration_context) # Arrange Slack.check_for_answers() result_args = demisto.handleEntitlementForUser.call_args_list[0][0] # Assert assert demisto.handleEntitlementForUser.call_count == 1 assert demisto.setIntegrationContext.call_count == 1 assert result_args[0] == "30" assert result_args[1] == "4404dae8-2d45-46bd-85fa-64779c12abe8" assert result_args[2] == "" assert result_args[3] == "NoResponse" assert result_args[4] == "44" # Should not delete the question assert demisto.getIntegrationContext()["questions"] == js.dumps( [ { "thread": "cool", "entitlement": "e95cb5a1-e394-4bc5-8ce0-508973aaf298@22|43", "reply": "Thanks bro", "expiry": "3000-09-26 18:38:25", "default_response": "NoResponse", "last_poll_time": "2019-09-26 18:38:25", } ] ) def test_check_for_answers_error(mocker, requests_mock): import Slack # Set mocker.patch.object(demisto, "handleEntitlementForUser") mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(demisto, "error") mocker.patch.object(Slack, "add_info_headers") mocker.patch.object(Slack, "get_current_utc_time", return_value=datetime.datetime(2019, 9, 26, 18, 38, 25)) requests_mock.post("https://oproxy.demisto.ninja/slack-poll", json="error", status_code=401) integration_context = get_integration_context() integration_context["questions"] = js.dumps( [ { "thread": "cool", "entitlement": "e95cb5a1-e394-4bc5-8ce0-508973aaf298@22|43", "expiry": "3000-09-26 18:38:25", "default_response": "NoResponse", }, { "thread": "notcool", "entitlement": "4404dae8-2d45-46bd-85fa-64779c12abe8@30|44", "expiry": "3000-09-26 18:38:25", "default_response": "NoResponse", }, ] ) set_integration_context(integration_context) # Arrange Slack.check_for_answers() # Assert assert demisto.handleEntitlementForUser.call_count == 0 assert demisto.setIntegrationContext.call_count == 1 assert demisto.error.call_count == 2 # Should not delete the question assert demisto.getIntegrationContext()["questions"] == js.dumps( [ { "thread": "cool", "entitlement": "e95cb5a1-e394-4bc5-8ce0-508973aaf298@22|43", "expiry": "3000-09-26 18:38:25", "default_response": "NoResponse", "last_poll_time": "2019-09-26 18:38:25", }, { "thread": "notcool", "entitlement": "4404dae8-2d45-46bd-85fa-64779c12abe8@30|44", "expiry": "3000-09-26 18:38:25", "default_response": "NoResponse", "last_poll_time": "2019-09-26 18:38:25", }, ] ) def test_check_for_answers_handle_entitlement_error(mocker, requests_mock): import Slack # Set mocker.patch.object(demisto, "handleEntitlementForUser", side_effect=InterruptedError()) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(demisto, "error") mocker.patch.object(Slack, "add_info_headers") mocker.patch.object(Slack, "get_current_utc_time", return_value=datetime.datetime(2019, 9, 26, 18, 38, 25)) requests_mock.post("https://oproxy.demisto.ninja/slack-poll", json={"payload": PAYLOAD_JSON}, status_code=200) integration_context = get_integration_context() integration_context["questions"] = js.dumps( [ { "thread": "notcool", "entitlement": "4404dae8-2d45-46bd-85fa-64779c12abe8@30|44", "expiry": "3000-09-26 18:38:25", "default_response": "NoResponse", } ] ) set_integration_context(integration_context) # Arrange Slack.check_for_answers() # Assert assert demisto.handleEntitlementForUser.call_count == 1 assert demisto.setIntegrationContext.call_count == 1 assert demisto.error.call_count == 1 # Should not delete the question assert demisto.getIntegrationContext()["questions"] == js.dumps([]) @pytest.mark.asyncio async def test_check_entitlement(mocker): from Slack import check_and_handle_entitlement # Set mocker.patch.object(demisto, "handleEntitlementForUser") user = {"id": "U123456", "name": "test", "profile": {"email": "test@demisto.com"}} message1 = "hi test@demisto.com 4404dae8-2d45-46bd-85fa-64779c12abe8@e093ba05-3f3c-402e-81a7-149db969be5d goodbye" message2 = "hi test@demisto.com 4404dae8-2d45-46bd-85fa-64779c12abe8@22 goodbye" message3 = "hi test@demisto.com 4404dae8-2d45-46bd-85fa-64779c12abe8@e093ba05-3f3c-402e-81a7-149db969be5d|4 goodbye" message4 = "hi test@demisto.com 4404dae8-2d45-46bd-85fa-64779c12abe8@22|43 goodbye" message5 = "hi test@demisto.com 43434@e093ba05-3f3c-402e-81a7-149db969be5d goodbye" message6 = "hi test@demisto.com name-of-someone@mail-of-someone goodbye" message7 = "hi test@demisto.com 4404dae8-2d45-46bd-85fa-64779c12abe8@22_1|43 goodbye" message8 = "hi test@demisto.com 4404dae8-2d45-46bd-85fa-64779c12abe8@22_2 goodbye" # Arrange result1 = await check_and_handle_entitlement(message1, user, "") result2 = await check_and_handle_entitlement(message2, user, "") result3 = await check_and_handle_entitlement(message3, user, "") result4 = await check_and_handle_entitlement(message4, user, "") result5 = await check_and_handle_entitlement(message5, user, "") result6 = await check_and_handle_entitlement(message6, user, "") result7 = await check_and_handle_entitlement(message7, user, "") result8 = await check_and_handle_entitlement(message8, user, "") result1_args = demisto.handleEntitlementForUser.call_args_list[0][0] result2_args = demisto.handleEntitlementForUser.call_args_list[1][0] result3_args = demisto.handleEntitlementForUser.call_args_list[2][0] result4_args = demisto.handleEntitlementForUser.call_args_list[3][0] result7_args = demisto.handleEntitlementForUser.call_args_list[4][0] result8_args = demisto.handleEntitlementForUser.call_args_list[5][0] assert result1 == "Thank you for your response." assert result2 == "Thank you for your response." assert result3 == "Thank you for your response." assert result4 == "Thank you for your response." assert result5 == "" assert result6 == "" assert result7 == "Thank you for your response." assert result8 == "Thank you for your response." assert demisto.handleEntitlementForUser.call_count == 6 assert result1_args[0] == "e093ba05-3f3c-402e-81a7-149db969be5d" # incident ID assert result1_args[1] == "4404dae8-2d45-46bd-85fa-64779c12abe8" # GUID assert result1_args[2] == "test@demisto.com" # email assert result1_args[3] == "hi test@demisto.com goodbye" # content assert result1_args[4] == "" # task id assert result2_args[0] == "22" assert result2_args[1] == "4404dae8-2d45-46bd-85fa-64779c12abe8" assert result2_args[2] == "test@demisto.com" assert result2_args[3] == "hi test@demisto.com goodbye" assert result2_args[4] == "" assert result3_args[0] == "e093ba05-3f3c-402e-81a7-149db969be5d" assert result3_args[1] == "4404dae8-2d45-46bd-85fa-64779c12abe8" assert result3_args[2] == "test@demisto.com" assert result3_args[3] == "hi test@demisto.com goodbye" assert result3_args[4] == "4" assert result4_args[0] == "22" assert result4_args[1] == "4404dae8-2d45-46bd-85fa-64779c12abe8" assert result4_args[2] == "test@demisto.com" assert result4_args[3] == "hi test@demisto.com goodbye" assert result4_args[4] == "43" assert result7_args[0] == "22_1" assert result7_args[1] == "4404dae8-2d45-46bd-85fa-64779c12abe8" assert result7_args[2] == "test@demisto.com" assert result7_args[3] == "hi test@demisto.com goodbye" assert result7_args[4] == "43" assert result8_args[0] == "22_2" assert result8_args[1] == "4404dae8-2d45-46bd-85fa-64779c12abe8" assert result8_args[2] == "test@demisto.com" assert result8_args[3] == "hi test@demisto.com goodbye" assert result8_args[4] == "" @pytest.mark.asyncio async def test_check_entitlement_with_context(mocker): from Slack import check_and_handle_entitlement # Set mocker.patch.object(demisto, "handleEntitlementForUser") mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) user = {"id": "U123456", "name": "test", "profile": {"email": "test@demisto.com"}} integration_context = get_integration_context() integration_context["questions"] = js.dumps( [ {"thread": "cool", "entitlement": "4404dae8-2d45-46bd-85fa-64779c12abe8@22|43"}, {"thread": "notcool", "entitlement": "4404dae8-2d45-46bd-85fa-64779c12abe8@30|44"}, ] ) set_integration_context(integration_context) # Arrange await check_and_handle_entitlement("hola", user, "cool") result_args = demisto.handleEntitlementForUser.call_args_list[0][0] # Assert assert demisto.handleEntitlementForUser.call_count == 1 assert result_args[0] == "22" assert result_args[1] == "4404dae8-2d45-46bd-85fa-64779c12abe8" assert result_args[2] == "test@demisto.com" assert result_args[3] == "hola" assert result_args[4] == "43" # Should delete the question assert demisto.getIntegrationContext()["questions"] == js.dumps( [{"thread": "notcool", "entitlement": "4404dae8-2d45-46bd-85fa-64779c12abe8@30|44"}] ) def test_send_request(mocker): import Slack # Set def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "users.list": return {"members": js.loads(USERS)} elif method == "conversations.list": return {"channels": js.loads(CONVERSATIONS)} elif method == "conversations.open": return {"channel": {"id": "im_channel"}} return {} mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) mocker.patch.object(Slack, "send_file", return_value="neat") mocker.patch.object(Slack, "send_message", return_value="cool") # Arrange user_res = Slack.slack_send_request("spengler", None, None, message="Hi") channel_res = Slack.slack_send_request(None, "general", None, file_dict="file") user_args = Slack.send_message.call_args[0] channel_args = Slack.send_file.call_args[0] calls = slack.WebClient.api_call.call_args_list users_call = [c for c in calls if c[0][0] == "users.list"] conversations_call = [c for c in calls if c[0][0] == "conversations.list"] # Assert assert len(users_call) == 0 assert len(conversations_call) == 0 assert Slack.send_message.call_count == 1 assert Slack.send_file.call_count == 1 assert user_args[0] == ["im_channel"] assert user_args[1] == "" assert user_args[2] is False assert user_args[4] == "Hi" assert user_args[5] == "" assert channel_args[0] == ["C012AB3CD"] assert channel_args[1] == "file" assert channel_args[3] == "" assert user_res == "cool" assert channel_res == "neat" def test_send_request_different_name(mocker): import Slack # Set def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "users.list": return {"members": js.loads(USERS)} elif method == "conversations.list": return {"channels": js.loads(CONVERSATIONS)} elif method == "conversations.open": return {"channel": {"id": "im_channel"}} return {} mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(demisto, "setIntegrationContext") mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) mocker.patch.object(Slack, "send_message", return_value="cool") # Arrange channel_res = Slack.slack_send_request(None, "incident-684", None, message="Hi") channel_args = Slack.send_message.call_args[0] calls = slack.WebClient.api_call.call_args_list users_call = [c for c in calls if c[0][0] == "users.list"] conversations_call = [c for c in calls if c[0][0] == "conversations.list"] # Assert assert len(users_call) == 0 assert len(conversations_call) == 0 assert Slack.send_message.call_count == 1 assert channel_args[0] == ["GKB19PA3V"] assert channel_args[1] == "" assert channel_args[2] is False assert channel_args[4] == "Hi" assert channel_args[5] == "" assert channel_res == "cool" def test_send_request_with_severity(mocker): import Slack mocker.patch.object( demisto, "params", return_value={"incidentNotificationChannel": "general", "min_severity": "High", "notify_incidents": True}, ) Slack.init_globals() # Set def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "users.list": return {"members": js.loads(USERS)} elif method == "conversations.list": return {"channels": js.loads(CONVERSATIONS)} elif method == "conversations.open": return {"channel": {"id": "im_channel"}} return {} mocker.patch.object(demisto, "args", return_value={"severity": "3", "message": "!!!", "messageType": "incidentOpened"}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(demisto, "results") mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) mocker.patch.object(Slack, "send_message", return_value=SLACK_RESPONSE) # Arrange Slack.slack_send() send_args = Slack.send_message.call_args[0] results = demisto.results.call_args_list[0][0] calls = slack.WebClient.api_call.call_args_list users_call = [c for c in calls if c[0][0] == "users.list"] conversations_call = [c for c in calls if c[0][0] == "conversations.list"] # Assert assert len(users_call) == 0 assert len(conversations_call) == 0 assert Slack.send_message.call_count == 1 assert send_args[0] == ["C012AB3CD"] assert send_args[1] is None assert send_args[2] is False assert send_args[4] == "!!!" assert send_args[5] == "" assert results[0]["HumanReadable"] == "Message sent to Slack successfully.\nThread ID is: cool" def test_send_request_with_notification_channel(mocker): import Slack mocker.patch.object( demisto, "params", return_value={"incidentNotificationChannel": "general", "min_severity": "High", "notify_incidents": True}, ) Slack.init_globals() # Set def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "users.list": return {"members": js.loads(USERS)} elif method == "conversations.list": return {"channels": js.loads(CONVERSATIONS)} elif method == "conversations.open": return {"channel": {"id": "im_channel"}} return {} mocker.patch.object( demisto, "args", return_value={ "channel": "incidentNotificationChannel", "severity": "4", "message": "!!!", "messageType": "incidentOpened", }, ) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(demisto, "results") mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) mocker.patch.object(Slack, "send_message", return_value=SLACK_RESPONSE) # Arrange Slack.slack_send() send_args = Slack.send_message.call_args[0] results = demisto.results.call_args_list[0][0] calls = slack.WebClient.api_call.call_args_list users_call = [c for c in calls if c[0][0] == "users.list"] conversations_call = [c for c in calls if c[0][0] == "conversations.list"] # Assert assert len(users_call) == 0 assert len(conversations_call) == 0 assert Slack.send_message.call_count == 1 assert send_args[0] == ["C012AB3CD"] assert send_args[1] is None assert send_args[2] is False assert send_args[4] == "!!!" assert send_args[5] == "" assert results[0]["HumanReadable"] == "Message sent to Slack successfully.\nThread ID is: cool" @pytest.mark.parametrize("notify", [False, True]) def test_send_request_with_notification_channel_as_dest(mocker, notify): import Slack mocker.patch.object( demisto, "params", return_value={"incidentNotificationChannel": "general", "min_severity": "High", "notify_incidents": notify}, ) Slack.init_globals() # Set def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "users.list": return {"members": js.loads(USERS)} elif method == "conversations.list": return {"channels": js.loads(CONVERSATIONS)} elif method == "conversations.open": return {"channel": {"id": "im_channel"}} return {} mocker.patch.object(demisto, "args", return_value={"channel": "general", "message": "!!!"}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(demisto, "results") mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) mocker.patch.object(Slack, "send_message", return_value=SLACK_RESPONSE) # Arrange Slack.slack_send() send_args = Slack.send_message.call_args[0] results = demisto.results.call_args_list[0][0] calls = slack.WebClient.api_call.call_args_list users_call = [c for c in calls if c[0][0] == "users.list"] conversations_call = [c for c in calls if c[0][0] == "conversations.list"] # Assert assert len(users_call) == 0 assert len(conversations_call) == 0 assert Slack.send_message.call_count == 1 assert send_args[0] == ["C012AB3CD"] assert send_args[1] is None assert send_args[2] is False assert send_args[4] == "!!!" assert send_args[5] == "" assert results[0]["HumanReadable"] == "Message sent to Slack successfully.\nThread ID is: cool" def test_send_request_with_entitlement(mocker): import Slack # Set def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "users.list": return {"members": js.loads(USERS)} elif method == "conversations.list": return {"channels": js.loads(CONVERSATIONS)} elif method == "conversations.open": return {"channel": {"id": "im_channel"}} return {} mocker.patch.object( demisto, "args", return_value={ "message": js.dumps( { "message": "hi test@demisto.com", "entitlement": "4404dae8-2d45-46bd-85fa-64779c12abe8@22|43", "reply": "Thanks bro", "expiry": "2019-09-26 18:38:25", "default_response": "NoResponse", } ), "to": "spengler", }, ) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(demisto, "results") mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) mocker.patch.object(Slack, "send_message", return_value=SLACK_RESPONSE) mocker.patch.object(Slack, "get_current_utc_time", return_value=datetime.datetime(2019, 9, 26, 18, 38, 25)) questions = [ { "thread": "cool", "entitlement": "4404dae8-2d45-46bd-85fa-64779c12abe8@22|43", "reply": "Thanks bro", "expiry": "2019-09-26 18:38:25", "sent": "2019-09-26 18:38:25", "default_response": "NoResponse", } ] # Arrange Slack.slack_send() send_args = Slack.send_message.call_args[0] results = demisto.results.call_args_list[0][0] calls = slack.WebClient.api_call.call_args_list users_call = [c for c in calls if c[0][0] == "users.list"] conversations_call = [c for c in calls if c[0][0] == "conversations.list"] # Assert assert len(users_call) == 0 assert len(conversations_call) == 0 assert Slack.send_message.call_count == 1 assert send_args[0] == ["im_channel"] assert send_args[1] is None assert send_args[2] is False assert send_args[4] == "hi test@demisto.com" assert send_args[5] == "" assert results[0]["HumanReadable"] == "Message sent to Slack successfully.\nThread ID is: cool" assert demisto.getIntegrationContext()["questions"] == js.dumps(questions) def test_send_request_with_entitlement_blocks(mocker): import Slack # Set def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "users.list": return {"members": js.loads(USERS)} elif method == "conversations.list": return {"channels": js.loads(CONVERSATIONS)} elif method == "conversations.open": return {"channel": {"id": "im_channel"}} return {} mocker.patch.object( demisto, "args", return_value={ "blocks": js.dumps( { "blocks": js.dumps(BLOCK_JSON), "entitlement": "e95cb5a1-e394-4bc5-8ce0-508973aaf298@22|43", "reply": "Thanks bro", "expiry": "2019-09-26 18:38:25", "default_response": "NoResponse", } ), "to": "spengler", }, ) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(demisto, "results") mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) mocker.patch.object(Slack, "send_message", return_value=SLACK_RESPONSE) mocker.patch.object(Slack, "get_current_utc_time", return_value=datetime.datetime(2019, 9, 26, 18, 38, 25)) questions = [ { "thread": "cool", "entitlement": "e95cb5a1-e394-4bc5-8ce0-508973aaf298@22|43", "reply": "Thanks bro", "expiry": "2019-09-26 18:38:25", "sent": "2019-09-26 18:38:25", "default_response": "NoResponse", } ] # Arrange Slack.slack_send() send_args = Slack.send_message.call_args[0] results = demisto.results.call_args_list[0][0] calls = slack.WebClient.api_call.call_args_list users_call = [c for c in calls if c[0][0] == "users.list"] conversations_call = [c for c in calls if c[0][0] == "conversations.list"] # Assert assert len(users_call) == 0 assert len(conversations_call) == 0 assert Slack.send_message.call_count == 1 assert send_args[0] == ["im_channel"] assert send_args[1] is None assert send_args[2] is False assert send_args[4] == "" assert send_args[6] == js.dumps(BLOCK_JSON) assert results[0]["HumanReadable"] == "Message sent to Slack successfully.\nThread ID is: cool" assert demisto.getIntegrationContext()["questions"] == js.dumps(questions) def test_send_request_with_entitlement_blocks_message(mocker): import Slack # Set def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "users.list": return {"members": js.loads(USERS)} elif method == "conversations.list": return {"channels": js.loads(CONVERSATIONS)} elif method == "conversations.open": return {"channel": {"id": "im_channel"}} return {} mocker.patch.object( demisto, "args", return_value={ "message": "wat up", "blocks": js.dumps( { "blocks": js.dumps(BLOCK_JSON), "entitlement": "e95cb5a1-e394-4bc5-8ce0-508973aaf298@22|43", "reply": "Thanks bro", "expiry": "2019-09-26 18:38:25", "default_response": "NoResponse", } ), "to": "spengler", }, ) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(demisto, "results") mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) mocker.patch.object(Slack, "send_message", return_value=SLACK_RESPONSE) mocker.patch.object(Slack, "get_current_utc_time", return_value=datetime.datetime(2019, 9, 26, 18, 38, 25)) questions = [ { "thread": "cool", "entitlement": "e95cb5a1-e394-4bc5-8ce0-508973aaf298@22|43", "reply": "Thanks bro", "expiry": "2019-09-26 18:38:25", "sent": "2019-09-26 18:38:25", "default_response": "NoResponse", } ] # Arrange Slack.slack_send() send_args = Slack.send_message.call_args[0] results = demisto.results.call_args_list[0][0] calls = slack.WebClient.api_call.call_args_list users_call = [c for c in calls if c[0][0] == "users.list"] conversations_call = [c for c in calls if c[0][0] == "conversations.list"] # Assert assert len(users_call) == 0 assert len(conversations_call) == 0 assert Slack.send_message.call_count == 1 assert send_args[0] == ["im_channel"] assert send_args[1] is None assert send_args[2] is False assert send_args[4] == "wat up" assert send_args[6] == js.dumps(BLOCK_JSON) assert results[0]["HumanReadable"] == "Message sent to Slack successfully.\nThread ID is: cool" assert demisto.getIntegrationContext()["questions"] == js.dumps(questions) def test_send_to_user_lowercase(mocker): import Slack # Set def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "users.list": return {"members": js.loads(USERS)} elif method == "conversations.list": return {"channels": js.loads(CONVERSATIONS)} elif method == "conversations.open": return {"channel": {"id": "im_channel"}} return {} mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(demisto, "args", return_value={"to": "glenda@south.oz.coven", "message": "hi"}) mocker.patch.object(demisto, "results") mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) mocker.patch.object(Slack, "send_file", return_value="neat") mocker.patch.object(Slack, "send_message", return_value=SLACK_RESPONSE) # Arrange Slack.slack_send() send_args = Slack.send_message.call_args[0] results = demisto.results.call_args_list[0][0] calls = slack.WebClient.api_call.call_args_list users_call = [c for c in calls if c[0][0] == "users.list"] conversations_call = [c for c in calls if c[0][0] == "conversations.list"] # Assert assert len(users_call) == 0 assert len(conversations_call) == 0 assert Slack.send_message.call_count == 1 assert send_args[0] == ["im_channel"] assert send_args[1] is None assert send_args[2] is False assert send_args[4] == "hi" assert send_args[5] == "" assert results[0]["HumanReadable"] == "Message sent to Slack successfully.\nThread ID is: cool" def test_send_request_with_severity_user_doesnt_exist(mocker, capfd): import Slack mocker.patch.object( demisto, "params", return_value={"incidentNotificationChannel": "general", "min_severity": "High", "notify_incidents": True}, ) Slack.init_globals() # Set def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "users.list": return {"members": js.loads(USERS)} elif method == "conversations.list": return {"channels": js.loads(CONVERSATIONS)} elif method == "conversations.open": return {"channel": {"id": "im_channel"}} return {} mocker.patch.object( demisto, "args", return_value={"severity": "3", "message": "!!!", "messageType": "incidentOpened", "to": "alexios"} ) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(demisto, "setIntegrationContext") mocker.patch.object(demisto, "results") mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) mocker.patch.object(Slack, "send_message", return_value=SLACK_RESPONSE) # Arrange with capfd.disabled(): Slack.slack_send() send_args = Slack.send_message.call_args[0] results = demisto.results.call_args_list[0][0] calls = slack.WebClient.api_call.call_args_list users_call = [c for c in calls if c[0][0] == "users.list"] conversations_call = [c for c in calls if c[0][0] == "conversations.list"] # Assert assert len(users_call) == 1 assert len(conversations_call) == 0 assert Slack.send_message.call_count == 1 assert send_args[0] == ["C012AB3CD"] assert send_args[1] is None assert send_args[2] is False assert send_args[4] == "!!!" assert send_args[5] == "" assert results[0]["HumanReadable"] == "Message sent to Slack successfully.\nThread ID is: cool" def test_send_request_no_user(mocker, capfd): import Slack # Set def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "users.list": return {"members": js.loads(USERS)} elif method == "conversations.list": return {"channels": js.loads(CONVERSATIONS)} elif method == "conversations.open": return {"channel": {"id": "im_channel"}} return {} mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) return_error_mock = mocker.patch(RETURN_ERROR_TARGET, side_effect=InterruptedError()) mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) mocker.patch.object(Slack, "send_file", return_value="neat") mocker.patch.object(Slack, "send_message", return_value=SLACK_RESPONSE) # Arrange with capfd.disabled(), pytest.raises(InterruptedError): Slack.slack_send_request("alexios", None, None, message="Hi") err_msg = return_error_mock.call_args[0][0] calls = slack.WebClient.api_call.call_args_list users_call = [c for c in calls if c[0][0] == "users.list"] # Assert assert return_error_mock.call_count == 1 assert err_msg == "Could not find any destination to send to." assert len(users_call) == 1 assert Slack.send_message.call_count == 0 assert Slack.send_file.call_count == 0 def test_send_request_no_severity(mocker): import Slack mocker.patch.object( demisto, "params", return_value={"incidentNotificationChannel": "general", "min_severity": "High", "notify_incidents": True}, ) Slack.init_globals() # Set def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "users.list": return {"members": js.loads(USERS)} elif method == "conversations.list": return {"channels": js.loads(CONVERSATIONS)} elif method == "conversations.open": return {"channel": {"id": "im_channel"}} return {} mocker.patch.object(demisto, "args", return_value={"severity": "2", "message": "!!!", "messageType": "incidentOpened"}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(demisto, "results") return_error_mock = mocker.patch(RETURN_ERROR_TARGET, side_effect=InterruptedError()) mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) mocker.patch.object(Slack, "send_message", return_value=SLACK_RESPONSE) # Arrange with pytest.raises(InterruptedError): Slack.slack_send() err_msg = return_error_mock.call_args[0][0] calls = slack.WebClient.api_call.call_args_list users_call = [c for c in calls if c[0][0] == "users.list"] # Assert assert return_error_mock.call_count == 1 assert err_msg == "Either a user, group or channel must be provided." assert len(users_call) == 0 assert Slack.send_message.call_count == 0 def test_send_request_zero_severity(mocker): import Slack mocker.patch.object( demisto, "params", return_value={"incidentNotificationChannel": "general", "min_severity": "High", "notify_incidents": True}, ) Slack.init_globals() # Set def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "users.list": return {"members": js.loads(USERS)} elif method == "conversations.list": return {"channels": js.loads(CONVERSATIONS)} elif method == "conversations.open": return {"channel": {"id": "im_channel"}} return {} mocker.patch.object(demisto, "args", return_value={"severity": "0", "message": "!!!", "messageType": "incidentOpened"}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(demisto, "results") return_error_mock = mocker.patch(RETURN_ERROR_TARGET, side_effect=InterruptedError()) mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) mocker.patch.object(Slack, "send_message", return_value=SLACK_RESPONSE) # Arrange with pytest.raises(InterruptedError): Slack.slack_send() err_msg = return_error_mock.call_args[0][0] calls = slack.WebClient.api_call.call_args_list users_call = [c for c in calls if c[0][0] == "users.list"] # Assert assert return_error_mock.call_count == 1 assert err_msg == "Either a user, group or channel must be provided." assert len(users_call) == 0 assert Slack.send_message.call_count == 0 def test_send_message(mocker): import Slack # Set link = "https://www.eizelulz.com:8443/#/WarRoom/727" mocker.patch.object(demisto, "investigation", return_value={"type": 1}) mocker.patch.object(demisto, "demistoUrls", return_value={"warRoom": link}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(Slack, "send_message_to_destinations") mocker.patch.object(Slack, "invite_users_to_conversation") # Arrange Slack.send_message(["channel"], None, None, demisto.getIntegrationContext(), "yo", None, "") args = Slack.send_message_to_destinations.call_args[0] # Assert assert Slack.send_message_to_destinations.call_count == 1 assert args[0] == ["channel"] assert args[1] == "yo" + "\nView it on: " + link assert args[2] is None def test_send_message_to_destinations(mocker): import Slack # Set link = "https://www.eizelulz.com:8443/#/WarRoom/727" mocker.patch.object(demisto, "investigation", return_value={"type": 1}) mocker.patch.object(demisto, "demistoUrls", return_value={"warRoom": link}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(Slack, "send_slack_request_sync") # Arrange Slack.send_message_to_destinations(["channel"], "yo", None, "") args = Slack.send_slack_request_sync.call_args[1] # Assert assert Slack.send_slack_request_sync.call_count == 1 assert "http_verb" not in args assert args["body"]["channel"] == "channel" assert args["body"]["text"] def test_send_file_to_destinations(mocker): import Slack # Set link = "https://www.eizelulz.com:8443/#/WarRoom/727" mocker.patch.object(demisto, "investigation", return_value={"type": 1}) mocker.patch.object(demisto, "demistoUrls", return_value={"warRoom": link}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(Slack, "send_slack_request_sync") # Arrange Slack.send_file_to_destinations(["channel"], {"name": "name", "path": "yo"}, None) args = Slack.send_slack_request_sync.call_args[1] # Assert assert Slack.send_slack_request_sync.call_count == 1 assert "http_verb" not in args assert args["file_"] == "yo" assert args["body"]["filename"] == "name" def test_send_message_retry(mocker): import Slack from slack.errors import SlackApiError # Set link = "https://www.eizelulz.com:8443/#/WarRoom/727" mocker.patch.object(demisto, "investigation", return_value={"type": 1}) mocker.patch.object(demisto, "demistoUrls", return_value={"warRoom": link}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(Slack, "invite_users_to_conversation") # Arrange mocker.patch.object(Slack, "send_message_to_destinations", side_effect=[SlackApiError("not_in_channel", None), "ok"]) Slack.send_message(["channel"], None, None, demisto.getIntegrationContext(), "yo", None, "") args = Slack.send_message_to_destinations.call_args_list[1][0] # Assert assert Slack.send_message_to_destinations.call_count == 2 assert args[0] == ["channel"] assert args[1] == "yo" + "\nView it on: " + link assert args[2] is None def test_send_file_retry(mocker): import Slack from slack.errors import SlackApiError # Set mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(Slack, "invite_users_to_conversation") # Arrange mocker.patch.object(Slack, "send_file_to_destinations", side_effect=[SlackApiError("not_in_channel", None), "ok"]) Slack.send_file(["channel"], "file", demisto.getIntegrationContext(), None) args = Slack.send_file_to_destinations.call_args_list[1][0] # Assert assert Slack.send_file_to_destinations.call_count == 2 assert args[0] == ["channel"] assert args[1] == "file" assert args[2] is None def test_close_channel_with_name(mocker): import Slack # Set mocker.patch.object(demisto, "args", return_value={"channel": "general"}) mocker.patch.object(demisto, "investigation", return_value={"id": "681"}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(Slack, "get_conversation_by_name", return_value={"id": "C012AB3CD"}) mocker.patch.object(slack.WebClient, "api_call") mocker.patch.object(demisto, "results") # Arrange Slack.close_channel() close_args = slack.WebClient.api_call.call_args success_results = demisto.results.call_args[0] # Assert assert Slack.get_conversation_by_name.call_count == 1 assert slack.WebClient.api_call.call_count == 1 assert success_results[0] == "Channel successfully archived." assert close_args[0][0] == "conversations.archive" assert close_args[1]["json"]["channel"] == "C012AB3CD" def test_close_channel_should_delete_mirror(mocker): from Slack import close_channel # Set mirrors = js.loads(MIRRORS) mirrors.pop(0) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(demisto, "mirrorInvestigation") mocker.patch.object(demisto, "investigation", return_value={"id": "681"}) mocker.patch.object(slack.WebClient, "api_call") # Arrange close_channel() archive_args = slack.WebClient.api_call.call_args context_args = demisto.setIntegrationContext.call_args[0][0] context_args_mirrors = js.loads(context_args["mirrors"]) mirror_args = demisto.mirrorInvestigation.call_args[0] # Assert assert archive_args[0][0] == "conversations.archive" assert archive_args[1]["json"]["channel"] == "GKQ86DVPH" assert context_args_mirrors == mirrors assert mirror_args == ("681", "none:both", True) def test_close_channel_should_delete_mirrors(mocker): from Slack import close_channel # Set mirrors = js.loads(MIRRORS) mirrors.pop(1) mirrors.pop(1) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(demisto, "investigation", return_value={"id": "684"}) mocker.patch.object(demisto, "mirrorInvestigation") mocker.patch.object(slack.WebClient, "api_call") # Arrange close_channel() archive_args = slack.WebClient.api_call.call_args context_args = demisto.setIntegrationContext.call_args[0][0] mirrors_args = [args[0] for args in demisto.mirrorInvestigation.call_args_list] context_args_mirrors = js.loads(context_args["mirrors"]) # Assert assert archive_args[0][0] == "conversations.archive" assert archive_args[1]["json"]["channel"] == "GKB19PA3V" assert context_args_mirrors == mirrors assert mirrors_args == [("684", "none:both", True), ("692", "none:both", True)] def test_get_conversation_by_name_paging(mocker): from Slack import get_conversation_by_name # Set def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): if method == "conversations.list": # noqa: RET503 if len(params) == 2: return {"channels": js.loads(CONVERSATIONS), "response_metadata": {"next_cursor": "dGVhbTpDQ0M3UENUTks="}} else: return {"channels": [{"id": "C248918AB", "name": "lulz"}], "response_metadata": {"next_cursor": ""}} mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) # Arrange channel = get_conversation_by_name("lulz") args = slack.WebClient.api_call.call_args_list first_args = args[0][1] second_args = args[1][1] # Assert assert args[0][0][0] == "conversations.list" assert len(first_args["params"]) == 2 assert first_args["params"]["limit"] == 200 assert len(second_args["params"]) == 3 assert second_args["params"]["cursor"] == "dGVhbTpDQ0M3UENUTks=" assert channel["id"] == "C248918AB" assert slack.WebClient.api_call.call_count == 2 def test_send_file_no_args_investigation(mocker): import Slack # Set mocker.patch.object(demisto, "args", return_value={}) mocker.patch.object(demisto, "investigation", return_value={"id": "681"}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(demisto, "getFilePath", return_value={"path": "path", "name": "name"}) mocker.patch.object(demisto, "results") mocker.patch.object(Slack, "slack_send_request", return_value="cool") # Arrange Slack.slack_send_file() send_args = Slack.slack_send_request.call_args success_results = demisto.results.call_args[0] # Assert assert Slack.slack_send_request.call_count == 1 assert success_results[0] == "File sent to Slack successfully." assert send_args[0][1] == "incident-681" assert send_args[1]["file_dict"] == {"path": "path", "name": "name", "comment": ""} def test_send_file_no_args_no_investigation(mocker): import Slack # Set mocker.patch.object(demisto, "args", return_value={}) mocker.patch.object(demisto, "investigation", return_value={"id": "999"}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(Slack, "slack_send_request", return_value="cool") return_error_mock = mocker.patch(RETURN_ERROR_TARGET, side_effect=InterruptedError()) # Arrange with pytest.raises(InterruptedError): Slack.slack_send_file() err_msg = return_error_mock.call_args[0][0] # Assert assert Slack.slack_send_request.call_count == 0 assert err_msg == "Either a user, group or channel must be provided." def test_set_topic(mocker): import Slack # Set mocker.patch.object(demisto, "args", return_value={"channel": "general", "topic": "ey"}) mocker.patch.object(demisto, "investigation", return_value={"id": "681"}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(Slack, "get_conversation_by_name", return_value={"id": "C012AB3CD"}) mocker.patch.object(slack.WebClient, "api_call") mocker.patch.object(demisto, "results") # Arrange Slack.set_channel_topic() send_args = slack.WebClient.api_call.call_args success_results = demisto.results.call_args[0] # Assert assert Slack.get_conversation_by_name.call_count == 1 assert slack.WebClient.api_call.call_count == 1 assert success_results[0] == "Topic successfully set." assert send_args[0][0] == "conversations.setTopic" assert send_args[1]["json"]["channel"] == "C012AB3CD" assert send_args[1]["json"]["topic"] == "ey" def test_set_topic_no_args_investigation(mocker): import Slack # Set new_mirror = { "channel_id": "GKQ86DVPH", "channel_name": "incident-681", "channel_topic": "ey", "investigation_id": "681", "mirror_type": "all", "mirror_direction": "both", "mirror_to": "group", "auto_close": True, "mirrored": True, } mocker.patch.object(demisto, "args", return_value={"topic": "ey"}) mocker.patch.object(demisto, "investigation", return_value={"id": "681"}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(Slack, "get_conversation_by_name", return_value={"id": "C012AB3CD"}) mocker.patch.object(slack.WebClient, "api_call") mocker.patch.object(demisto, "results") # Arrange Slack.set_channel_topic() send_args = slack.WebClient.api_call.call_args success_results = demisto.results.call_args[0] new_context = demisto.setIntegrationContext.call_args[0][0] new_mirrors = js.loads(new_context["mirrors"]) our_mirror_filter = list(filter(lambda m: m["investigation_id"] == "681", new_mirrors)) our_mirror = our_mirror_filter[0] # Assert assert Slack.get_conversation_by_name.call_count == 0 assert slack.WebClient.api_call.call_count == 1 assert success_results[0] == "Topic successfully set." assert send_args[0][0] == "conversations.setTopic" assert send_args[1]["json"]["channel"] == "GKQ86DVPH" assert send_args[1]["json"]["topic"] == "ey" assert new_mirror == our_mirror def test_set_topic_no_args_no_investigation(mocker): import Slack # Set mocker.patch.object(demisto, "args", return_value={"topic": "ey"}) mocker.patch.object(demisto, "investigation", return_value={"id": "9999"}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(Slack, "get_conversation_by_name", return_value={"id": "C012AB3CD"}) mocker.patch.object(slack.WebClient, "api_call") mocker.patch.object(demisto, "results") return_error_mock = mocker.patch(RETURN_ERROR_TARGET, side_effect=InterruptedError()) # Arrange with pytest.raises(InterruptedError): Slack.set_channel_topic() err_msg = return_error_mock.call_args[0][0] # Assert assert Slack.get_conversation_by_name.call_count == 0 assert err_msg == "Channel not found - the Demisto app needs to be a member of the channel in order to look it up." def test_invite_users(mocker): import Slack # Set mocker.patch.object(demisto, "args", return_value={"channel": "general", "users": "spengler, glinda"}) mocker.patch.object(demisto, "investigation", return_value={"id": "681"}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(Slack, "get_conversation_by_name", return_value={"id": "C012AB3CD"}) mocker.patch.object(Slack, "invite_users_to_conversation") mocker.patch.object(demisto, "results") # Arrange Slack.invite_to_channel() send_args = Slack.invite_users_to_conversation.call_args[0] success_results = demisto.results.call_args[0] # Assert assert Slack.get_conversation_by_name.call_count == 1 assert Slack.invite_users_to_conversation.call_count == 1 assert success_results[0] == "Successfully invited users to the channel." assert send_args[0] == "C012AB3CD" assert send_args[1] == ["U012A3CDE", "U07QCRPA4"] def test_invite_users_no_channel(mocker): import Slack # Set mocker.patch.object(demisto, "args", return_value={"users": "spengler, glinda"}) mocker.patch.object(demisto, "investigation", return_value={"id": "681"}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(Slack, "get_conversation_by_name", return_value={"id": "GKQ86DVPH"}) mocker.patch.object(Slack, "invite_users_to_conversation") mocker.patch.object(demisto, "results") # Arrange Slack.invite_to_channel() send_args = Slack.invite_users_to_conversation.call_args[0] success_results = demisto.results.call_args[0] # Assert assert Slack.get_conversation_by_name.call_count == 0 assert Slack.invite_users_to_conversation.call_count == 1 assert success_results[0] == "Successfully invited users to the channel." assert send_args[0] == "GKQ86DVPH" assert send_args[1] == ["U012A3CDE", "U07QCRPA4"] def test_invite_users_no_channel_doesnt_exist(mocker): import Slack # Set mocker.patch.object(demisto, "args", return_value={"users": "spengler, glinda"}) mocker.patch.object(demisto, "investigation", return_value={"id": "777"}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(Slack, "get_conversation_by_name", return_value={"id": "GKQ86DVPH"}) mocker.patch.object(Slack, "invite_users_to_conversation") mocker.patch.object(demisto, "results") return_error_mock = mocker.patch(RETURN_ERROR_TARGET, side_effect=InterruptedError()) # Arrange with pytest.raises(InterruptedError): Slack.invite_to_channel() err_msg = return_error_mock.call_args[0][0] # Assert assert Slack.get_conversation_by_name.call_count == 0 assert Slack.invite_users_to_conversation.call_count == 0 assert err_msg == "Channel not found - the Demisto app needs to be a member of the channel in order to look it up." def test_kick_users(mocker): import Slack # Set mocker.patch.object(demisto, "args", return_value={"channel": "general", "users": "spengler, glinda"}) mocker.patch.object(demisto, "investigation", return_value={"id": "681"}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(Slack, "get_conversation_by_name", return_value={"id": "C012AB3CD"}) mocker.patch.object(Slack, "kick_users_from_conversation") mocker.patch.object(demisto, "results") # Arrange Slack.kick_from_channel() send_args = Slack.kick_users_from_conversation.call_args[0] success_results = demisto.results.call_args[0] # Assert assert Slack.get_conversation_by_name.call_count == 1 assert Slack.kick_users_from_conversation.call_count == 1 assert success_results[0] == "Successfully kicked users from the channel." assert send_args[0] == "C012AB3CD" assert send_args[1] == ["U012A3CDE", "U07QCRPA4"] def test_kick_users_no_channel(mocker): import Slack # Set mocker.patch.object(demisto, "args", return_value={"users": "spengler, glinda"}) mocker.patch.object(demisto, "investigation", return_value={"id": "681"}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(Slack, "get_conversation_by_name", return_value={"id": "GKQ86DVPH"}) mocker.patch.object(Slack, "kick_users_from_conversation") mocker.patch.object(demisto, "results") # Arrange Slack.kick_from_channel() send_args = Slack.kick_users_from_conversation.call_args[0] success_results = demisto.results.call_args[0] # Assert assert Slack.get_conversation_by_name.call_count == 0 assert Slack.kick_users_from_conversation.call_count == 1 assert success_results[0] == "Successfully kicked users from the channel." assert send_args[0] == "GKQ86DVPH" assert send_args[1] == ["U012A3CDE", "U07QCRPA4"] def test_kick_users_no_channel_doesnt_exist(mocker): import Slack # Set mocker.patch.object(demisto, "args", return_value={"users": "spengler, glinda"}) mocker.patch.object(demisto, "investigation", return_value={"id": "777"}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(Slack, "get_conversation_by_name", return_value={"id": "GKQ86DVPH"}) mocker.patch.object(Slack, "invite_users_to_conversation") mocker.patch.object(demisto, "results") return_error_mock = mocker.patch(RETURN_ERROR_TARGET, side_effect=InterruptedError()) # Arrange with pytest.raises(InterruptedError): Slack.kick_from_channel() err_msg = return_error_mock.call_args[0][0] # Assert assert Slack.get_conversation_by_name.call_count == 0 assert Slack.invite_users_to_conversation.call_count == 0 assert err_msg == "Channel not found - the Demisto app needs to be a member of the channel in order to look it up." def test_rename_channel(mocker): import Slack # Set mocker.patch.object(demisto, "args", return_value={"channel": "general", "name": "ey"}) mocker.patch.object(demisto, "investigation", return_value={"id": "681"}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(Slack, "get_conversation_by_name", return_value={"id": "C012AB3CD"}) mocker.patch.object(slack.WebClient, "api_call") mocker.patch.object(demisto, "results") # Arrange Slack.rename_channel() send_args = slack.WebClient.api_call.call_args success_results = demisto.results.call_args[0] # Assert assert Slack.get_conversation_by_name.call_count == 1 assert slack.WebClient.api_call.call_count == 1 assert success_results[0] == "Channel renamed successfully." assert send_args[0][0] == "conversations.rename" assert send_args[1]["json"]["channel"] == "C012AB3CD" assert send_args[1]["json"]["name"] == "ey" def test_rename_no_args_investigation(mocker): import Slack # Set new_mirror = { "channel_id": "GKQ86DVPH", "channel_name": "ey", "channel_topic": "incident-681", "investigation_id": "681", "mirror_type": "all", "mirror_direction": "both", "mirror_to": "group", "auto_close": True, "mirrored": True, } mocker.patch.object(demisto, "args", return_value={"name": "ey"}) mocker.patch.object(demisto, "investigation", return_value={"id": "681"}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(Slack, "get_conversation_by_name", return_value={"id": "C012AB3CD"}) mocker.patch.object(slack.WebClient, "api_call") mocker.patch.object(demisto, "results") # Arrange Slack.rename_channel() send_args = slack.WebClient.api_call.call_args success_results = demisto.results.call_args[0] new_context = demisto.setIntegrationContext.call_args[0][0] new_mirrors = js.loads(new_context["mirrors"]) our_mirror_filter = list(filter(lambda m: m["investigation_id"] == "681", new_mirrors)) our_mirror = our_mirror_filter[0] # Assert assert Slack.get_conversation_by_name.call_count == 0 assert slack.WebClient.api_call.call_count == 1 assert success_results[0] == "Channel renamed successfully." assert send_args[0][0] == "conversations.rename" assert send_args[1]["json"]["channel"] == "GKQ86DVPH" assert send_args[1]["json"]["name"] == "ey" assert new_mirror == our_mirror def test_rename_no_args_no_investigation(mocker): import Slack # Set mocker.patch.object(demisto, "args", return_value={"name": "ey"}) mocker.patch.object(demisto, "investigation", return_value={"id": "9999"}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(Slack, "get_conversation_by_name", return_value={"id": "C012AB3CD"}) mocker.patch.object(slack.WebClient, "api_call") mocker.patch.object(demisto, "results") return_error_mock = mocker.patch(RETURN_ERROR_TARGET, side_effect=InterruptedError()) # Arrange with pytest.raises(InterruptedError): Slack.rename_channel() err_msg = return_error_mock.call_args[0][0] # Assert assert Slack.get_conversation_by_name.call_count == 0 assert err_msg == "Channel not found - the Demisto app needs to be a member of the channel in order to look it up." def test_get_user(mocker): from Slack import get_user # Set mocker.patch.object(demisto, "args", return_value={"user": "spengler"}) mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(demisto, "results") # Arrange get_user() user_results = demisto.results.call_args[0] assert user_results[0]["EntryContext"] == { "Slack.User(val.ID === obj.ID)": { "ID": "U012A3CDE", "Username": "spengler", "Name": "Egon Spengler", "DisplayName": "spengler", "Email": "spengler@ghostbusters.example.com", } } def test_get_user_by_name_paging_rate_limit(mocker): from Slack import get_user_by_name, init_globals from slack.errors import SlackApiError from slack.web.slack_response import SlackResponse import time # Set init_globals() err_response: SlackResponse = SlackResponse( api_url="", client=None, http_verb="GET", req_args={}, data={"ok": False}, status_code=429, headers={"Retry-After": 30} ) first_call = {"members": js.loads(USERS), "response_metadata": {"next_cursor": "dGVhbTpDQ0M3UENUTks="}} second_call = SlackApiError("Rate limit reached!", err_response) third_call = {"members": [{"id": "U248918AB", "name": "alexios"}], "response_metadata": {"next_cursor": ""}} mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(slack.WebClient, "api_call", side_effect=[first_call, second_call, third_call]) mocker.patch.object(time, "sleep") # Arrange user = get_user_by_name("alexios") args = slack.WebClient.api_call.call_args_list first_args = args[0][1] second_args = args[2][1] # Assert assert len(first_args["params"]) == 1 assert first_args["params"]["limit"] == 200 assert len(second_args["params"]) == 2 assert second_args["params"]["cursor"] == "dGVhbTpDQ0M3UENUTks=" assert user["id"] == "U248918AB" assert slack.WebClient.api_call.call_count == 3 def test_get_user_by_name_paging_rate_limit_error(mocker): from Slack import get_user_by_name, init_globals from slack.errors import SlackApiError from slack.web.slack_response import SlackResponse import time # Set init_globals() err_response: SlackResponse = SlackResponse( api_url="", client=None, http_verb="GET", req_args={}, data={"ok": False}, status_code=429, headers={"Retry-After": 40} ) first_call = {"members": js.loads(USERS), "response_metadata": {"next_cursor": "dGVhbTpDQ0M3UENUTks="}} second_call = SlackApiError("Rate limit reached!", err_response) third_call = {"members": [{"id": "U248918AB", "name": "alexios"}], "response_metadata": {"next_cursor": ""}} mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(slack.WebClient, "api_call", side_effect=[first_call, second_call, second_call, third_call]) mocker.patch.object(time, "sleep") # Arrange with pytest.raises(SlackApiError): get_user_by_name("alexios") args = slack.WebClient.api_call.call_args_list first_args = args[0][1] # Assert assert len(first_args["params"]) == 1 assert first_args["params"]["limit"] == 200 assert slack.WebClient.api_call.call_count == 3 def test_get_user_by_name_paging_normal_error(mocker): from Slack import get_user_by_name, init_globals from slack.errors import SlackApiError from slack.web.slack_response import SlackResponse # Set init_globals() err_response: SlackResponse = SlackResponse( api_url="", client=None, http_verb="GET", req_args={}, data={"ok": False}, status_code=500, headers={} ) first_call = {"members": js.loads(USERS), "response_metadata": {"next_cursor": "dGVhbTpDQ0M3UENUTks="}} second_call = SlackApiError("Whoops!", err_response) third_call = {"members": [{"id": "U248918AB", "name": "alexios"}], "response_metadata": {"next_cursor": ""}} mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(slack.WebClient, "api_call", side_effect=[first_call, second_call, third_call]) # Arrange with pytest.raises(SlackApiError): get_user_by_name("alexios") args = slack.WebClient.api_call.call_args_list first_args = args[0][1] # Assert assert len(first_args["params"]) == 1 assert first_args["params"]["limit"] == 200 assert slack.WebClient.api_call.call_count == 2 def test_message_setting_name_and_icon(mocker): from Slack import send_slack_request_sync, init_globals mocker.patch.object(demisto, "params", return_value={"bot_name": "kassandra", "bot_icon": "coolimage"}) init_globals() # Set mocker.patch.object(slack.WebClient, "api_call") # Arrange send_slack_request_sync(slack.WebClient, "chat.postMessage", body={"channel": "c", "text": "t"}) send_args = slack.WebClient.api_call.call_args[1] # Assert assert "username" in send_args["json"] assert "icon_url" in send_args["json"] def test_message_not_setting_name_and_icon(mocker): from Slack import send_slack_request_sync, init_globals mocker.patch.object(demisto, "params", return_value={"bot_name": "kassandra", "bot_icon": "coolimage"}) init_globals() # Set mocker.patch.object(slack.WebClient, "api_call") # Arrange send_slack_request_sync(slack.WebClient, "conversations.setTopic", body={"channel": "c", "topic": "t"}) send_args = slack.WebClient.api_call.call_args[1] # Assert assert "username" not in send_args["json"] assert "icon_url" not in send_args["json"] @pytest.mark.asyncio async def test_message_setting_name_and_icon_async(mocker): from Slack import send_slack_request_async, init_globals # Set async def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): return mocker.patch.object(demisto, "params", return_value={"bot_name": "kassandra", "bot_icon": "coolimage"}) init_globals() mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) # Arrange await send_slack_request_async(slack.WebClient, "chat.postMessage", body={"channel": "c", "text": "t"}) send_args = slack.WebClient.api_call.call_args[1] # Assert assert "username" in send_args["json"] assert "icon_url" in send_args["json"] @pytest.mark.asyncio async def test_message_not_setting_name_and_icon_async(mocker): from Slack import send_slack_request_async, init_globals # Set async def api_call(method: str, http_verb: str = "POST", file: str = None, params=None, json=None, data=None): return mocker.patch.object(demisto, "params", return_value={"bot_name": "kassandra", "bot_icon": "coolimage"}) init_globals() mocker.patch.object(slack.WebClient, "api_call", side_effect=api_call) # Arrange await send_slack_request_async(slack.WebClient, "conversations.setTopic", body={"channel": "c", "topic": "t"}) send_args = slack.WebClient.api_call.call_args[1] # Assert assert "username" not in send_args["json"] assert "icon_url" not in send_args["json"] def test_set_proxy_and_ssl(mocker): import Slack import ssl # Set mocker.patch.object(demisto, "params", return_value={"unsecure": "true", "proxy": "true"}) mocker.patch.object(slack, "WebClient") mocker.patch.object(Slack, "handle_proxy", return_value={"https": "https_proxy", "http": "http_proxy"}) ssl_context = ssl.create_default_context() ssl_context.check_hostname = False ssl_context.verify_mode = ssl.CERT_NONE # Arrange Slack.init_globals() init_args = slack.WebClient.call_args[1] assert init_args["ssl"].check_hostname is False assert init_args["ssl"].verify_mode == ssl.CERT_NONE assert init_args["proxy"] == "http_proxy" def test_set_proxy_by_url(mocker): import Slack import ssl # Set mocker.patch.object(demisto, "params", return_value={"unsecure": "true", "proxy": "true", "proxy_url": "my_proxy"}) mocker.patch.object(slack, "WebClient") mocker.patch.object(Slack, "handle_proxy", return_value={"https": "https_proxy", "http": "http_proxy"}) ssl_context = ssl.create_default_context() ssl_context.check_hostname = False ssl_context.verify_mode = ssl.CERT_NONE # Arrange Slack.init_globals() init_args = slack.WebClient.call_args[1] assert init_args["ssl"].check_hostname is False assert init_args["ssl"].verify_mode == ssl.CERT_NONE assert init_args["proxy"] == "my_proxy" def test_unset_proxy_and_ssl(mocker): from Slack import init_globals # Set mocker.patch.object(slack, "WebClient") # Arrange init_globals() init_args = slack.WebClient.call_args[1] assert init_args["ssl"] is None assert init_args["proxy"] is None def test_fail_connect_threads(mocker): import Slack mocker.patch.object(demisto, "params", return_value={"unsecure": "true", "bot_token": "123"}) mocker.patch.object(demisto, "args", return_value={"to": "test", "message": "test message"}) mocker.patch.object(demisto, "command", return_value="send-notification") return_error_mock = mocker.patch(RETURN_ERROR_TARGET) for _i in range(8): Slack.main() time.sleep(0.5) assert return_error_mock.call_count == 8 assert threading.active_count() < 6 # we shouldn't have more than 5 threads (1 + 4 max size of executor) def test_slack_send_filter_one_mirror_tag(mocker): # When filtered_tags parameter contains the same tag as the entry tag - slack_send method should send the message import Slack mocker.patch.object(demisto, "results") mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(Slack, "slack_send_request", return_value=SLACK_RESPONSE_2) mocker.patch.object( demisto, "args", return_value={"to": "demisto", "messageType": "mirrorEntry", "entryObject": {"tags": ["tag1"]}} ) mocker.patch.object(demisto, "params", return_value={"filtered_tags": "tag1"}) Slack.slack_send() assert demisto.results.mock_calls[0][1][0]["HumanReadable"] == "Message sent to Slack successfully.\nThread ID is: None" def test_slack_send_filter_no_mirror_tags(mocker): # When filtered_tags parameter is empty slack_send method should send the message import Slack mocker.patch.object(demisto, "results") mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(Slack, "slack_send_request", return_value=SLACK_RESPONSE_2) mocker.patch.object( demisto, "args", return_value={"to": "demisto", "messageType": "mirrorEntry", "entryObject": {"tags": ["tag1"]}} ) mocker.patch.object(demisto, "params", return_value={"filtered_tags": ""}) Slack.slack_send() assert demisto.results.mock_calls[0][1][0]["HumanReadable"] == "Message sent to Slack successfully.\nThread ID is: None" def test_slack_send_filter_no_entry_tags(mocker): # When filtered_tags parameter contains one tag to filter messages and the entry have no tags - # slack_send method should exit and demisto.results.mock_calls should be empty import Slack mocker.patch.object(demisto, "results") mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_integration_context) mocker.patch.object(Slack, "slack_send_request", return_value={"cool": "cool"}) mocker.patch.object( demisto, "args", return_value={"to": "demisto", "messageType": "mirrorEntry", "entryObject": {"tags": []}} ) mocker.patch.object(demisto, "params", return_value={"filtered_tags": "tag1"}) Slack.slack_send() assert demisto.results.mock_calls == [] def test_send_message_to_destinations_non_strict(): """ Given: Blocks with non-strict json When: Sending message Then: No error is raised """ from Slack import send_message_to_destinations blocks = """[ { "type": "section", "text": { "type": "mrkdwn", "text": "*<${incident.siemlink}|${incident.name}>*\n${incident.details}" } }, { "type": "section", "fields": [ { "type": "mrkdwn", "text": "*Account ID:*\n${incident.accountid} " } ] }, { "type": "actions", "elements": [ { "type": "button", "text": { "type": "plain_text", "text": "Acknowledge" }, "value": "ack" } ] } ]""" send_message_to_destinations([], "", "", blocks=blocks) # No destinations, no response class TestFilterConversations: @staticmethod def set_conversation_mock(mocker, get_context=get_integration_context): mocker.patch.object(demisto, "getIntegrationContext", side_effect=get_context) mocker.patch.object(demisto, "setIntegrationContext", side_effect=set_integration_context) mocker.patch.object(slack.WebClient, "api_call", return_value={"channels": js.loads(CONVERSATIONS)}) def test_filter_conversations_with_name_exists_in_context(self, mocker): """ Given: - filter name set to 'general' When: - 'general' conversation exists in context Then: - Check if the 'general' conversation was returned - Check that no API command was called. """ from Slack import filter_conversations self.set_conversation_mock(mocker) conversation_name = "general" conversations = filter_conversations(name=conversation_name) # Assertions assert len(conversations) == 1 assert conversation_name == conversations[0]["name"] assert slack.WebClient.api_call.call_count == 0 def test_get_conversation_by_name_exists_in_api_call(self, mocker): """ Given: - filter name set to 'general' When: - 'general' conversation not exists in context, but does in the API Then: - Check if the 'general' conversation was returned - Check that a API command was called. """ def get_context(): return {} from Slack import filter_conversations self.set_conversation_mock(mocker, get_context=get_context) conversation_name = "general" conversations = filter_conversations(name=conversation_name) # Assertions assert conversation_name == conversations[0]["name"] assert slack.WebClient.api_call.call_count == 1 # Find that 'general' conversation has been added to context conversations = json.loads(demisto.setIntegrationContext.call_args[0][0]["conversations"]) filtered = list(filter(lambda c: c["name"] == conversation_name, conversations)) assert filtered, "Could not find the 'general' conversation in the context" def test_get_conversation_by_name_not_exists(self, mocker): """ Given: - Conversation name known not to exist When: - Conversation does not exist. Then: - Check no conversation was returned. - Check that a API command was called. """ from Slack import filter_conversations self.set_conversation_mock(mocker) conversation_name = "no exists" conversations = filter_conversations(name=conversation_name) assert not conversations assert slack.WebClient.api_call.call_count == 1 def test_filter_conversations_with_regex_name_exists_in_context(self, mocker): """ Given: - filter name set to '^general$' When: - 'general' conversation exists in context Then: - Check if the 'general' conversation was returned - Check that no API command was called. """ from Slack import filter_conversations self.set_conversation_mock(mocker) conversation_name = "^general$" conversations = filter_conversations(name=conversation_name) # Assertions assert len(conversations) == 1 assert conversations[0]["name"] == "general" assert slack.WebClient.api_call.call_count == 0 def test_filter_conversations_with_no_args(self, mocker): """ Given: - No arguments passed to function Then: - Check if all conversation were returned """ from Slack import filter_conversations self.set_conversation_mock(mocker) conversations = filter_conversations() assert len(conversations) == len(js.loads(CONVERSATIONS)) def test_filter_conversations_with_is_archived_false_arg(self, mocker): """ Given: - filter is_archived set to False Then: - The first conversation's name is 'general' """ from Slack import filter_conversations self.set_conversation_mock(mocker) conversations = filter_conversations(is_archived=False) assert conversations[0]["name"] == "general" def test_filter_conversations_with_is_general_arg(self, mocker): """ Given: - filter is_general set to True Then: - Only one conversation is returned - The only conversation's name is 'general' """ from Slack import filter_conversations self.set_conversation_mock(mocker) conversations = filter_conversations(is_general=True) assert len(conversations) == 1 assert conversations[0]["name"] == "general" def test_filter_conversations_with_is_general_false_arg(self, mocker): """ Given: - filter is_general set to False Then: - All conversations except one (named 'general') was returned """ from Slack import filter_conversations self.set_conversation_mock(mocker) conversations = filter_conversations(is_general=False) assert len(conversations) == (len(js.loads(CONVERSATIONS)) - 1) def test_filter_conversations_with_is_private_false_arg(self, mocker): """ Given: - filter is_private set to False Then: - The first conversation's name is 'general' """ from Slack import filter_conversations self.set_conversation_mock(mocker) conversations = filter_conversations(is_private=False) assert conversations[0]["name"] == "general"