Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions libraries/functional-tests/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
requests==2.23.0
aiounittest==1.3.0
131 changes: 131 additions & 0 deletions libraries/functional-tests/slacktestbot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Slack functional test pipeline setup

This is a step by step guide to setup the Slack functional test pipeline.

## Slack Application setup

We'll need to create a Slack application to connect with the bot.

1. Create App

Create a Slack App from [here](https://api.slack.com/apps), associate it to a workspace.

![Create Slack App](./media/SlackCreateSlackApp.png)

2. Get the Signing Secret and the Verification Token

Keep the Signing Secret and the Verification Token from the Basic Information tab.

These tokens will be needed to configure the pipeline.

- Signing Secret will become *SlackTestBotSlackClientSigningSecret*.
- Verification Token will become *SlackTestBotSlackVerificationToken*.

![App Credentials](./media/SlackAppCredentials.png)

3. Grant Scopes

Go to the OAuth & Permissions tab and scroll to the Scopes section.

In the Bot Token Scopes, add chat:write, im:history, and im:read using the Add an Oauth Scope button.

![Grant Scopes](./media/SlackGrantScopes.png)

4. Install App

On the same OAuth & Permissions tab, scroll up to the OAuth Tokens & Redirect URLs section and click on Install to Workspace.

A new window will be prompted, click on Allow.

![Install App](./media/SlackInstallApp.png)

5. Get the Bot User OAuth Access Token

You will be redirected back to OAuth & Permissions tab, keep the Bot User OAuth Access Token.

- Bot User OAuth Access Token will become *SlackTestBotSlackBotToken* later in the pipeline variables.

![OAuthToken](./media/SlackOAuthToken.png)

6. Get the Channel ID

Go to the Slack workspace you associated the app to. The new App should have appeared; if not, add it using the plus sign that shows up while hovering the mouse over the Apps tab.

Right click on it and then on Copy link.

![ChannelID](./media/SlackChannelID.png)

The link will look something like https://workspace.slack.com/archives/N074R34L1D.

The last segment of the URL represents the channel ID, in this case, **N074R34L1D**.

- Keep this ID as it will later become the *SlackTestBotSlackChannel* pipeline variable.

## Azure setup

We will need to create an Azure App Registration and setup a pipeline.

### App Registration

1. Create an App Registration

Go [here](https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade) and click on New Registration.

Set a name and change the supported account type to Multitenant, then Register.

![Azure App Registration 1](./media/AzureAppRegistration1.png)

1. Get the Application ID and client secret values

You will be redirected to the Overview tab.

Copy the Application ID then go to the Certificates and secrets tab.

Create a secret and copy its value.

- The Azure App Registration ID will be the *SlackTestBotAppId* for the pipeline.
- The Azure App Registration Secret value will be the *SlackTestBotAppSecret* for the pipeline.

![Azure App Registration 2](./media/AzureAppRegistration2.png)

### Pipeline Setup

1. Create the pipeline

From an Azure DevOps project, go to the Pipelines view and create a new one.

Using the classic editor, select GitHub, then set the repository and branch.

![Azure Pipeline Setup 1](./media/AzurePipelineSetup1.png)

2. Set the YAML

On the following view, click on the Apply button of the YAML configuration.

Set the pipeline name and point to the YAML file clicking on the three highlighted dots.

![Azure Pipeline Setup 2](./media/AzurePipelineSetup2.png)

3. Set the pipeline variables

Finally, click on the variables tab.

You will need to set up the variables using the values you got throughout this guide:

|Variable|Value|
|---|---|
| AzureSubscription | Azure Resource Manager name, click [here](https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/overview) for more information. |
| SlackTestBotAppId | Azure App Registration ID. |
| SlackTestBotAppSecret | Azure App Registration Secret value. |
| SlackTestBotBotGroup | Name of the Azure resource group to be created. |
| SlackTestBotBotName | Name of the Bot to be created. |
| SlackTestBotSlackBotToken | Slack Bot User OAuth Access Token. |
| SlackTestBotSlackChannel | Slack Channel ID. |
| SlackTestBotSlackClientSigningSecret | Slack Signing Secret. |
| SlackTestBotSlackVerificationToken | Slack Verification Token. |

Once the variables are set up your panel should look something like this:

![Azure Pipeline Variables](./media/AzurePipelineVariables.png)

Click Save and the pipeline is ready to run.
78 changes: 78 additions & 0 deletions libraries/functional-tests/slacktestbot/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

import sys
import traceback
from datetime import datetime

from aiohttp import web
from aiohttp.web import Request, Response
from botbuilder.adapters.slack import SlackAdapterOptions
from botbuilder.adapters.slack import SlackAdapter
from botbuilder.adapters.slack import SlackClient
from botbuilder.core import TurnContext
from botbuilder.core.integration import aiohttp_error_middleware
from botbuilder.schema import Activity, ActivityTypes

from bots import EchoBot
from config import DefaultConfig

CONFIG = DefaultConfig()

# Create adapter.
SLACK_OPTIONS = SlackAdapterOptions(
CONFIG.SLACK_VERIFICATION_TOKEN,
CONFIG.SLACK_BOT_TOKEN,
CONFIG.SLACK_CLIENT_SIGNING_SECRET,
)
SLACK_CLIENT = SlackClient(SLACK_OPTIONS)
ADAPTER = SlackAdapter(SLACK_CLIENT)


# Catch-all for errors.
async def on_error(context: TurnContext, error: Exception):
# This check writes out errors to console log .vs. app insights.
# NOTE: In production environment, you should consider logging this to Azure
# application insights.
print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr)
traceback.print_exc()

# Send a message to the user
await context.send_activity("The bot encountered an error or bug.")
await context.send_activity(
"To continue to run this bot, please fix the bot source code."
)
# Send a trace activity if we're talking to the Bot Framework Emulator
if context.activity.channel_id == "emulator":
# Create a trace activity that contains the error object
trace_activity = Activity(
label="TurnError",
name="on_turn_error Trace",
timestamp=datetime.utcnow(),
type=ActivityTypes.trace,
value=f"{error}",
value_type="https://www.botframework.com/schemas/error",
)
# Send a trace activity, which will be displayed in Bot Framework Emulator
await context.send_activity(trace_activity)


ADAPTER.on_turn_error = on_error

# Create the Bot
BOT = EchoBot()


# Listen for incoming requests on /api/messages
async def messages(req: Request) -> Response:
return await ADAPTER.process(req, BOT.on_turn)


APP = web.Application(middlewares=[aiohttp_error_middleware])
APP.router.add_post("/api/messages", messages)

if __name__ == "__main__":
try:
web.run_app(APP, host="localhost", port=CONFIG.PORT)
except Exception as error:
raise error
6 changes: 6 additions & 0 deletions libraries/functional-tests/slacktestbot/bots/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

from .echo_bot import EchoBot

__all__ = ["EchoBot"]
52 changes: 52 additions & 0 deletions libraries/functional-tests/slacktestbot/bots/echo_bot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

import json
import os

from botbuilder.adapters.slack import SlackRequestBody, SlackEvent
from botbuilder.core import ActivityHandler, MessageFactory, TurnContext
from botbuilder.schema import ChannelAccount, Attachment


class EchoBot(ActivityHandler):
async def on_members_added_activity(
self, members_added: [ChannelAccount], turn_context: TurnContext
):
for member in members_added:
if member.id != turn_context.activity.recipient.id:
await turn_context.send_activity("Hello and welcome!")

async def on_message_activity(self, turn_context: TurnContext):
return await turn_context.send_activity(
MessageFactory.text(f"Echo: {turn_context.activity.text}")
)

async def on_event_activity(self, turn_context: TurnContext):
body = turn_context.activity.channel_data
if not body:
return

if isinstance(body, SlackRequestBody) and body.command == "/test":
interactive_message = MessageFactory.attachment(
self.__create_interactive_message(
os.path.join(os.getcwd(), "./resources/InteractiveMessage.json")
)
)
await turn_context.send_activity(interactive_message)

if isinstance(body, SlackEvent):
if body.subtype == "file_share":
await turn_context.send_activity("Echo: I received and attachment")
elif body.message and body.message.attachments:
await turn_context.send_activity("Echo: I received a link share")

def __create_interactive_message(self, file_path: str) -> Attachment:
with open(file_path, "rb") as in_file:
adaptive_card_attachment = json.load(in_file)

return Attachment(
content=adaptive_card_attachment,
content_type="application/json",
name="blocks",
)
15 changes: 15 additions & 0 deletions libraries/functional-tests/slacktestbot/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env python3
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

import os


class DefaultConfig:
""" Bot Configuration """

PORT = 3978

SLACK_VERIFICATION_TOKEN = os.environ.get("SlackVerificationToken", "")
SLACK_BOT_TOKEN = os.environ.get("SlackBotToken", "")
SLACK_CLIENT_SIGNING_SECRET = os.environ.get("SlackClientSigningSecret", "")
Loading