From e10947324d021e634580e4669629bdaf079be6a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20Su=C3=A1rez?= Date: Fri, 13 Dec 2019 10:06:48 -0800 Subject: [PATCH] Revert "Functional Test (#439)" This reverts commit 68b7e21d946d39aef73979c163b749341f1f8269. --- .coveragerc | 3 +- azure-pipelines.yml | 67 +++-- .../functionaltestbot/Dockerfile | 48 ++++ .../functionaltestbot/Dockfile | 27 ++ .../functionaltestbot/README.md | 9 - .../functionaltestbot/application.py | 98 -------- .../functionaltestbot/bots/echo_bot.py | 19 -- .../functionaltestbot/client_driver/README.md | 5 + .../{bots => flask_bot_app}/__init__.py | 4 +- .../functionaltestbot/flask_bot_app/app.py | 21 ++ .../flask_bot_app/bot_app.py | 108 ++++++++ .../flask_bot_app/default_config.py | 12 + .../functionaltestbot/flask_bot_app/my_bot.py | 19 ++ .../functionaltestbot/README.md | 35 +++ .../functionaltestbot/about.py | 14 ++ .../functionaltestbot/app.py | 86 +++++++ .../functionaltestbot/bot.py | 19 ++ .../{ => functionaltestbot}/config.py | 2 +- .../functionaltestbot/requirements.txt | 3 + .../functionaltestbot/init.sh | 8 + .../functionaltestbot/requirements.txt | 12 +- .../functionaltestbot/runserver.py | 16 ++ .../scripts/deploy_webapp.sh | 186 -------------- .../functionaltestbot/setup.py | 40 +++ .../functionaltestbot/sshd_config | 21 ++ .../template/linux/template.json | 238 ++++++++++++++++++ .../functionaltestbot/test.sh | 1 + .../functionaltestbot/tests/test_py_bot.py | 48 ---- .../tests/direct_line_client.py | 0 .../functional-tests/tests/test_py_bot.py | 26 ++ setup.cfg | 2 - 31 files changed, 804 insertions(+), 393 deletions(-) create mode 100644 libraries/functional-tests/functionaltestbot/Dockerfile create mode 100644 libraries/functional-tests/functionaltestbot/Dockfile delete mode 100644 libraries/functional-tests/functionaltestbot/README.md delete mode 100644 libraries/functional-tests/functionaltestbot/application.py delete mode 100644 libraries/functional-tests/functionaltestbot/bots/echo_bot.py create mode 100644 libraries/functional-tests/functionaltestbot/client_driver/README.md rename libraries/functional-tests/functionaltestbot/{bots => flask_bot_app}/__init__.py (64%) create mode 100644 libraries/functional-tests/functionaltestbot/flask_bot_app/app.py create mode 100644 libraries/functional-tests/functionaltestbot/flask_bot_app/bot_app.py create mode 100644 libraries/functional-tests/functionaltestbot/flask_bot_app/default_config.py create mode 100644 libraries/functional-tests/functionaltestbot/flask_bot_app/my_bot.py create mode 100644 libraries/functional-tests/functionaltestbot/functionaltestbot/README.md create mode 100644 libraries/functional-tests/functionaltestbot/functionaltestbot/about.py create mode 100644 libraries/functional-tests/functionaltestbot/functionaltestbot/app.py create mode 100644 libraries/functional-tests/functionaltestbot/functionaltestbot/bot.py rename libraries/functional-tests/functionaltestbot/{ => functionaltestbot}/config.py (94%) create mode 100644 libraries/functional-tests/functionaltestbot/functionaltestbot/requirements.txt create mode 100644 libraries/functional-tests/functionaltestbot/init.sh create mode 100644 libraries/functional-tests/functionaltestbot/runserver.py delete mode 100644 libraries/functional-tests/functionaltestbot/scripts/deploy_webapp.sh create mode 100644 libraries/functional-tests/functionaltestbot/setup.py create mode 100644 libraries/functional-tests/functionaltestbot/sshd_config create mode 100644 libraries/functional-tests/functionaltestbot/template/linux/template.json create mode 100644 libraries/functional-tests/functionaltestbot/test.sh delete mode 100644 libraries/functional-tests/functionaltestbot/tests/test_py_bot.py rename libraries/functional-tests/{functionaltestbot => }/tests/direct_line_client.py (100%) create mode 100644 libraries/functional-tests/tests/test_py_bot.py delete mode 100644 setup.cfg diff --git a/.coveragerc b/.coveragerc index 304e0a883..4dd59303b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -3,5 +3,4 @@ source = ./libraries/ omit = */tests/* setup.py - */botbuilder-schema/* - */functional-tests/* + */botbuilder-schema/* \ No newline at end of file diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f2eb246e9..c424c7f01 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,32 +1,61 @@ -schedules: -- cron: "0 0 * * *" - displayName: Daily midnight build +trigger: branches: include: + - daveta-python-functional + exclude: - master variables: - resourceGroupName: 'pyfuntest' + # Container registry service connection established during pipeline creation + dockerRegistryServiceConnection: 'NightlyE2E-Acr' + azureRmServiceConnection: 'NightlyE2E-RM' + dockerFilePath: 'libraries/functional-tests/functionaltestbot/Dockerfile' + buildIdTag: $(Build.BuildNumber) + webAppName: 'e2epython' + containerRegistry: 'nightlye2etest.azurecr.io' + imageRepository: 'functionaltestpy' + + + jobs: -- job: Doit +# Build and publish container +- job: Build pool: vmImage: 'Ubuntu-16.04' - + displayName: Build and push bot image + continueOnError: false steps: - - task: UsePythonVersion@0 - displayName: Use Python 3.6 + - task: Docker@2 + displayName: Build and push bot image inputs: - versionSpec: '3.6' + command: buildAndPush + repository: $(imageRepository) + dockerfile: $(dockerFilePath) + containerRegistry: $(dockerRegistryServiceConnection) + tags: $(buildIdTag) + + - - task: AzureCLI@2 - displayName: Provision, Deploy and run tests +- job: Deploy + displayName: Provision bot container + pool: + vmImage: 'Ubuntu-16.04' + dependsOn: + - Build + steps: + - task: AzureRMWebAppDeployment@4 + displayName: Python Functional E2E test. inputs: - azureSubscription: 'FUSE Temporary (174c5021-8109-4087-a3e2-a1de20420569)' - scriptType: 'bash' - scriptLocation: 'inlineScript' - inlineScript: | - cd $(Build.SourcesDirectory)/libraries/functional-tests/functionaltestbot - chmod +x ./scripts/deploy_webapp.sh - ./scripts/deploy_webapp.sh --appid $(botAppId) --password $(botAppPassword) -g $(resourceGroupName) - continueOnError: false + ConnectionType: AzureRM + ConnectedServiceName: $(azureRmServiceConnection) + appType: webAppContainer + WebAppName: $(webAppName) + DockerNamespace: $(containerRegistry) + DockerRepository: $(imageRepository) + DockerImageTag: $(buildIdTag) + AppSettings: '-MicrosoftAppId $(botAppId) -MicrosoftAppPassword $(botAppPassword) -FLASK_APP /functionaltestbot/app.py -FLASK_DEBUG 1' + + #StartupCommand: 'flask run --host=0.0.0.0 --port=3978' + + diff --git a/libraries/functional-tests/functionaltestbot/Dockerfile b/libraries/functional-tests/functionaltestbot/Dockerfile new file mode 100644 index 000000000..3364fc380 --- /dev/null +++ b/libraries/functional-tests/functionaltestbot/Dockerfile @@ -0,0 +1,48 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +FROM tiangolo/uwsgi-nginx-flask:python3.6 + + +RUN mkdir /functionaltestbot + +EXPOSE 443 +# EXPOSE 2222 + +COPY ./functionaltestbot /functionaltestbot +COPY setup.py / +COPY test.sh / +# RUN ls -ltr +# RUN cat prestart.sh +# RUN cat main.py + +ENV FLASK_APP=/functionaltestbot/app.py +ENV LANG=C.UTF-8 +ENV LC_ALL=C.UTF-8 +ENV PATH ${PATH}:/home/site/wwwroot + +WORKDIR / + +# Initialize the bot +RUN pip3 install -e . + +# ssh +ENV SSH_PASSWD "root:Docker!" +RUN apt-get update \ + && apt-get install -y --no-install-recommends dialog \ + && apt-get update \ + && apt-get install -y --no-install-recommends openssh-server \ + && echo "$SSH_PASSWD" | chpasswd \ + && apt install -y --no-install-recommends vim +COPY sshd_config /etc/ssh/ +COPY init.sh /usr/local/bin/ +RUN chmod u+x /usr/local/bin/init.sh + +# For Debugging, uncomment the following: +# ENTRYPOINT ["python3.6", "-c", "import time ; time.sleep(500000)"] +ENTRYPOINT ["init.sh"] + +# For Devops, they don't like entry points. This is now in the devops +# pipeline. +# ENTRYPOINT [ "flask" ] +# CMD [ "run", "--port", "3978", "--host", "0.0.0.0" ] diff --git a/libraries/functional-tests/functionaltestbot/Dockfile b/libraries/functional-tests/functionaltestbot/Dockfile new file mode 100644 index 000000000..8383f9a2b --- /dev/null +++ b/libraries/functional-tests/functionaltestbot/Dockfile @@ -0,0 +1,27 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +FROM python:3.7-slim as pkg_holder + +ARG EXTRA_INDEX_URL +RUN pip config set global.extra-index-url "${EXTRA_INDEX_URL}" + +COPY requirements.txt . +RUN pip download -r requirements.txt -d packages + +FROM python:3.7-slim + +ENV VIRTUAL_ENV=/opt/venv +RUN python3.7 -m venv $VIRTUAL_ENV +ENV PATH="$VIRTUAL_ENV/bin:$PATH" + +COPY . /app +WORKDIR /app + +COPY --from=pkg_holder packages packages + +RUN pip install -r requirements.txt --no-index --find-links=packages && rm -rf packages + +ENTRYPOINT ["python"] +EXPOSE 3978 +CMD ["runserver.py"] diff --git a/libraries/functional-tests/functionaltestbot/README.md b/libraries/functional-tests/functionaltestbot/README.md deleted file mode 100644 index f6d8e670f..000000000 --- a/libraries/functional-tests/functionaltestbot/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Functional Test Bot -This bot is the "Echo" bot which perform E2E functional test. -- Cleans up -- Deploys the python echo bot to Azure -- Creates an Azure Bot and associates with the deployed python bot. -- Creates a DirectLine channel and associates with the newly created bot. -- Runs a client test, using the DirectLine channel and and verifies response. - -This is modeled in a Devops. \ No newline at end of file diff --git a/libraries/functional-tests/functionaltestbot/application.py b/libraries/functional-tests/functionaltestbot/application.py deleted file mode 100644 index cf8de8edc..000000000 --- a/libraries/functional-tests/functionaltestbot/application.py +++ /dev/null @@ -1,98 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -import asyncio -import sys -import os -from datetime import datetime - -from flask import Flask, request, Response -from botbuilder.core import ( - BotFrameworkAdapterSettings, - BotFrameworkAdapter, - TurnContext, -) -from botbuilder.schema import Activity, ActivityTypes - -from bots import EchoBot - -# Create the loop and Flask app -LOOP = asyncio.get_event_loop() -# pylint: disable=invalid-name -app = Flask(__name__, instance_relative_config=True) -app.config.from_object("config.DefaultConfig") - -# Create adapter. -# See https://aka.ms/about-bot-adapter to learn more about how bots work. -SETTINGS = BotFrameworkAdapterSettings( - os.environ.get("MicrosoftAppId", ""), os.environ.get("MicrosoftAppPassword", "") -) -ADAPTER = BotFrameworkAdapter(SETTINGS) - - -# 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) - - # 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 -@app.route("/api/messages", methods=["POST"]) -def messages(): - # Main bot message handler. - if "application/json" in request.headers["Content-Type"]: - body = request.json - else: - return Response(status=415) - - activity = Activity().deserialize(body) - auth_header = ( - request.headers["Authorization"] if "Authorization" in request.headers else "" - ) - - try: - task = LOOP.create_task( - ADAPTER.process_activity(activity, auth_header, BOT.on_turn) - ) - LOOP.run_until_complete(task) - return Response(status=201) - except Exception as exception: - raise exception - - -@app.route("/", methods=["GET"]) -def ping(): - return "Hello World!" - - -if __name__ == "__main__": - try: - app.run(debug=False, port=3978) # nosec debug - except Exception as exception: - raise exception diff --git a/libraries/functional-tests/functionaltestbot/bots/echo_bot.py b/libraries/functional-tests/functionaltestbot/bots/echo_bot.py deleted file mode 100644 index 90a094640..000000000 --- a/libraries/functional-tests/functionaltestbot/bots/echo_bot.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -from botbuilder.core import ActivityHandler, MessageFactory, TurnContext -from botbuilder.schema import ChannelAccount - - -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}") - ) diff --git a/libraries/functional-tests/functionaltestbot/client_driver/README.md b/libraries/functional-tests/functionaltestbot/client_driver/README.md new file mode 100644 index 000000000..317a457c9 --- /dev/null +++ b/libraries/functional-tests/functionaltestbot/client_driver/README.md @@ -0,0 +1,5 @@ +# Client Driver for Function E2E test + +This contains the client code that drives the bot functional test. + +It performs simple operations against the bot and validates results. \ No newline at end of file diff --git a/libraries/functional-tests/functionaltestbot/bots/__init__.py b/libraries/functional-tests/functionaltestbot/flask_bot_app/__init__.py similarity index 64% rename from libraries/functional-tests/functionaltestbot/bots/__init__.py rename to libraries/functional-tests/functionaltestbot/flask_bot_app/__init__.py index f95fbbbad..d5d099805 100644 --- a/libraries/functional-tests/functionaltestbot/bots/__init__.py +++ b/libraries/functional-tests/functionaltestbot/flask_bot_app/__init__.py @@ -1,6 +1,6 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -from .echo_bot import EchoBot +from .app import APP -__all__ = ["EchoBot"] +__all__ = ["APP"] diff --git a/libraries/functional-tests/functionaltestbot/flask_bot_app/app.py b/libraries/functional-tests/functionaltestbot/flask_bot_app/app.py new file mode 100644 index 000000000..10f99452e --- /dev/null +++ b/libraries/functional-tests/functionaltestbot/flask_bot_app/app.py @@ -0,0 +1,21 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +"""Bot app with Flask routing.""" + +from flask import Response + +from .bot_app import BotApp + + +APP = BotApp() + + +@APP.flask.route("/api/messages", methods=["POST"]) +def messages() -> Response: + return APP.messages() + + +@APP.flask.route("/api/test", methods=["GET"]) +def test() -> Response: + return APP.test() diff --git a/libraries/functional-tests/functionaltestbot/flask_bot_app/bot_app.py b/libraries/functional-tests/functionaltestbot/flask_bot_app/bot_app.py new file mode 100644 index 000000000..5fb109576 --- /dev/null +++ b/libraries/functional-tests/functionaltestbot/flask_bot_app/bot_app.py @@ -0,0 +1,108 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import asyncio +import sys +from types import MethodType +from flask import Flask, Response, request + +from botbuilder.core import ( + BotFrameworkAdapter, + BotFrameworkAdapterSettings, + MessageFactory, + TurnContext, +) +from botbuilder.schema import Activity, InputHints + +from .default_config import DefaultConfig +from .my_bot import MyBot + + +class BotApp: + """A Flask echo bot.""" + + def __init__(self): + # Create the loop and Flask app + self.loop = asyncio.get_event_loop() + self.flask = Flask(__name__, instance_relative_config=True) + self.flask.config.from_object(DefaultConfig) + + # Create adapter. + # See https://aka.ms/about-bot-adapter to learn more about how bots work. + self.settings = BotFrameworkAdapterSettings( + self.flask.config["APP_ID"], self.flask.config["APP_PASSWORD"] + ) + self.adapter = BotFrameworkAdapter(self.settings) + + # Catch-all for errors. + async def on_error(adapter, 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]: {error}", file=sys.stderr) + + # Send a message to the user + error_message_text = "Sorry, it looks like something went wrong." + error_message = MessageFactory.text( + error_message_text, error_message_text, InputHints.expecting_input + ) + await context.send_activity(error_message) + + # pylint: disable=protected-access + if adapter._conversation_state: + # If state was defined, clear it. + await adapter._conversation_state.delete(context) + + self.adapter.on_turn_error = MethodType(on_error, self.adapter) + + # Create the main dialog + self.bot = MyBot() + + def messages(self) -> Response: + """Main bot message handler that listens for incoming requests.""" + + if "application/json" in request.headers["Content-Type"]: + body = request.json + else: + return Response(status=415) + + activity = Activity().deserialize(body) + auth_header = ( + request.headers["Authorization"] + if "Authorization" in request.headers + else "" + ) + + async def aux_func(turn_context): + await self.bot.on_turn(turn_context) + + try: + task = self.loop.create_task( + self.adapter.process_activity(activity, auth_header, aux_func) + ) + self.loop.run_until_complete(task) + return Response(status=201) + except Exception as exception: + raise exception + + @staticmethod + def test() -> Response: + """ + For test only - verify if the flask app works locally - e.g. with: + ```bash + curl http://127.0.0.1:3978/api/test + ``` + You shall get: + ``` + test + ``` + """ + return Response(status=200, response="test\n") + + def run(self, host=None) -> None: + try: + self.flask.run( + host=host, debug=False, port=self.flask.config["PORT"] + ) # nosec debug + except Exception as exception: + raise exception diff --git a/libraries/functional-tests/functionaltestbot/flask_bot_app/default_config.py b/libraries/functional-tests/functionaltestbot/flask_bot_app/default_config.py new file mode 100644 index 000000000..96c277e09 --- /dev/null +++ b/libraries/functional-tests/functionaltestbot/flask_bot_app/default_config.py @@ -0,0 +1,12 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from os import environ + + +class DefaultConfig: + """ Bot Configuration """ + + PORT: int = 3978 + APP_ID: str = environ.get("MicrosoftAppId", "") + APP_PASSWORD: str = environ.get("MicrosoftAppPassword", "") diff --git a/libraries/functional-tests/functionaltestbot/flask_bot_app/my_bot.py b/libraries/functional-tests/functionaltestbot/flask_bot_app/my_bot.py new file mode 100644 index 000000000..58f002986 --- /dev/null +++ b/libraries/functional-tests/functionaltestbot/flask_bot_app/my_bot.py @@ -0,0 +1,19 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from botbuilder.core import ActivityHandler, TurnContext +from botbuilder.schema import ChannelAccount + + +class MyBot(ActivityHandler): + """See https://aka.ms/about-bot-activity-message to learn more about the message and other activity types.""" + + async def on_message_activity(self, turn_context: TurnContext): + await turn_context.send_activity(f"You said '{ turn_context.activity.text }'") + + async def on_members_added_activity( + self, members_added: ChannelAccount, turn_context: TurnContext + ): + for member_added in members_added: + if member_added.id != turn_context.activity.recipient.id: + await turn_context.send_activity("Hello and welcome!") diff --git a/libraries/functional-tests/functionaltestbot/functionaltestbot/README.md b/libraries/functional-tests/functionaltestbot/functionaltestbot/README.md new file mode 100644 index 000000000..996e0909b --- /dev/null +++ b/libraries/functional-tests/functionaltestbot/functionaltestbot/README.md @@ -0,0 +1,35 @@ +# Console EchoBot +Bot Framework v4 console echo sample. + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to create a simple bot that you can talk to from the console window. + +This sample shows a simple echo bot and demonstrates the bot working as a console app using a sample console adapter. + +## To try this sample +- Clone the repository +```bash +git clone https://github.com/Microsoft/botbuilder-python.git +``` + + +### Visual studio code +- open `botbuilder-python\samples\01.console-echo` folder +- Bring up a terminal, navigate to `botbuilder-python\samples\01.console-echo` folder +- type 'python main.py' + + +# Adapters +[Adapters](https://docs.microsoft.com/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0#the-bot-adapter) provide an abstraction for your bot to work with a variety of environments. + +A bot is directed by it's adapter, which can be thought of as the conductor for your bot. The adapter is responsible for directing incoming and outgoing communication, authentication, and so on. The adapter differs based on it's environment (the adapter internally works differently locally versus on Azure) but in each instance it achieves the same goal. + +In most situations we don't work with the adapter directly, such as when creating a bot from a template, but it's good to know it's there and what it does. +The bot adapter encapsulates authentication processes and sends activities to and receives activities from the Bot Connector Service. When your bot receives an activity, the adapter wraps up everything about that activity, creates a [context object](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0#turn-context), passes it to your bot's application logic, and sends responses generated by your bot back to the user's channel. + + +# Further reading + +- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) +- [Bot basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) +- [Channels and Bot Connector service](https://docs.microsoft.com/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) +- [Activity processing](https://docs.microsoft.com/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) \ No newline at end of file diff --git a/libraries/functional-tests/functionaltestbot/functionaltestbot/about.py b/libraries/functional-tests/functionaltestbot/functionaltestbot/about.py new file mode 100644 index 000000000..223c72f3d --- /dev/null +++ b/libraries/functional-tests/functionaltestbot/functionaltestbot/about.py @@ -0,0 +1,14 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +"""Package information.""" +import os + +__title__ = "functionaltestbot" +__version__ = ( + os.environ["packageVersion"] if "packageVersion" in os.environ else "0.0.1" +) +__uri__ = "https://www.github.com/Microsoft/botbuilder-python" +__author__ = "Microsoft" +__description__ = "Microsoft Bot Framework Bot Builder" +__summary__ = "Microsoft Bot Framework Bot Builder SDK for Python." +__license__ = "MIT" diff --git a/libraries/functional-tests/functionaltestbot/functionaltestbot/app.py b/libraries/functional-tests/functionaltestbot/functionaltestbot/app.py new file mode 100644 index 000000000..071a17d2b --- /dev/null +++ b/libraries/functional-tests/functionaltestbot/functionaltestbot/app.py @@ -0,0 +1,86 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import asyncio +import sys +from types import MethodType + +from flask import Flask, request, Response +from botbuilder.core import ( + BotFrameworkAdapter, + BotFrameworkAdapterSettings, + MessageFactory, + TurnContext, +) +from botbuilder.schema import Activity, InputHints +from bot import MyBot + +# Create the loop and Flask app +LOOP = asyncio.get_event_loop() +APP = Flask(__name__, instance_relative_config=True) +APP.config.from_object("config.DefaultConfig") + +# Create adapter. +# See https://aka.ms/about-bot-adapter to learn more about how bots work. +SETTINGS = BotFrameworkAdapterSettings(APP.config["APP_ID"], APP.config["APP_PASSWORD"]) +ADAPTER = BotFrameworkAdapter(SETTINGS) + +# Catch-all for errors. +# pylint: disable=unused-argument +async def on_error(self, 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]: {error}", file=sys.stderr) + + # Send a message to the user + error_message_text = "Sorry, it looks like something went wrong." + error_message = MessageFactory.text( + error_message_text, error_message_text, InputHints.expecting_input + ) + await context.send_activity(error_message) + + +ADAPTER.on_turn_error = MethodType(on_error, ADAPTER) + +# Create the main dialog +BOT = MyBot() + +# Listen for incoming requests on GET / for Azure monitoring +@APP.route("/", methods=["GET"]) +def ping(): + return Response(status=200) + + +# Listen for incoming requests on /api/messages. +@APP.route("/api/messages", methods=["POST"]) +def messages(): + # Main bot message handler. + if "application/json" in request.headers["Content-Type"]: + body = request.json + else: + return Response(status=415) + + activity = Activity().deserialize(body) + auth_header = ( + request.headers["Authorization"] if "Authorization" in request.headers else "" + ) + + async def aux_func(turn_context): + await BOT.on_turn(turn_context) + + try: + task = LOOP.create_task( + ADAPTER.process_activity(activity, auth_header, aux_func) + ) + LOOP.run_until_complete(task) + return Response(status=201) + except Exception as exception: + raise exception + + +if __name__ == "__main__": + try: + APP.run(debug=False, port=APP.config["PORT"]) # nosec debug + except Exception as exception: + raise exception diff --git a/libraries/functional-tests/functionaltestbot/functionaltestbot/bot.py b/libraries/functional-tests/functionaltestbot/functionaltestbot/bot.py new file mode 100644 index 000000000..128f47cf6 --- /dev/null +++ b/libraries/functional-tests/functionaltestbot/functionaltestbot/bot.py @@ -0,0 +1,19 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from botbuilder.core import ActivityHandler, TurnContext +from botbuilder.schema import ChannelAccount + + +class MyBot(ActivityHandler): + # See https://aka.ms/about-bot-activity-message to learn more about the message and other activity types. + + async def on_message_activity(self, turn_context: TurnContext): + await turn_context.send_activity(f"You said '{ turn_context.activity.text }'") + + async def on_members_added_activity( + self, members_added: ChannelAccount, turn_context: TurnContext + ): + for member_added in members_added: + if member_added.id != turn_context.activity.recipient.id: + await turn_context.send_activity("Hello and welcome!") diff --git a/libraries/functional-tests/functionaltestbot/config.py b/libraries/functional-tests/functionaltestbot/functionaltestbot/config.py similarity index 94% rename from libraries/functional-tests/functionaltestbot/config.py rename to libraries/functional-tests/functionaltestbot/functionaltestbot/config.py index 6b5116fba..a3bd72174 100644 --- a/libraries/functional-tests/functionaltestbot/config.py +++ b/libraries/functional-tests/functionaltestbot/functionaltestbot/config.py @@ -8,6 +8,6 @@ class DefaultConfig: """ Bot Configuration """ - PORT = 3978 + PORT = 443 APP_ID = os.environ.get("MicrosoftAppId", "") APP_PASSWORD = os.environ.get("MicrosoftAppPassword", "") diff --git a/libraries/functional-tests/functionaltestbot/functionaltestbot/requirements.txt b/libraries/functional-tests/functionaltestbot/functionaltestbot/requirements.txt new file mode 100644 index 000000000..2e5ecf3fc --- /dev/null +++ b/libraries/functional-tests/functionaltestbot/functionaltestbot/requirements.txt @@ -0,0 +1,3 @@ +botbuilder-core>=4.5.0.b4 +flask>=1.0.3 + diff --git a/libraries/functional-tests/functionaltestbot/init.sh b/libraries/functional-tests/functionaltestbot/init.sh new file mode 100644 index 000000000..4a5a5be78 --- /dev/null +++ b/libraries/functional-tests/functionaltestbot/init.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -e + +echo "Starting SSH ..." +service ssh start + +# flask run --port 3978 --host 0.0.0.0 +python /functionaltestbot/app.py --host 0.0.0.0 \ No newline at end of file diff --git a/libraries/functional-tests/functionaltestbot/requirements.txt b/libraries/functional-tests/functionaltestbot/requirements.txt index 38ad6c528..a348b59af 100644 --- a/libraries/functional-tests/functionaltestbot/requirements.txt +++ b/libraries/functional-tests/functionaltestbot/requirements.txt @@ -1,7 +1,5 @@ -click==6.7 -Flask==1.0.2 -itsdangerous==0.24 -Jinja2==2.10 -MarkupSafe==1.0 -Werkzeug==0.14.1 -botbuilder-core>=4.4.0b1 \ No newline at end of file +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +botbuilder-core>=4.5.0.b4 +flask==1.1.1 diff --git a/libraries/functional-tests/functionaltestbot/runserver.py b/libraries/functional-tests/functionaltestbot/runserver.py new file mode 100644 index 000000000..9b0e449a7 --- /dev/null +++ b/libraries/functional-tests/functionaltestbot/runserver.py @@ -0,0 +1,16 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +""" +To run the Flask bot app, in a py virtual environment, +```bash +pip install -r requirements.txt +python runserver.py +``` +""" + +from flask_bot_app import APP + + +if __name__ == "__main__": + APP.run(host="0.0.0.0") diff --git a/libraries/functional-tests/functionaltestbot/scripts/deploy_webapp.sh b/libraries/functional-tests/functionaltestbot/scripts/deploy_webapp.sh deleted file mode 100644 index c1e7efd4d..000000000 --- a/libraries/functional-tests/functionaltestbot/scripts/deploy_webapp.sh +++ /dev/null @@ -1,186 +0,0 @@ -#!/bin/bash -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -# This Script provisions and deploys a Python bot with retries. - -# Make errors stop the script (set -e) -set -e -# Uncomment to debug: print commands (set -x) -#set -x - -# Define Environment Variables -WEBAPP_NAME="pyfuntest" -WEBAPP_URL= -BOT_NAME="pyfuntest" -BOT_ID="python_functional" -AZURE_RESOURCE_GROUP= -BOT_APPID= -BOT_PASSWORD= - -usage() -{ - echo "${0##*/} [options]" - echo "" - echo "Runs Python DirectLine bot test." - echo "- Deletes and recreates given Azure Resource Group (cleanup)" - echo "- Provision and deploy python bot" - echo "- Provision DirectLine support for bot" - echo "- Run python directline client against deployed bot using DirectLine" - echo " as a test." - echo "" - echo "Note: Assumes you are logged into Azure." - echo "" - echo "options" - echo " -a, --appid Bot App ID" - echo " -p, --password Bot App Password" - echo " -g, --resource-group Azure Resource Group name" - exit 1; -} - -print_help_and_exit() -{ - echo "Run '${0##*/} --help' for more information." - exit 1 -} - -process_args() -{ - if [ "${PWD##*/}" != 'functionaltestbot' ]; then - echo "ERROR: Must run from '/functional-tests/functionaltestbot' directory." - echo "Your current directory: ${PWD##*/}" - echo "" - echo "For example:" - echo "$ ./scripts/deploy_webapp.sh --appid X --password Y -g Z" - exit 1 - fi - - save_next_arg=0 - for arg in "$@" - do - if [[ ${save_next_arg} -eq 1 ]]; then - BOT_APPID="$arg" - save_next_arg=0 - elif [[ ${save_next_arg} -eq 2 ]]; then - BOT_PASSWORD="$arg" - save_next_arg=0 - elif [[ ${save_next_arg} -eq 3 ]]; then - AZURE_RESOURCE_GROUP="$arg" - save_next_arg=0 - else - case "$arg" in - "-h" | "--help" ) usage;; - "-a" | "--appid" ) save_next_arg=1;; - "-p" | "--password" ) save_next_arg=2;; - "-g" | "--resource-group" ) save_next_arg=3;; - * ) usage;; - esac - fi - done - if [[ -z ${BOT_APPID} ]]; then - echo "Bot appid parameter invalid" - print_help_and_exit - fi - if [[ -z ${BOT_PASSWORD} ]]; then - echo "Bot password parameter invalid" - print_help_and_exit - fi - if [[ -z ${AZURE_RESOURCE_GROUP} ]]; then - echo "Azure Resource Group parameter invalid" - print_help_and_exit - fi -} - -############################################################################### -# Main Script Execution -############################################################################### -process_args "$@" - -# Recreate Resource Group - -# It's ok to fail (set +e) - script continues on error result code. -set +e -az group delete --name ${AZURE_RESOURCE_GROUP} -y - -n=0 -until [ $n -ge 3 ] -do - az group create --location westus --name ${AZURE_RESOURCE_GROUP} && break - n=$[$n+1] - sleep 25 -done -if [[ $n -ge 3 ]]; then - echo "Could not create group ${AZURE_RESOURCE_GROUP}" - exit 3 -fi - -# Push Web App -n=0 -until [ $n -ge 3 ] -do - az webapp up --sku F1 -n ${WEBAPP_NAME} -l westus --resource-group ${AZURE_RESOURCE_GROUP} && break - n=$[$n+1] - sleep 25 -done -if [[ $n -ge 3 ]]; then - echo "Could not create webapp ${WEBAPP_NAME}" - exit 4 -fi - - -n=0 -until [ $n -ge 3 ] -do - az bot create --appid ${BOT_APPID} --name ${BOT_NAME} --password ${BOT_PASSWORD} --resource-group ${AZURE_RESOURCE_GROUP} --sku F0 --kind registration --location westus --endpoint "https://${WEBAPP_NAME}.azurewebsites.net/api/messages" && break - n=$[$n+1] - sleep 25 -done -if [[ $n -ge 3 ]]; then - echo "Could not create BOT ${BOT_NAME}" - exit 5 -fi - - -# Create bot settings -n=0 -until [ $n -ge 3 ] -do - az webapp config appsettings set -g ${AZURE_RESOURCE_GROUP} -n ${AZURE_RESOURCE_GROUP} --settings MicrosoftAppId=${BOT_APPID} MicrosoftAppPassword=${BOT_PASSWORD} botId=${BOT_ID} && break - n=$[$n+1] - sleep 25 -done -if [[ $n -ge 3 ]]; then - echo "Could not create BOT configuration" - exit 6 -fi - -# Create DirectLine -cd tests -n=0 -until [ $n -ge 3 ] -do - az bot directline create --name ${BOT_NAME} --resource-group ${AZURE_RESOURCE_GROUP} > "DirectLineConfig.json" && break - n=$[$n+1] - sleep 25 -done -if [[ $n -ge 3 ]]; then - echo "Could not create Directline configuration" - exit 7 -fi - - -# Run Tests -pip install requests -n=0 -until [ $n -ge 3 ] -do - python -m unittest test_py_bot.py && break - n=$[$n+1] - sleep 25 -done -if [[ $n -ge 3 ]]; then - echo "Tests failed!" - exit 8 -fi - - diff --git a/libraries/functional-tests/functionaltestbot/setup.py b/libraries/functional-tests/functionaltestbot/setup.py new file mode 100644 index 000000000..1378ac4b0 --- /dev/null +++ b/libraries/functional-tests/functionaltestbot/setup.py @@ -0,0 +1,40 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import os +from setuptools import setup + +REQUIRES = [ + "botbuilder-core>=4.5.0.b4", + "flask==1.1.1", +] + +root = os.path.abspath(os.path.dirname(__file__)) + +with open(os.path.join(root, "functionaltestbot", "about.py")) as f: + package_info = {} + info = f.read() + exec(info, package_info) + +setup( + name=package_info["__title__"], + version=package_info["__version__"], + url=package_info["__uri__"], + author=package_info["__author__"], + description=package_info["__description__"], + keywords="botframework azure botbuilder", + long_description=package_info["__summary__"], + license=package_info["__license__"], + packages=["functionaltestbot"], + install_requires=REQUIRES, + dependency_links=["https://github.com/pytorch/pytorch"], + include_package_data=True, + classifiers=[ + "Programming Language :: Python :: 3.6", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Development Status :: 3 - Alpha", + "Topic :: Scientific/Engineering :: Artificial Intelligence", + ], +) diff --git a/libraries/functional-tests/functionaltestbot/sshd_config b/libraries/functional-tests/functionaltestbot/sshd_config new file mode 100644 index 000000000..7afb7469f --- /dev/null +++ b/libraries/functional-tests/functionaltestbot/sshd_config @@ -0,0 +1,21 @@ +# +# /etc/ssh/sshd_config +# + +Port 2222 +ListenAddress 0.0.0.0 +LoginGraceTime 180 +X11Forwarding yes +Ciphers aes128-cbc,3des-cbc,aes256-cbc +MACs hmac-sha1,hmac-sha1-96 +StrictModes yes +SyslogFacility DAEMON +PrintMotd no +IgnoreRhosts no +#deprecated option +#RhostsAuthentication no +RhostsRSAAuthentication yes +RSAAuthentication no +PasswordAuthentication yes +PermitEmptyPasswords no +PermitRootLogin yes \ No newline at end of file diff --git a/libraries/functional-tests/functionaltestbot/template/linux/template.json b/libraries/functional-tests/functionaltestbot/template/linux/template.json new file mode 100644 index 000000000..dcf832eb2 --- /dev/null +++ b/libraries/functional-tests/functionaltestbot/template/linux/template.json @@ -0,0 +1,238 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "botName": { + "defaultValue": "nightly-build-python-linux", + "type": "string", + "minLength": 2 + }, + "sku": { + "defaultValue": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + }, + "type": "object" + }, + "linuxFxVersion": { + "type": "string", + "defaultValue": "PYTHON|3.6" + }, + "location": { + "type": "string", + "defaultValue": "West US", + "metadata": { + "description": "Location for all resources." + } + }, + "appId": { + "defaultValue": "1234", + "type": "string" + }, + "appSecret": { + "defaultValue": "blank", + "type": "string" + } + }, + "variables": { + "siteHost": "[concat(parameters('botName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/mybot')]" + }, + "resources": [ + { + "type": "Microsoft.Web/serverfarms", + "apiVersion": "2017-08-01", + "name": "[parameters('botName')]", + "kind": "linux", + "location": "[parameters('location')]", + "sku": "[parameters('sku')]", + "properties": { + "name": "[parameters('botName')]", + "reserved": true, + "perSiteScaling": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "type": "Microsoft.Web/sites", + "apiVersion": "2016-08-01", + "name": "[parameters('botName')]", + "location": "[parameters('location')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', parameters('botName'))]" + ], + "kind": "app,linux", + "properties": { + "enabled": true, + "hostNameSslStates": [ + { + "name": "[concat(parameters('botName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('botName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('botName'))]", + "siteConfig": { + "linuxFxVersion": "[parameters('linuxFxVersion')]", + "appSettings": [ + { + "name": "WEBSITE_NODE_DEFAULT_VERSION", + "value": "10.14.1" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ] + }, + "reserved": true, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2016-08-01", + "name": "[concat(parameters('botName'), '/web')]", + "location": "West US", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', parameters('botName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "phpVersion": "", + "pythonVersion": "", + "nodeVersion": "", + "linuxFxVersion": "[parameters('linuxFxVersion')]", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "parameters('botName')", + "scmType": "LocalGit", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "appCommandLine": "", + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true, + "virtualDirectories": null + } + ], + "winAuthAdminState": 0, + "winAuthTenantState": 0, + "customAppPoolIdentityAdminState": false, + "customAppPoolIdentityTenantState": false, + "loadBalancing": "LeastRequests", + "routingRules": [], + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "vnetName": "", + "siteAuthEnabled": false, + "siteAuthSettings": { + "enabled": null, + "unauthenticatedClientAction": null, + "tokenStoreEnabled": null, + "allowedExternalRedirectUrls": null, + "defaultProvider": null, + "clientId": null, + "clientSecret": null, + "clientSecretCertificateThumbprint": null, + "issuer": null, + "allowedAudiences": null, + "additionalLoginParams": null, + "isAadAutoProvisioned": false, + "googleClientId": null, + "googleClientSecret": null, + "googleOAuthScopes": null, + "facebookAppId": null, + "facebookAppSecret": null, + "facebookOAuthScopes": null, + "twitterConsumerKey": null, + "twitterConsumerSecret": null, + "microsoftAccountClientId": null, + "microsoftAccountClientSecret": null, + "microsoftAccountOAuthScopes": null + }, + "localMySqlEnabled": false, + "http20Enabled": true, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botName')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botName')]" + }, + "properties": { + "name": "[parameters('botName')]", + "displayName": "[parameters('botName')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', parameters('botName'))]" + ] + }, + { + "type": "Microsoft.Web/sites/hostNameBindings", + "apiVersion": "2016-08-01", + "name": "[concat(parameters('botName'), '/', parameters('botName'), '.azurewebsites.net')]", + "location": "West US", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', parameters('botName'))]" + ], + "properties": { + "siteName": "parameters('botName')", + "hostNameType": "Verified" + } + } + ] +} \ No newline at end of file diff --git a/libraries/functional-tests/functionaltestbot/test.sh b/libraries/functional-tests/functionaltestbot/test.sh new file mode 100644 index 000000000..1c987232e --- /dev/null +++ b/libraries/functional-tests/functionaltestbot/test.sh @@ -0,0 +1 @@ +curl -X POST --header 'Accept: application/json' -d '{"text": "Hi!"}' http://localhost:3979 diff --git a/libraries/functional-tests/functionaltestbot/tests/test_py_bot.py b/libraries/functional-tests/functionaltestbot/tests/test_py_bot.py deleted file mode 100644 index 3a78aca08..000000000 --- a/libraries/functional-tests/functionaltestbot/tests/test_py_bot.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -""" -Unit test for testing DirectLine - -To execute: - python -m unittest test_py_bot.py - -This assumes a DirectLine configuration json file is available (DirectLineConfig.json) -that was generated when adding DirectLine to the bot's channel. - - az bot directline create --name "pyfuntest" --resource-group "pyfuntest" > "DirectLineConfig.json" - -""" - - -import os -import json -from unittest import TestCase - -from direct_line_client import DirectLineClient - - -class PyBotTest(TestCase): - def setUp(self): - direct_line_config = os.environ.get( - "DIRECT_LINE_CONFIG", "DirectLineConfig.json" - ) - with open(direct_line_config) as direct_line_file: - self.direct_line_config = json.load(direct_line_file) - self.direct_line_secret = self.direct_line_config["properties"]["properties"][ - "sites" - ][0]["key"] - self.assertIsNotNone(self.direct_line_secret) - - def test_deployed_bot_answer(self): - client = DirectLineClient(self.direct_line_secret) - user_message = "Contoso" - - send_result = client.send_message(user_message) - self.assertIsNotNone(send_result) - self.assertEqual(200, send_result.status_code) - - response, text = client.get_message() - self.assertIsNotNone(response) - self.assertEqual(200, response.status_code) - self.assertEqual(f"Echo: {user_message}", text) - print("SUCCESS!") diff --git a/libraries/functional-tests/functionaltestbot/tests/direct_line_client.py b/libraries/functional-tests/tests/direct_line_client.py similarity index 100% rename from libraries/functional-tests/functionaltestbot/tests/direct_line_client.py rename to libraries/functional-tests/tests/direct_line_client.py diff --git a/libraries/functional-tests/tests/test_py_bot.py b/libraries/functional-tests/tests/test_py_bot.py new file mode 100644 index 000000000..bdea7fd6c --- /dev/null +++ b/libraries/functional-tests/tests/test_py_bot.py @@ -0,0 +1,26 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import os +from unittest import TestCase + +from direct_line_client import DirectLineClient + + +class PyBotTest(TestCase): + def test_deployed_bot_answer(self): + direct_line_secret = os.environ.get("DIRECT_LINE_KEY", "") + if direct_line_secret == "": + return + + client = DirectLineClient(direct_line_secret) + user_message: str = "Contoso" + + send_result = client.send_message(user_message) + self.assertIsNotNone(send_result) + self.assertEqual(200, send_result.status_code) + + response, text = client.get_message() + self.assertIsNotNone(response) + self.assertEqual(200, response.status_code) + self.assertEqual(f"You said '{user_message}'", text) diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index dd4ed50bd..000000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[tool:pytest] -norecursedirs = functionaltestbot