cohit's picture
Upload folder using huggingface_hub
0827183 verified
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
import uuid
from http import HTTPStatus
from typing import Callable, Union, List
from unittest.mock import Mock
import aiounittest
from botframework.connector.token_api.models import TokenExchangeResource
from botbuilder.core import (
ConversationState,
MemoryStorage,
InvokeResponse,
TurnContext,
MessageFactory,
)
from botbuilder.core.card_factory import ContentTypes
from botbuilder.core.skills import (
BotFrameworkSkill,
ConversationIdFactoryBase,
SkillConversationIdFactoryOptions,
SkillConversationReference,
BotFrameworkClient,
)
from botbuilder.schema import (
Activity,
ActivityTypes,
ConversationReference,
OAuthCard,
Attachment,
ConversationAccount,
ChannelAccount,
ExpectedReplies,
DeliveryModes,
)
from botbuilder.testing import DialogTestClient
from botbuilder.dialogs import (
SkillDialog,
SkillDialogOptions,
BeginSkillDialogOptions,
DialogTurnStatus,
)
class SimpleConversationIdFactory(
ConversationIdFactoryBase
): # pylint: disable=abstract-method
def __init__(self):
self.conversation_refs = {}
self.create_count = 0
async def create_skill_conversation_id(
self,
options_or_conversation_reference: Union[
SkillConversationIdFactoryOptions, ConversationReference
],
) -> str:
self.create_count += 1
key = (
options_or_conversation_reference.activity.conversation.id
+ options_or_conversation_reference.activity.service_url
)
if key not in self.conversation_refs:
self.conversation_refs[key] = SkillConversationReference(
conversation_reference=TurnContext.get_conversation_reference(
options_or_conversation_reference.activity
),
oauth_scope=options_or_conversation_reference.from_bot_oauth_scope,
)
return key
async def get_skill_conversation_reference(
self, skill_conversation_id: str
) -> SkillConversationReference:
return self.conversation_refs[skill_conversation_id]
async def delete_conversation_reference(self, skill_conversation_id: str):
self.conversation_refs.pop(skill_conversation_id, None)
return
class SkillDialogTests(aiounittest.AsyncTestCase):
async def test_constructor_validation_test(self):
# missing dialog_id
with self.assertRaises(TypeError):
SkillDialog(SkillDialogOptions(), None)
# missing dialog options
with self.assertRaises(TypeError):
SkillDialog(None, "dialog_id")
async def test_begin_dialog_options_validation(self):
dialog_options = SkillDialogOptions()
sut = SkillDialog(dialog_options, dialog_id="dialog_id")
# empty options should raise
client = DialogTestClient("test", sut)
with self.assertRaises(TypeError):
await client.send_activity("irrelevant")
# non DialogArgs should raise
client = DialogTestClient("test", sut, {})
with self.assertRaises(TypeError):
await client.send_activity("irrelevant")
# Activity in DialogArgs should be set
client = DialogTestClient("test", sut, BeginSkillDialogOptions(None))
with self.assertRaises(TypeError):
await client.send_activity("irrelevant")
async def test_begin_dialog_calls_skill_no_deliverymode(self):
return await self.begin_dialog_calls_skill(None)
async def test_begin_dialog_calls_skill_expect_replies(self):
return await self.begin_dialog_calls_skill(DeliveryModes.expect_replies)
async def begin_dialog_calls_skill(self, deliver_mode: str):
activity_sent = None
from_bot_id_sent = None
to_bot_id_sent = None
to_url_sent = None
async def capture(
from_bot_id: str,
to_bot_id: str,
to_url: str,
service_url: str, # pylint: disable=unused-argument
conversation_id: str, # pylint: disable=unused-argument
activity: Activity,
):
nonlocal from_bot_id_sent, to_bot_id_sent, to_url_sent, activity_sent
from_bot_id_sent = from_bot_id
to_bot_id_sent = to_bot_id
to_url_sent = to_url
activity_sent = activity
mock_skill_client = self._create_mock_skill_client(capture)
conversation_state = ConversationState(MemoryStorage())
dialog_options = SkillDialogTests.create_skill_dialog_options(
conversation_state, mock_skill_client
)
sut = SkillDialog(dialog_options, "dialog_id")
activity_to_send = MessageFactory.text(str(uuid.uuid4()))
activity_to_send.delivery_mode = deliver_mode
client = DialogTestClient(
"test",
sut,
BeginSkillDialogOptions(activity=activity_to_send),
conversation_state=conversation_state,
)
assert len(dialog_options.conversation_id_factory.conversation_refs) == 0
# Send something to the dialog to start it
await client.send_activity(MessageFactory.text("irrelevant"))
# Assert results and data sent to the SkillClient for fist turn
assert len(dialog_options.conversation_id_factory.conversation_refs) == 1
assert dialog_options.bot_id == from_bot_id_sent
assert dialog_options.skill.app_id == to_bot_id_sent
assert dialog_options.skill.skill_endpoint == to_url_sent
assert activity_to_send.text == activity_sent.text
assert DialogTurnStatus.Waiting == client.dialog_turn_result.status
# Send a second message to continue the dialog
await client.send_activity(MessageFactory.text("Second message"))
# Assert results for second turn
assert len(dialog_options.conversation_id_factory.conversation_refs) == 1
assert activity_sent.text == "Second message"
assert DialogTurnStatus.Waiting == client.dialog_turn_result.status
# Send EndOfConversation to the dialog
await client.send_activity(Activity(type=ActivityTypes.end_of_conversation))
# Assert we are done.
assert DialogTurnStatus.Complete == client.dialog_turn_result.status
async def test_should_handle_invoke_activities(self):
activity_sent = None
from_bot_id_sent = None
to_bot_id_sent = None
to_url_sent = None
async def capture(
from_bot_id: str,
to_bot_id: str,
to_url: str,
service_url: str, # pylint: disable=unused-argument
conversation_id: str, # pylint: disable=unused-argument
activity: Activity,
):
nonlocal from_bot_id_sent, to_bot_id_sent, to_url_sent, activity_sent
from_bot_id_sent = from_bot_id
to_bot_id_sent = to_bot_id
to_url_sent = to_url
activity_sent = activity
mock_skill_client = self._create_mock_skill_client(capture)
conversation_state = ConversationState(MemoryStorage())
dialog_options = SkillDialogTests.create_skill_dialog_options(
conversation_state, mock_skill_client
)
sut = SkillDialog(dialog_options, "dialog_id")
activity_to_send = Activity(
type=ActivityTypes.invoke,
name=str(uuid.uuid4()),
)
client = DialogTestClient(
"test",
sut,
BeginSkillDialogOptions(activity=activity_to_send),
conversation_state=conversation_state,
)
# Send something to the dialog to start it
await client.send_activity(MessageFactory.text("irrelevant"))
# Assert results and data sent to the SkillClient for fist turn
assert dialog_options.bot_id == from_bot_id_sent
assert dialog_options.skill.app_id == to_bot_id_sent
assert dialog_options.skill.skill_endpoint == to_url_sent
assert activity_to_send.text == activity_sent.text
assert DialogTurnStatus.Waiting == client.dialog_turn_result.status
# Send a second message to continue the dialog
await client.send_activity(MessageFactory.text("Second message"))
# Assert results for second turn
assert activity_sent.text == "Second message"
assert DialogTurnStatus.Waiting == client.dialog_turn_result.status
# Send EndOfConversation to the dialog
await client.send_activity(Activity(type=ActivityTypes.end_of_conversation))
# Assert we are done.
assert DialogTurnStatus.Complete == client.dialog_turn_result.status
async def test_cancel_dialog_sends_eoc(self):
activity_sent = None
async def capture(
from_bot_id: str, # pylint: disable=unused-argument
to_bot_id: str, # pylint: disable=unused-argument
to_url: str, # pylint: disable=unused-argument
service_url: str, # pylint: disable=unused-argument
conversation_id: str, # pylint: disable=unused-argument
activity: Activity,
):
nonlocal activity_sent
activity_sent = activity
mock_skill_client = self._create_mock_skill_client(capture)
conversation_state = ConversationState(MemoryStorage())
dialog_options = SkillDialogTests.create_skill_dialog_options(
conversation_state, mock_skill_client
)
sut = SkillDialog(dialog_options, "dialog_id")
activity_to_send = MessageFactory.text(str(uuid.uuid4()))
client = DialogTestClient(
"test",
sut,
BeginSkillDialogOptions(activity=activity_to_send),
conversation_state=conversation_state,
)
# Send something to the dialog to start it
await client.send_activity(MessageFactory.text("irrelevant"))
# Cancel the dialog so it sends an EoC to the skill
await client.dialog_context.cancel_all_dialogs()
assert activity_sent
assert activity_sent.type == ActivityTypes.end_of_conversation
async def test_should_throw_on_post_failure(self):
# This mock client will fail
mock_skill_client = self._create_mock_skill_client(None, 500)
conversation_state = ConversationState(MemoryStorage())
dialog_options = SkillDialogTests.create_skill_dialog_options(
conversation_state, mock_skill_client
)
sut = SkillDialog(dialog_options, "dialog_id")
activity_to_send = MessageFactory.text(str(uuid.uuid4()))
client = DialogTestClient(
"test",
sut,
BeginSkillDialogOptions(activity=activity_to_send),
conversation_state=conversation_state,
)
# A send should raise an exception
with self.assertRaises(Exception):
await client.send_activity("irrelevant")
async def test_should_intercept_oauth_cards_for_sso(self):
connection_name = "connectionName"
first_response = ExpectedReplies(
activities=[
SkillDialogTests.create_oauth_card_attachment_activity("https://test")
]
)
sequence = 0
async def post_return():
nonlocal sequence
if sequence == 0:
result = InvokeResponse(body=first_response, status=HTTPStatus.OK)
else:
result = InvokeResponse(status=HTTPStatus.OK)
sequence += 1
return result
mock_skill_client = self._create_mock_skill_client(None, post_return)
conversation_state = ConversationState(MemoryStorage())
dialog_options = SkillDialogTests.create_skill_dialog_options(
conversation_state, mock_skill_client, connection_name
)
sut = SkillDialog(dialog_options, dialog_id="dialog")
activity_to_send = SkillDialogTests.create_send_activity()
client = DialogTestClient(
"test",
sut,
BeginSkillDialogOptions(
activity=activity_to_send,
),
conversation_state=conversation_state,
)
client.test_adapter.add_exchangeable_token(
connection_name, "test", "User1", "https://test", "https://test1"
)
final_activity = await client.send_activity(MessageFactory.text("irrelevant"))
self.assertIsNone(final_activity)
async def test_should_not_intercept_oauth_cards_for_empty_connection_name(self):
connection_name = "connectionName"
first_response = ExpectedReplies(
activities=[
SkillDialogTests.create_oauth_card_attachment_activity("https://test")
]
)
sequence = 0
async def post_return():
nonlocal sequence
if sequence == 0:
result = InvokeResponse(body=first_response, status=HTTPStatus.OK)
else:
result = InvokeResponse(status=HTTPStatus.OK)
sequence += 1
return result
mock_skill_client = self._create_mock_skill_client(None, post_return)
conversation_state = ConversationState(MemoryStorage())
dialog_options = SkillDialogTests.create_skill_dialog_options(
conversation_state, mock_skill_client
)
sut = SkillDialog(dialog_options, dialog_id="dialog")
activity_to_send = SkillDialogTests.create_send_activity()
client = DialogTestClient(
"test",
sut,
BeginSkillDialogOptions(
activity=activity_to_send,
),
conversation_state=conversation_state,
)
client.test_adapter.add_exchangeable_token(
connection_name, "test", "User1", "https://test", "https://test1"
)
final_activity = await client.send_activity(MessageFactory.text("irrelevant"))
self.assertIsNotNone(final_activity)
self.assertEqual(len(final_activity.attachments), 1)
async def test_should_not_intercept_oauth_cards_for_empty_token(self):
first_response = ExpectedReplies(
activities=[
SkillDialogTests.create_oauth_card_attachment_activity("https://test")
]
)
sequence = 0
async def post_return():
nonlocal sequence
if sequence == 0:
result = InvokeResponse(body=first_response, status=HTTPStatus.OK)
else:
result = InvokeResponse(status=HTTPStatus.OK)
sequence += 1
return result
mock_skill_client = self._create_mock_skill_client(None, post_return)
conversation_state = ConversationState(MemoryStorage())
dialog_options = SkillDialogTests.create_skill_dialog_options(
conversation_state, mock_skill_client
)
sut = SkillDialog(dialog_options, dialog_id="dialog")
activity_to_send = SkillDialogTests.create_send_activity()
client = DialogTestClient(
"test",
sut,
BeginSkillDialogOptions(
activity=activity_to_send,
),
conversation_state=conversation_state,
)
# Don't add exchangeable token to test adapter
final_activity = await client.send_activity(MessageFactory.text("irrelevant"))
self.assertIsNotNone(final_activity)
self.assertEqual(len(final_activity.attachments), 1)
async def test_should_not_intercept_oauth_cards_for_token_exception(self):
connection_name = "connectionName"
first_response = ExpectedReplies(
activities=[
SkillDialogTests.create_oauth_card_attachment_activity("https://test")
]
)
sequence = 0
async def post_return():
nonlocal sequence
if sequence == 0:
result = InvokeResponse(body=first_response, status=HTTPStatus.OK)
else:
result = InvokeResponse(status=HTTPStatus.OK)
sequence += 1
return result
mock_skill_client = self._create_mock_skill_client(None, post_return)
conversation_state = ConversationState(MemoryStorage())
dialog_options = SkillDialogTests.create_skill_dialog_options(
conversation_state, mock_skill_client, connection_name
)
sut = SkillDialog(dialog_options, dialog_id="dialog")
activity_to_send = SkillDialogTests.create_send_activity()
initial_dialog_options = BeginSkillDialogOptions(
activity=activity_to_send,
)
client = DialogTestClient(
"test",
sut,
initial_dialog_options,
conversation_state=conversation_state,
)
client.test_adapter.throw_on_exchange_request(
connection_name, "test", "User1", "https://test"
)
final_activity = await client.send_activity(MessageFactory.text("irrelevant"))
self.assertIsNotNone(final_activity)
self.assertEqual(len(final_activity.attachments), 1)
async def test_should_not_intercept_oauth_cards_for_bad_request(self):
connection_name = "connectionName"
first_response = ExpectedReplies(
activities=[
SkillDialogTests.create_oauth_card_attachment_activity("https://test")
]
)
sequence = 0
async def post_return():
nonlocal sequence
if sequence == 0:
result = InvokeResponse(body=first_response, status=HTTPStatus.OK)
else:
result = InvokeResponse(status=HTTPStatus.CONFLICT)
sequence += 1
return result
mock_skill_client = self._create_mock_skill_client(None, post_return)
conversation_state = ConversationState(MemoryStorage())
dialog_options = SkillDialogTests.create_skill_dialog_options(
conversation_state, mock_skill_client, connection_name
)
sut = SkillDialog(dialog_options, dialog_id="dialog")
activity_to_send = SkillDialogTests.create_send_activity()
client = DialogTestClient(
"test",
sut,
BeginSkillDialogOptions(
activity=activity_to_send,
),
conversation_state=conversation_state,
)
client.test_adapter.add_exchangeable_token(
connection_name, "test", "User1", "https://test", "https://test1"
)
final_activity = await client.send_activity(MessageFactory.text("irrelevant"))
self.assertIsNotNone(final_activity)
self.assertEqual(len(final_activity.attachments), 1)
async def test_end_of_conversation_from_expect_replies_calls_delete_conversation_reference(
self,
):
activity_sent: Activity = None
# Callback to capture the parameters sent to the skill
async def capture_action(
from_bot_id: str, # pylint: disable=unused-argument
to_bot_id: str, # pylint: disable=unused-argument
to_uri: str, # pylint: disable=unused-argument
service_url: str, # pylint: disable=unused-argument
conversation_id: str, # pylint: disable=unused-argument
activity: Activity,
):
# Capture values sent to the skill so we can assert the right parameters were used.
nonlocal activity_sent
activity_sent = activity
eoc = Activity.create_end_of_conversation_activity()
expected_replies = list([eoc])
# Create a mock skill client to intercept calls and capture what is sent.
mock_skill_client = self._create_mock_skill_client(
capture_action, expected_replies=expected_replies
)
# Use Memory for conversation state
conversation_state = ConversationState(MemoryStorage())
dialog_options = self.create_skill_dialog_options(
conversation_state, mock_skill_client
)
# Create the SkillDialogInstance and the activity to send.
sut = SkillDialog(dialog_options, dialog_id="dialog")
activity_to_send = Activity.create_message_activity()
activity_to_send.delivery_mode = DeliveryModes.expect_replies
activity_to_send.text = str(uuid.uuid4())
client = DialogTestClient(
"test",
sut,
BeginSkillDialogOptions(activity_to_send),
conversation_state=conversation_state,
)
# Send something to the dialog to start it
await client.send_activity("hello")
simple_id_factory: SimpleConversationIdFactory = (
dialog_options.conversation_id_factory
)
self.assertEqual(0, len(simple_id_factory.conversation_refs))
self.assertEqual(1, simple_id_factory.create_count)
@staticmethod
def create_skill_dialog_options(
conversation_state: ConversationState,
skill_client: BotFrameworkClient,
connection_name: str = None,
):
return SkillDialogOptions(
bot_id=str(uuid.uuid4()),
skill_host_endpoint="http://test.contoso.com/skill/messages",
conversation_id_factory=SimpleConversationIdFactory(),
conversation_state=conversation_state,
skill_client=skill_client,
skill=BotFrameworkSkill(
app_id=str(uuid.uuid4()),
skill_endpoint="http://testskill.contoso.com/api/messages",
),
connection_name=connection_name,
)
@staticmethod
def create_send_activity() -> Activity:
return Activity(
type=ActivityTypes.message,
delivery_mode=DeliveryModes.expect_replies,
text=str(uuid.uuid4()),
)
@staticmethod
def create_oauth_card_attachment_activity(uri: str) -> Activity:
oauth_card = OAuthCard(token_exchange_resource=TokenExchangeResource(uri=uri))
attachment = Attachment(
content_type=ContentTypes.oauth_card,
content=oauth_card,
)
attachment_activity = MessageFactory.attachment(attachment)
attachment_activity.conversation = ConversationAccount(id=str(uuid.uuid4()))
attachment_activity.from_property = ChannelAccount(id="blah", name="name")
return attachment_activity
def _create_mock_skill_client(
self,
callback: Callable,
return_status: Union[Callable, int] = 200,
expected_replies: List[Activity] = None,
) -> BotFrameworkClient:
mock_client = Mock()
activity_list = ExpectedReplies(
activities=expected_replies or [MessageFactory.text("dummy activity")]
)
async def mock_post_activity(
from_bot_id: str,
to_bot_id: str,
to_url: str,
service_url: str,
conversation_id: str,
activity: Activity,
):
nonlocal callback, return_status
if callback:
await callback(
from_bot_id,
to_bot_id,
to_url,
service_url,
conversation_id,
activity,
)
if isinstance(return_status, Callable):
return await return_status()
return InvokeResponse(status=return_status, body=activity_list)
mock_client.post_activity.side_effect = mock_post_activity
return mock_client