async def test_trace_test(self): activity = Activity( type=ActivityTypes.message, text="how do I clean the stove?", conversation=ConversationAccount(), recipient=ChannelAccount(), from_property=ChannelAccount(), ) response_json = QnaApplicationTest._get_json_for_file("ReturnsAnswer.json") qna = QnAMaker(QnaApplicationTest.tests_endpoint) context = TestContext(activity) with patch( "aiohttp.ClientSession.post", return_value=aiounittest.futurized(response_json), ): result = await qna.get_answers(context) qna_trace_activities = list( filter( lambda act: act.type == "trace" and act.name == "QnAMaker", context.sent, ) ) trace_activity = qna_trace_activities[0] self.assertEqual("trace", trace_activity.type) self.assertEqual("QnAMaker", trace_activity.name) self.assertEqual("QnAMaker Trace", trace_activity.label) self.assertEqual( "https://www.qnamaker.ai/schemas/trace", trace_activity.value_type ) self.assertEqual(True, hasattr(trace_activity, "value")) self.assertEqual(True, hasattr(trace_activity.value, "message")) self.assertEqual(True, hasattr(trace_activity.value, "query_results")) self.assertEqual(True, hasattr(trace_activity.value, "score_threshold")) self.assertEqual(True, hasattr(trace_activity.value, "top")) self.assertEqual(True, hasattr(trace_activity.value, "strict_filters")) self.assertEqual( self._knowledge_base_id, trace_activity.value.knowledge_base_id ) return result
def create_reply(activity): return Activity( type=ActivityTypes.message, from_property=ChannelAccount( id=activity.recipient.id, name=activity.recipient.name ), recipient=ChannelAccount( id=activity.from_property.id, name=activity.from_property.name ), reply_to_id=activity.id, service_url=activity.service_url, channel_id=activity.channel_id, conversation=ConversationAccount( is_group=activity.conversation.is_group, id=activity.conversation.id, name=activity.conversation.name, ), )
def create_reply(self, activity, text, locale=None): return Activity( type=ActivityTypes.message, from_property=ChannelAccount(id=activity.recipient.id, name=activity.recipient.name), recipient=ChannelAccount(id=activity.from_property.id, name=activity.from_property.name), reply_to_id=activity.id, service_url=activity.service_url, channel_id=activity.channel_id, conversation=ConversationAccount( is_group=activity.conversation.is_group, id=activity.conversation.id, name=activity.conversation.name, ), text=text or "", locale=locale or activity.locale, )
async def test_legacy_conversation_id_factory(self): mock_adapter = Mock() legacy_factory = LegacyConversationIdFactoryForTest() conversation_reference = ConversationReference( conversation=ConversationAccount(id=str(uuid4())), service_url="http://testbot.com/api/messages", ) conversation_id = await legacy_factory.create_skill_conversation_id( conversation_reference ) async def continue_conversation( reference: ConversationReference, callback: Callable, bot_id: str = None, claims_identity: ClaimsIdentity = None, audience: str = None, ): # pylint: disable=unused-argument # Invoke the callback created by the handler so we can assert the rest of the execution. turn_context = TurnContext( mock_adapter, conversation_reference_extension.get_continuation_activity( conversation_reference ), ) await callback(turn_context) async def send_activities( context: TurnContext, activities: List[Activity] ): # pylint: disable=unused-argument return [ResourceResponse(id="resourceId")] mock_adapter.continue_conversation = continue_conversation mock_adapter.send_activities = send_activities activity = Activity.create_message_activity() activity.apply_conversation_reference(conversation_reference) sut = self.create_skill_handler_for_testing(mock_adapter, legacy_factory) await sut.test_on_send_to_conversation( self._claims_identity, conversation_id, activity )
def _create_reply(self, activity) -> Activity: return Activity( type=ActivityTypes.message, timestamp=datetime.utcnow(), from_property=ChannelAccount(id=activity.recipient.id, name=activity.recipient.name), recipient=ChannelAccount(id=activity.from_property.id, name=activity.from_property.name), reply_to_id=activity.id, service_url=activity.service_url, channel_id=activity.channel_id, conversation=ConversationAccount( is_group=activity.conversation.is_group, id=activity.conversation.id, name=activity.conversation.name, ), text="", locale=activity.locale, )
def setUpClass(cls): cls.bot_id = str(uuid4()) cls.skill_id = str(uuid4()) cls._test_id_factory = ConversationIdFactoryForTest() cls._claims_identity = ClaimsIdentity({}, False) cls._claims_identity.claims[ AuthenticationConstants.AUDIENCE_CLAIM] = cls.bot_id cls._claims_identity.claims[ AuthenticationConstants.APP_ID_CLAIM] = cls.skill_id cls._claims_identity.claims[ AuthenticationConstants. SERVICE_URL_CLAIM] = "http://testbot.com/api/messages" cls._conversation_reference = ConversationReference( conversation=ConversationAccount(id=str(uuid4())), service_url="http://testbot.com/api/messages", )
async def test_trace_test(self): activity = Activity( type=ActivityTypes.message, text='how do I clean the stove?', conversation=ConversationAccount(), recipient=ChannelAccount(), from_property=ChannelAccount(), ) response_json = QnaApplicationTest._get_json_for_file( 'ReturnsAnswer.json') qna = QnAMaker(QnaApplicationTest.tests_endpoint) context = TestContext(activity) with patch('aiohttp.ClientSession.post', return_value=aiounittest.futurized(response_json)): result = await qna.get_answers(context) qna_trace_activities = list( filter( lambda act: act.type == 'trace' and act.name == 'QnAMaker', context.sent)) trace_activity = qna_trace_activities[0] self.assertEqual('trace', trace_activity.type) self.assertEqual('QnAMaker', trace_activity.name) self.assertEqual('QnAMaker Trace', trace_activity.label) self.assertEqual('https://www.qnamaker.ai/schemas/trace', trace_activity.value_type) self.assertEqual(True, hasattr(trace_activity, 'value')) self.assertEqual(True, hasattr(trace_activity.value, 'message')) self.assertEqual(True, hasattr(trace_activity.value, 'query_results')) self.assertEqual(True, hasattr(trace_activity.value, 'score_threshold')) self.assertEqual(True, hasattr(trace_activity.value, 'top')) self.assertEqual(True, hasattr(trace_activity.value, 'strict_filters')) self.assertEqual(self._knowledge_base_id, trace_activity.value.knowledge_base_id[0]) return result
def create_conversation_reference( name: str, user: str = "User1", bot: str = "Bot") -> ConversationReference: return ConversationReference( channel_id="test", service_url="https://test.com", conversation=ConversationAccount( is_group=False, conversation_type=name, id=name, ), user=ChannelAccount( id=user.lower(), name=user.lower(), ), bot=ChannelAccount( id=bot.lower(), name=bot.lower(), ), )
async def test_delivery_mode_normal(self): mock_credential_provider = unittest.mock.create_autospec(CredentialProvider) settings = BotFrameworkAdapterSettings( app_id="bot_id", credential_provider=mock_credential_provider ) adapter = AdapterUnderTest(settings) async def callback(context: TurnContext): await context.send_activity("activity 1") await context.send_activity("activity 2") await context.send_activity("activity 3") inbound_activity = Activity( type=ActivityTypes.message, channel_id="emulator", service_url="http://tempuri.org/whatever", delivery_mode=DeliveryModes.normal, text="hello world", conversation=ConversationAccount(id="conversationId"), ) identity = ClaimsIdentity( claims={ AuthenticationConstants.AUDIENCE_CLAIM: "bot_id", AuthenticationConstants.APP_ID_CLAIM: "bot_id", AuthenticationConstants.VERSION_CLAIM: "1.0", }, is_authenticated=True, ) invoke_response = await adapter.process_activity_with_identity( inbound_activity, identity, callback ) assert not invoke_response assert ( adapter.connector_client_mock.conversations.send_to_conversation.call_count == 3 )
async def send_activities( self, context: TurnContext, activities: List[Activity] ) -> List[ResourceResponse]: """ Send a message from the bot to the messaging API. :param context: A TurnContext representing the current incoming message and environment. :type context: :class:`botbuilder.core.TurnContext` :param activities: An array of outgoing activities to be sent back to the messaging API. :type activities: :class:`typing.List[Activity]` :return: An array of ResourceResponse objects containing the IDs that Slack assigned to the sent messages. :rtype: :class:`typing.List[ResourceResponse]` """ if not context: raise Exception("TurnContext is required") if not activities: raise Exception("List[Activity] is required") responses = [] for activity in activities: if activity.type == ActivityTypes.message: message = SlackHelper.activity_to_slack(activity) slack_response = await self.slack_client.post_message_to_slack(message) if slack_response and slack_response.status_code / 100 == 2: resource_response = ActivityResourceResponse( id=slack_response.data["ts"], activity_id=slack_response.data["ts"], conversation=ConversationAccount( id=slack_response.data["channel"] ), ) responses.append(resource_response) return responses
def __init__(self, reference: ConversationReference = None): #calling method ConsoleAdapter and initializes attributes of ConsoleAdapter class super(ConsoleAdapter, self).__init__() #setting paramaeters for ConversationReference. Setting ConversationReferences and paarameters = to self.reference #ConversationReference defines a particular point in a conversations self.reference = ConversationReference( channel_id='console', user=ChannelAccount(id='user', name='User1'), bot=ChannelAccount(id='bot', name='Bot'), conversation=ConversationAccount(id='convo1', name='', is_group=False), service_url='') # Warn users to pass in an instance of a ConversationReference, otherwise the parameter will be ignored. if reference is not None and not isinstance(reference, ConversationReference): warnings.warn( 'ConsoleAdapter: `reference` argument is not an instance of ConversationReference and will ' 'be ignored.') else: #getattr returns value of the named attribute of an object #sets self.reference attributes to initial values self.reference.channel_id = getattr(reference, 'channel_id', self.reference.channel_id) self.reference.user = getattr(reference, 'user', self.reference.user) self.reference.bot = getattr(reference, 'bot', self.reference.bot) self.reference.conversation = getattr(reference, 'conversation', self.reference.conversation) self.reference.service_url = getattr(reference, 'service_url', self.reference.service_url) # The only attribute on self.reference without an initial value is activity_id, so if reference does not # have a value for activity_id, default self.reference.activity_id to None self.reference.activity_id = getattr(reference, 'activity_id', None) # setting _next_id variable to zero self._next_id = 0
async def test_do_not_throw_on_null_from(self): telemetry = Mock() my_logger = TelemetryLoggerMiddleware(telemetry, False) adapter = TestAdapter(template_or_conversation=Activity( channel_id="test", recipient=ChannelAccount(id="bot", name="Bot"), conversation=ConversationAccount(id=str(uuid.uuid4())), )) adapter.use(my_logger) async def send_proactive(context: TurnContext): await context.send_activity("proactive") async def logic(context: TurnContext): await adapter.create_conversation( context.activity.channel_id, send_proactive, ) adapter.logic = logic test_flow = TestFlow(None, adapter) await test_flow.send("foo") await test_flow.assert_reply("proactive") telemetry_calls = [ ( TelemetryLoggerConstants.BOT_MSG_RECEIVE_EVENT, { "fromId": None, "conversationName": None, "locale": None, "recipientId": "bot", "recipientName": "Bot", }, ), ] self.assert_telemetry_calls(telemetry, telemetry_calls)
def create_activity_reply(activity: Activity, text: str = None, locale: str = None): return Activity(type=ActivityTypes.message, timestamp=datetime.utcnow(), from_property=ChannelAccount( id=getattr(activity.recipient, 'id', None), name=getattr(activity.recipient, 'name', None)), recipient=ChannelAccount(id=activity.from_property.id, name=activity.from_property.name), reply_to_id=activity.id, service_url=activity.service_url, channel_id=activity.channel_id, conversation=ConversationAccount( is_group=activity.conversation.is_group, id=activity.conversation.id, name=activity.conversation.name), text=text or '', locale=locale or '', attachments=[], entities=[])
async def test_clear_and_save(self): turn_context = TestUtilities.create_empty_context() turn_context.activity.conversation = ConversationAccount(id="1234") storage = MemoryStorage({}) # Turn 0 bot_state1 = ConversationState(storage) (await bot_state1.create_property("test-name").get( turn_context, lambda: TestPocoState() # pylint: disable=unnecessary-lambda )).value = "test-value" await bot_state1.save_changes(turn_context) # Turn 1 bot_state2 = ConversationState(storage) value1 = ( await bot_state2.create_property("test-name").get( turn_context, lambda: TestPocoState(value="default-value") # pylint: disable=unnecessary-lambda )).value assert value1 == "test-value" # Turn 2 bot_state3 = ConversationState(storage) await bot_state3.clear_state(turn_context) await bot_state3.save_changes(turn_context) # Turn 3 bot_state4 = ConversationState(storage) value2 = ( await bot_state4.create_property("test-name").get( turn_context, lambda: TestPocoState(value="default-value") # pylint: disable=unnecessary-lambda )).value assert value2, "default-value"
async def process_activity(channel_id: str, channel_data_tenant_id: str, conversation_tenant_id: str): activity = None mock_claims = unittest.mock.create_autospec(ClaimsIdentity) mock_credential_provider = unittest.mock.create_autospec( BotFrameworkAdapterSettings) sut = BotFrameworkAdapter(mock_credential_provider) async def aux_func(context): nonlocal activity activity = context.Activity await sut.process_activity( Activity( channel_id=channel_id, service_url="https://smba.trafficmanager.net/amer/", channel_data={"tenant": { "id": channel_data_tenant_id }}, conversation=ConversationAccount(tenant_id=conversation_tenant_id), ), mock_claims, aux_func) return activity
def __init__( self, logic: Coroutine = None, template_or_conversation: Union[Activity, ConversationReference] = None, send_trace_activities: bool = False, ): # pylint: disable=unused-argument """ Creates a new TestAdapter instance. :param logic: :param conversation: A reference to the conversation to begin the adapter state with. """ super(TestAdapter, self).__init__() self.logic = logic self._next_id: int = 0 self._user_tokens: List[UserToken] = [] self._magic_codes: List[TokenMagicCode] = [] self._conversation_lock = Lock() self.activity_buffer: List[Activity] = [] self.updated_activities: List[Activity] = [] self.deleted_activities: List[ConversationReference] = [] self.send_trace_activities = send_trace_activities self.template = ( template_or_conversation if isinstance(template_or_conversation, Activity) else Activity( channel_id="test", service_url="https://test.com", from_property=ChannelAccount(id="User1", name="user"), recipient=ChannelAccount(id="bot", name="Bot"), conversation=ConversationAccount(id="Convo1"), ) ) if isinstance(template_or_conversation, ConversationReference): self.template.channel_id = template_or_conversation.channel_id
def __init__(self, reference: ConversationReference = None): super(ConsoleAdapter, self).__init__() self.reference = ConversationReference(channel_id='console', user=ChannelAccount(id='user', name='User1'), bot=ChannelAccount(id='bot', name='Bot'), conversation=ConversationAccount(id='convo1', name='', is_group=False), service_url='') # Warn users to pass in an instance of a ConversationReference, otherwise the parameter will be ignored. if reference is not None and not isinstance(reference, ConversationReference): warnings.warn('ConsoleAdapter: `reference` argument is not an instance of ConversationReference and will ' 'be ignored.') else: self.reference.channel_id = getattr(reference, 'channel_id', self.reference.channel_id) self.reference.user = getattr(reference, 'user', self.reference.user) self.reference.bot = getattr(reference, 'bot', self.reference.bot) self.reference.conversation = getattr(reference, 'conversation', self.reference.conversation) self.reference.service_url = getattr(reference, 'service_url', self.reference.service_url) # The only attribute on self.reference without an initial value is activity_id, so if reference does not # have a value for activity_id, default self.reference.activity_id to None self.reference.activity_id = getattr(reference, 'activity_id', None) self._next_id = 0
async def process_activity(self, logic: Callable): """ Begins listening to console input. :param logic: :return: """ while True: msg = input() if msg is None: pass else: self._next_id += 1 activity = Activity(text=msg, channel_id='console', from_property=ChannelAccount(id='user', name='User1'), recipient=ChannelAccount(id='bot', name='Bot'), conversation=ConversationAccount(id='Convo1'), type=ActivityTypes.message, timestamp=datetime.datetime.now(), id=str(self._next_id)) activity = BotContext.apply_conversation_reference(activity, self.reference, True) context = BotContext(self, activity) await self.run_middleware(context, logic)
def __init__(self, logic: Coroutine=None, conversation: ConversationReference=None, send_trace_activity: bool = False): """ Creates a new TestAdapter instance. :param logic: :param conversation: A reference to the conversation to begin the adapter state with. """ super(TestAdapter, self).__init__() self.logic = logic self._next_id: int = 0 self.activity_buffer: List[Activity] = [] self.updated_activities: List[Activity] = [] self.deleted_activities: List[ConversationReference] = [] self.template: Activity = Activity( channel_id='test', service_url='https://test.com', from_property=ChannelAccount(id='User1', name='user'), recipient=ChannelAccount(id='bot', name='Bot'), conversation=ConversationAccount(id='Convo1') ) if self.template is not None: self.template.service_url = self.template.service_url self.template.conversation = self.template.conversation self.template.channel_id = self.template.channel_id
# Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. import pytest from botbuilder.schema import Activity, ChannelAccount, ResourceResponse, ConversationAccount from botbuilder.core import BotAdapter, BotContext ACTIVITY = Activity(id='1234', type='message', text='test', from_property=ChannelAccount(id='user', name='User Name'), recipient=ChannelAccount(id='bot', name='Bot Name'), conversation=ConversationAccount(id='convo', name='Convo Name'), channel_id='UnitTest', service_url='https://example.org') class SimpleAdapter(BotAdapter): async def send_activities(self, context, activities): responses = [] assert context is not None assert activities is not None assert type(activities) == list assert len(activities) > 0 for (idx, activity) in enumerate(activities): assert isinstance(activity, Activity) assert activity.type == 'message' responses.append(ResourceResponse(id='5678')) return responses
async def create_conversation( self, reference: ConversationReference, logic: Callable[[TurnContext], Awaitable] = None, conversation_parameters: ConversationParameters = None, ): """ Starts a new conversation with a user. Used to direct message to a member of a group. :param reference: The conversation reference that contains the tenant :type reference: :class:`botbuilder.schema.ConversationReference` :param logic: The logic to use for the creation of the conversation :type logic: :class:`typing.Callable` :param conversation_parameters: The information to use to create the conversation :type conversation_parameters: :raises: It raises a generic exception error. :return: A task representing the work queued to execute. .. remarks:: To start a conversation, your bot must know its account information and the user's account information on that channel. Most channels only support initiating a direct message (non-group) conversation. The adapter attempts to create a new conversation on the channel, and then sends a conversation update activity through its middleware pipeline to the the callback method. If the conversation is established with the specified users, the ID of the activity will contain the ID of the new conversation.</para> """ try: if reference.service_url is None: raise TypeError( "BotFrameworkAdapter.create_conversation(): reference.service_url cannot be None." ) # Create conversation parameters = (conversation_parameters if conversation_parameters else ConversationParameters(bot=reference.bot, members=[reference.user], is_group=False)) client = await self.create_connector_client(reference.service_url) # Mix in the tenant ID if specified. This is required for MS Teams. if reference.conversation is not None and reference.conversation.tenant_id: # Putting tenant_id in channel_data is a temporary while we wait for the Teams API to be updated parameters.channel_data = { "tenant": { "id": reference.conversation.tenant_id } } # Permanent solution is to put tenant_id in parameters.tenant_id parameters.tenant_id = reference.conversation.tenant_id resource_response = await client.conversations.create_conversation( parameters) request = TurnContext.apply_conversation_reference( Activity(type=ActivityTypes.event, name="CreateConversation"), reference, is_incoming=True, ) request.conversation = ConversationAccount( id=resource_response.id, tenant_id=parameters.tenant_id) request.channel_data = parameters.channel_data if resource_response.service_url: request.service_url = resource_response.service_url context = self.create_context(request) return await self.run_pipeline(context, logic) except Exception as error: raise error
ActivityTypes, ChannelAccount, ConversationAccount, Entity, Mention, ResourceResponse, ) from botbuilder.core import BotAdapter, MessageFactory, TurnContext ACTIVITY = Activity( id="1234", type="message", text="test", from_property=ChannelAccount(id="user", name="User Name"), recipient=ChannelAccount(id="bot", name="Bot Name"), conversation=ConversationAccount(id="convo", name="Convo Name"), channel_id="UnitTest", locale= "en-uS", # Intentionally oddly-cased to check that it isn't defaulted somewhere, but tests stay in English service_url="https://example.org", ) class SimpleAdapter(BotAdapter): async def send_activities(self, context, activities) -> List[ResourceResponse]: responses = [] assert context is not None assert activities is not None assert isinstance(activities, list) assert activities
# Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. import pytest from botbuilder.core import TurnContext, MemoryStorage, TestAdapter, ConversationState from botbuilder.schema import Activity, ConversationAccount RECEIVED_MESSAGE = Activity(type='message', text='received', channel_id='test', conversation=ConversationAccount(id='convo')) MISSING_CHANNEL_ID = Activity(type='message', text='received', conversation=ConversationAccount(id='convo')) MISSING_CONVERSATION = Activity(type='message', text='received', channel_id='test') END_OF_CONVERSATION = Activity(type='endOfConversation', channel_id='test', conversation=ConversationAccount(id='convo')) class TestConversationState: storage = MemoryStorage() adapter = TestAdapter() context = TurnContext(adapter, RECEIVED_MESSAGE) middleware = ConversationState(storage) @pytest.mark.asyncio async def test_should_load_and_save_state_from_storage(self):
async def test_post_activity_using_invoke_response(self): for is_gov in [True, False]: with self.subTest(is_government=is_gov): # pylint: disable=undefined-variable # pylint: disable=cell-var-from-loop conversation_id = str(uuid4()) conversation_id_factory = SimpleConversationIdFactory( conversation_id) test_activity = MessageFactory.text("some message") test_activity.conversation = ConversationAccount() expected_oauth_scope = ( AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE) mock_channel_provider: ChannelProvider = Mock( spec=ChannelProvider) def is_government_mock(): nonlocal expected_oauth_scope if is_government: expected_oauth_scope = ( GovernmentConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE ) return is_government mock_channel_provider.is_government = Mock( side_effect=is_government_mock) skill = BotFrameworkSkill( id="SomeSkill", app_id="", skill_endpoint="https://someskill.com/api/messages", ) async def _mock_post_content( to_url: str, token: str, # pylint: disable=unused-argument activity: Activity, ) -> (int, object): nonlocal self self.assertEqual(skill.skill_endpoint, to_url) # Assert that the activity being sent has what we expect. self.assertEqual(conversation_id, activity.conversation.id) self.assertEqual("https://parentbot.com/api/messages", activity.service_url) # Create mock response. return 200, None sut = await self._create_http_client_with_mock_handler( _mock_post_content, conversation_id_factory) result = await sut.post_activity_to_skill( "", skill, "https://parentbot.com/api/messages", test_activity) # Assert factory options self.assertEqual( "", conversation_id_factory.creation_options.from_bot_id) self.assertEqual( expected_oauth_scope, conversation_id_factory.creation_options. from_bot_oauth_scope, ) self.assertEqual( test_activity, conversation_id_factory.creation_options.activity) self.assertEqual( skill, conversation_id_factory.creation_options. bot_framework_skill) # Assert result self.assertIsInstance(result, InvokeResponse) self.assertEqual(200, result.status)
async def post_activity( self, from_bot_id: str, to_bot_id: str, to_url: str, service_url: str, conversation_id: str, activity: Activity, ) -> InvokeResponse: if not from_bot_id: raise TypeError("from_bot_id") if not to_bot_id: raise TypeError("to_bot_id") if not to_url: raise TypeError("to_url") if not service_url: raise TypeError("service_url") if not conversation_id: raise TypeError("conversation_id") if not activity: raise TypeError("activity") if self._logger: self._logger.log(20, f"post to skill '{to_bot_id}' at '{to_url}'") credentials = await self._credentials_factory.create_credentials( from_bot_id, to_bot_id, self._login_endpoint, True ) # Get token for the skill call token = credentials.get_access_token() if credentials.microsoft_app_id else None # Clone the activity so we can modify it before sending without impacting the original object. activity_copy = deepcopy(activity) # Apply the appropriate addressing to the newly created Activity. activity_copy.relates_to = ConversationReference( service_url=activity_copy.service_url, activity_id=activity_copy.id, channel_id=activity_copy.channel_id, conversation=ConversationAccount( id=activity_copy.conversation.id, name=activity_copy.conversation.name, conversation_type=activity_copy.conversation.conversation_type, aad_object_id=activity_copy.conversation.aad_object_id, is_group=activity_copy.conversation.is_group, role=activity_copy.conversation.role, tenant_id=activity_copy.conversation.tenant_id, properties=activity_copy.conversation.properties, ), bot=None, ) activity_copy.conversation.id = conversation_id activity_copy.service_url = service_url if not activity_copy.recipient: activity_copy.recipient = ChannelAccount(role=RoleTypes.skill) else: activity_copy.recipient.role = RoleTypes.skill headers_dict = { "Content-type": "application/json; charset=utf-8", } if token: headers_dict.update( {"Authorization": f"Bearer {token}",} ) json_content = dumps(activity_copy.serialize()).encode("utf-8") request = HttpRequest( request_uri=to_url, content=json_content, headers=headers_dict ) response = await self._http_client.post(request=request) data = await response.read_content_str() if not await response.is_succesful() and self._logger: # Otherwise we can assume we don't have to deserialize - so just log the content so it's not lost. self._logger.log( 40, f"Bot Framework call failed to '{to_url}' returning '{int(response.status_code)}' and '{data}'", ) return InvokeResponse( status=response.status_code, body=loads(data) if data else None )
async def post_activity( self, from_bot_id: str, to_bot_id: str, to_url: str, service_url: str, conversation_id: str, activity: Activity, ) -> InvokeResponse: app_credentials = await self._get_app_credentials( from_bot_id, to_bot_id) if not app_credentials: raise KeyError( "Unable to get appCredentials to connect to the skill") # Get token for the skill call token = (app_credentials.get_access_token() if app_credentials.microsoft_app_id else None) # Capture current activity settings before changing them. # TODO: DO we need to set the activity ID? (events that are created manually don't have it). original_conversation_id = activity.conversation.id original_service_url = activity.service_url original_caller_id = activity.caller_id original_relates_to = activity.relates_to try: # TODO: The relato has to be ported to the adapter in the new integration library when # resolving conflicts in merge activity.relates_to = ConversationReference( service_url=activity.service_url, activity_id=activity.id, channel_id=activity.channel_id, conversation=ConversationAccount( id=activity.conversation.id, name=activity.conversation.name, conversation_type=activity.conversation.conversation_type, aad_object_id=activity.conversation.aad_object_id, is_group=activity.conversation.is_group, role=activity.conversation.role, tenant_id=activity.conversation.tenant_id, properties=activity.conversation.properties, ), bot=None, ) activity.conversation.id = conversation_id activity.service_url = service_url activity.caller_id = f"urn:botframework:aadappid:{from_bot_id}" headers_dict = { "Content-type": "application/json; charset=utf-8", } if token: headers_dict.update({ "Authorization": f"Bearer {token}", }) json_content = json.dumps(activity.serialize()) resp = await self._session.post( to_url, data=json_content.encode("utf-8"), headers=headers_dict, ) resp.raise_for_status() data = (await resp.read()).decode() content = json.loads(data) if data else None return InvokeResponse(status=resp.status, body=content) finally: # Restore activity properties. activity.conversation.id = original_conversation_id activity.service_url = original_service_url activity.caller_id = original_caller_id activity.relates_to = original_relates_to
async def test_exchange_token_from_credentials(self): mock_credential_provider = unittest.mock.create_autospec(CredentialProvider) settings = BotFrameworkAdapterSettings( app_id="bot_id", credential_provider=mock_credential_provider, ) adapter = AdapterUnderTest(settings) identity = ClaimsIdentity( claims={ AuthenticationConstants.AUDIENCE_CLAIM: "bot_id", AuthenticationConstants.APP_ID_CLAIM: "bot_id", AuthenticationConstants.VERSION_CLAIM: "1.0", }, is_authenticated=True, ) inbound_activity = Activity( type=ActivityTypes.invoke, name=SignInConstants.token_exchange_operation_name, service_url="http://tempuri.org/whatever", conversation=ConversationAccount(id="conversationId"), value=TokenExchangeInvokeRequest( id="token_exchange_id", token="token", connection_name="connection_name", ), ) async def callback(context): result = await adapter.exchange_token_from_credentials( turn_context=context, oauth_app_credentials=None, connection_name=context.activity.value.connection_name, exchange_request=TokenExchangeRequest( token=context.activity.value.token, uri=context.activity.service_url ), user_id="user_id", ) activity = Activity( type=ActivityTypes.invoke_response, value=InvokeResponse( status=200, body=TokenExchangeInvokeResponse( id=context.activity.value.id, connection_name=result.connection_name, ), ), ) await context.send_activity(activity) invoke_response = await adapter.process_activity_with_identity( inbound_activity, identity, callback, ) assert invoke_response assert invoke_response.status == 200 assert invoke_response.body.id == inbound_activity.value.id assert ( invoke_response.body.connection_name == inbound_activity.value.connection_name )
async def create_test_flow( dialog: Dialog, test_case: SkillFlowTestCase = SkillFlowTestCase.root_bot_only, enabled_trace=False, ) -> TestAdapter: conversation_id = "testFlowConversationId" storage = MemoryStorage() conversation_state = ConversationState(storage) user_state = UserState(storage) activity = Activity( channel_id="test", service_url="https://test.com", from_property=ChannelAccount(id="user1", name="User1"), recipient=ChannelAccount(id="bot", name="Bot"), conversation=ConversationAccount(is_group=False, conversation_type=conversation_id, id=conversation_id), ) dialog_manager = DialogManager(dialog) dialog_manager.user_state = user_state dialog_manager.conversation_state = conversation_state async def logic(context: TurnContext): if test_case != SkillFlowTestCase.root_bot_only: # Create a skill ClaimsIdentity and put it in turn_state so isSkillClaim() returns True. claims_identity = ClaimsIdentity({}, False) claims_identity.claims[ "ver"] = "2.0" # AuthenticationConstants.VersionClaim claims_identity.claims["aud"] = ( SimpleComponentDialog.skill_bot_id ) # AuthenticationConstants.AudienceClaim claims_identity.claims["azp"] = ( SimpleComponentDialog.parent_bot_id ) # AuthenticationConstants.AuthorizedParty context.turn_state[ BotAdapter.BOT_IDENTITY_KEY] = claims_identity if test_case == SkillFlowTestCase.root_bot_consuming_skill: # Simulate the SkillConversationReference with a channel OAuthScope stored in turn_state. # This emulates a response coming to a root bot through SkillHandler. context.turn_state[ SkillHandler. SKILL_CONVERSATION_REFERENCE_KEY] = SkillConversationReference( None, AuthenticationConstants. TO_CHANNEL_FROM_BOT_OAUTH_SCOPE) if test_case == SkillFlowTestCase.middle_skill: # Simulate the SkillConversationReference with a parent Bot ID stored in turn_state. # This emulates a response coming to a skill from another skill through SkillHandler. context.turn_state[ SkillHandler. SKILL_CONVERSATION_REFERENCE_KEY] = SkillConversationReference( None, SimpleComponentDialog.parent_bot_id) async def aux( turn_context: TurnContext, # pylint: disable=unused-argument activities: List[Activity], next: Callable, ): for activity in activities: if activity.type == ActivityTypes.end_of_conversation: SimpleComponentDialog.eoc_sent = activity break return await next() # Interceptor to capture the EoC activity if it was sent so we can assert it in the tests. context.on_send_activities(aux) SimpleComponentDialog.dm_turn_result = await dialog_manager.on_turn( context) adapter = TestAdapter(logic, activity, enabled_trace) adapter.use(AutoSaveStateMiddleware([user_state, conversation_state])) return adapter
# Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. import aiounittest from botbuilder.core import TurnContext, MemoryStorage, ConversationState from botbuilder.core.adapters import TestAdapter from botbuilder.schema import Activity, ConversationAccount RECEIVED_MESSAGE = Activity( type="message", text="received", channel_id="test", conversation=ConversationAccount(id="convo"), ) MISSING_CHANNEL_ID = Activity(type="message", text="received", conversation=ConversationAccount(id="convo")) MISSING_CONVERSATION = Activity(type="message", text="received", channel_id="test") END_OF_CONVERSATION = Activity( type="endOfConversation", channel_id="test", conversation=ConversationAccount(id="convo"), ) class TestConversationState(aiounittest.AsyncTestCase): storage = MemoryStorage() adapter = TestAdapter()
AuthenticationConstants, AppCredentials, CredentialProvider, SimpleChannelProvider, GovernmentConstants, SimpleCredentialProvider, ) REFERENCE = ConversationReference( activity_id="1234", channel_id="test", locale="en-uS", # Intentionally oddly-cased to check that it isn't defaulted somewhere, but tests stay in English service_url="https://example.org/channel", user=ChannelAccount(id="user", name="User Name"), bot=ChannelAccount(id="bot", name="Bot Name"), conversation=ConversationAccount(id="convo1"), ) TEST_ACTIVITY = Activity(text="test", type=ActivityTypes.message) INCOMING_MESSAGE = TurnContext.apply_conversation_reference( copy(TEST_ACTIVITY), REFERENCE, True ) OUTGOING_MESSAGE = TurnContext.apply_conversation_reference( copy(TEST_ACTIVITY), REFERENCE ) INCOMING_INVOKE = TurnContext.apply_conversation_reference( Activity(type=ActivityTypes.invoke), REFERENCE, True )