def test_copy_to_should_copy_all_references(self): # pylint: disable=protected-access old_adapter = SimpleAdapter() old_activity = Activity(id="2", type="message", text="test copy") old_context = TurnContext(old_adapter, old_activity) old_context.responded = True async def send_activities_handler(context, activities, next_handler): assert context is not None assert activities is not None assert next_handler is not None await next_handler async def delete_activity_handler(context, reference, next_handler): assert context is not None assert reference is not None assert next_handler is not None await next_handler async def update_activity_handler(context, activity, next_handler): assert context is not None assert activity is not None assert next_handler is not None await next_handler old_context.on_send_activities(send_activities_handler) old_context.on_delete_activity(delete_activity_handler) old_context.on_update_activity(update_activity_handler) adapter = SimpleAdapter() new_context = TurnContext(adapter, ACTIVITY) assert not new_context._on_send_activities # pylint: disable=protected-access assert not new_context._on_update_activity # pylint: disable=protected-access assert not new_context._on_delete_activity # pylint: disable=protected-access old_context.copy_to(new_context) assert new_context.adapter == old_adapter assert new_context.activity == old_activity assert new_context.responded is True assert ( len(new_context._on_send_activities) == 1 ) # pylint: disable=protected-access assert ( len(new_context._on_update_activity) == 1 ) # pylint: disable=protected-access assert ( len(new_context._on_delete_activity) == 1 ) # pylint: disable=protected-access
async def continue_conversation( self, reference: ConversationReference, callback: Callable, bot_id: str = None, # pylint: disable=unused-argument claims_identity: ClaimsIdentity = None, audience: str = None, # pylint: disable=unused-argument ): """ Send a proactive message to a conversation. .. remarks:: Most channels require a user to initiate a conversation with a bot before the bot can send activities to the user. :param reference: A reference to the conversation to continue. :type reference: :class:`botbuilder.schema.ConversationReference` :param callback: The method to call for the resulting bot turn. :type callback: :class:`typing.Callable` :param bot_id: Unused for this override. :type bot_id: str :param claims_identity: A ClaimsIdentity for the conversation. :type claims_identity: :class:`botframework.connector.auth.ClaimsIdentity` :param audience: Unused for this override. :type audience: str """ if not reference: raise Exception("ConversationReference is required") if not callback: raise Exception("callback is required") if claims_identity: request = conversation_reference_extension.get_continuation_activity( reference) context = TurnContext(self, request) context.turn_state[BotAdapter.BOT_IDENTITY_KEY] = claims_identity context.turn_state[BotAdapter.BOT_CALLBACK_HANDLER_KEY] = callback else: request = TurnContext.apply_conversation_reference( conversation_reference_extension.get_continuation_activity( reference), reference, ) context = TurnContext(self, request) return await self.run_pipeline(context, callback)
async def test_healthcheck_with_connector(self): activity = Activity( type=ActivityTypes.invoke, name="healthcheck", ) adapter = TestInvokeAdapter() turn_context = TurnContext(adapter, activity) mock_connector_client = MockConnectorClient() turn_context.turn_state[ BotAdapter.BOT_CONNECTOR_CLIENT_KEY] = mock_connector_client bot = ActivityHandler() await bot.on_turn(turn_context) self.assertIsNotNone(adapter.activity) self.assertIsInstance(adapter.activity.value, InvokeResponse) self.assertEqual(adapter.activity.value.status, 200) response = HealthCheckResponse.deserialize(adapter.activity.value.body) self.assertTrue(response.health_results.success) self.assertEqual(response.health_results.authorization, "Bearer awesome") self.assertEqual(response.health_results.user_agent, USER_AGENT) self.assertTrue(response.health_results.messages) self.assertEqual(response.health_results.messages[0], "Health check succeeded.")
async def test_on_reply_to_activity(self): self._conversation_id = await self._test_id_factory.create_skill_conversation_id( self._conversation_reference) mock_adapter = Mock() mock_adapter.continue_conversation = MagicMock(return_value=Future()) mock_adapter.continue_conversation.return_value.set_result(Mock()) mock_adapter.send_activities = MagicMock(return_value=Future()) mock_adapter.send_activities.return_value.set_result([]) sut = self.create_skill_handler_for_testing(mock_adapter) activity = Activity(type=ActivityTypes.message, attachments=[], entities=[]) activity_id = str(uuid4()) TurnContext.apply_conversation_reference(activity, self._conversation_reference) await sut.test_on_reply_to_activity(self._claims_identity, self._conversation_id, activity_id, activity) args, kwargs = mock_adapter.continue_conversation.call_args_list[0] assert isinstance(args[0], ConversationReference) assert callable(args[1]) assert isinstance(kwargs["claims_identity"], ClaimsIdentity) await args[1](TurnContext( mock_adapter, conversation_reference_extension.get_continuation_activity( self._conversation_reference), )) assert activity.caller_id is None
async def test_should_update_activity(self): adapter = AdapterUnderTest() context = TurnContext(adapter, INCOMING_MESSAGE) self.assertTrue( await adapter.update_activity(context, INCOMING_MESSAGE), "Activity not updated.", )
async def inspector(activity: Activity, description: str = None): # pylint: disable=unused-argument self.assertTrue(len(activity.attachments) == 1) self.assertTrue( activity.attachments[0].content_type == CardFactory.content_types.oauth_card ) # send a mock EventActivity back to the bot with the token adapter.add_user_token( connection_name, activity.channel_id, activity.recipient.id, token ) event_activity = create_reply(activity) event_activity.type = ActivityTypes.event event_activity.from_property, event_activity.recipient = ( event_activity.recipient, event_activity.from_property, ) event_activity.name = "tokens/response" event_activity.value = TokenResponse( connection_name=connection_name, token=token ) context = TurnContext(adapter, event_activity) await callback_handler(context)
async def test_get_user_token_returns_token(self): adapter = TestAdapter() connection_name = "myConnection" channel_id = "directline" user_id = "testUser" token = "abc123" activity = Activity(channel_id=channel_id, from_property=ChannelAccount(id=user_id)) turn_context = TurnContext(adapter, activity) adapter.add_user_token(connection_name, channel_id, user_id, token) token_response = await adapter.get_user_token(turn_context, connection_name) assert token_response assert token == token_response.token assert connection_name == token_response.connection_name oauth_app_credentials = MicrosoftAppCredentials(None, None) token_response = await adapter.get_user_token( turn_context, connection_name, oauth_app_credentials=oauth_app_credentials) assert token_response assert token == token_response.token assert connection_name == token_response.connection_name
async def receive_activity(self, activity): """ INTERNAL: called by a `TestFlow` instance to simulate a user sending a message to the bot. This will cause the adapters middleware pipe to be run and it's logic to be called. :param activity: :return: """ if type(activity) == str: activity = Activity(type='message', text=activity) # Initialize request request = copy(self.template) for key, value in vars(activity).items(): if value is not None and key != 'additional_properties': setattr(request, key, value) if not request.type: request.type = ActivityTypes.message if not request.id: self._next_id += 1 request.id = str(self._next_id) # Create context object and run middleware context = TurnContext(self, request) return await self.run_middleware(context, self.logic)
async def test_should_add_and_call_load_all_on_multiple_plugins(self): adapter = TestAdapter() context = TurnContext(adapter, Activity()) foo_state = BotStateMock({"foo": "bar"}) bar_state = BotStateMock({"bar": "foo"}) bot_state_set = AutoSaveStateMiddleware([foo_state, bar_state]) await bot_state_set.bot_state_set.load_all(context)
async def __activity_callback_test(self, activity: Activity): self._conversation_id = await self._test_id_factory.create_skill_conversation_id( self._conversation_reference) mock_adapter = Mock() mock_adapter.continue_conversation = MagicMock(return_value=Future()) mock_adapter.continue_conversation.return_value.set_result(Mock()) mock_adapter.send_activities = MagicMock(return_value=Future()) mock_adapter.send_activities.return_value.set_result([]) sut = self.create_skill_handler_for_testing(mock_adapter) activity_id = str(uuid4()) TurnContext.apply_conversation_reference(activity, self._conversation_reference) await sut.test_on_reply_to_activity(self._claims_identity, self._conversation_id, activity_id, activity) args, kwargs = mock_adapter.continue_conversation.call_args_list[0] assert isinstance(args[0], ConversationReference) assert callable(args[1]) assert isinstance(kwargs["claims_identity"], ClaimsIdentity) await args[1](TurnContext(mock_adapter, activity))
async def test_cancelling_waterfall_telemetry(self): # Arrange dialog_id = "waterfall" index = 0 guid = "(guid)" async def my_waterfall_step(step) -> DialogTurnResult: await step.context.send_activity("step1 response") return Dialog.end_of_turn dialog = WaterfallDialog(dialog_id, [my_waterfall_step]) telemetry_client = create_autospec(NullTelemetryClient) dialog.telemetry_client = telemetry_client dialog_instance = DialogInstance() dialog_instance.id = dialog_id dialog_instance.state = {"instanceId": guid, "stepIndex": index} # Act await dialog.end_dialog( TurnContext(TestAdapter(), Activity()), dialog_instance, DialogReason.CancelCalled, ) # Assert telemetry_props = telemetry_client.track_event.call_args_list[0][0][1] self.assertEqual(3, len(telemetry_props)) self.assertEqual(dialog_id, telemetry_props["DialogId"]) self.assertEqual(my_waterfall_step.__qualname__, telemetry_props["StepName"]) self.assertEqual(guid, telemetry_props["InstanceId"]) telemetry_client.track_event.assert_called_once()
async def test_should_work_as_a_middleware_plugin(self): adapter = TestAdapter() context = TurnContext(adapter, Activity()) foo_state = BotStateMock({"foo": "bar"}) autosave_middleware = AutoSaveStateMiddleware().add(foo_state) await autosave_middleware.on_turn(context, aux_func) assert foo_state.write_called, "save_all_changes() not called."
async def test_should_fail_to_update_activity_if_activity_id_missing(self): adapter = AdapterUnderTest() context = TurnContext(adapter, INCOMING_MESSAGE) cpy = deepcopy(INCOMING_MESSAGE) cpy.id = None with self.assertRaises(Exception) as _: await adapter.update_activity(context, cpy)
async def test_should_call_multiple_on_delete_activity_handlers_in_order( self): context = TurnContext(SimpleAdapter(), ACTIVITY) called_first = False called_second = False async def first_delete_handler(context, reference, next_handler_coroutine): nonlocal called_first, called_second assert called_first is False, 'called_first should not be True before first_delete_handler is called.' called_first = True assert called_second is False, 'Second on_delete_activity handler was called before first.' assert reference is not None assert context is not None assert reference.activity_id == '1234' await next_handler_coroutine() async def second_delete_handler(context, reference, next_handler_coroutine): nonlocal called_first, called_second assert called_first assert called_second is False, 'called_second was set to True before second handler was called.' called_second = True assert reference is not None assert context is not None assert reference.activity_id == '1234' await next_handler_coroutine() context.on_delete_activity(first_delete_handler) context.on_delete_activity(second_delete_handler) await context.delete_activity(ACTIVITY.id) assert called_first is True assert called_second is True
def test_should_not_create_context_without_request(self): try: TurnContext(SimpleAdapter(), None) except TypeError: pass except Exception as error: raise error
def test_should_not_create_context_without_adapter(self): try: TurnContext(None, ACTIVITY) except TypeError: pass except Exception as error: raise error
async def test_on_teams_messaging_extension_configuration_query_settings_url( self): # Arrange activity = Activity( type=ActivityTypes.invoke, name="composeExtension/querySettingUrl", value={ "commandId": "test_command", "parameters": [], "messagingExtensionQueryOptions": { "skip": 1, "count": 1 }, "state": "state_string", }, ) turn_context = TurnContext(SimpleAdapter(), activity) # Act bot = TestingTeamsActivityHandler() await bot.on_turn(turn_context) # Assert assert len(bot.record) == 2 assert bot.record[0] == "on_invoke_activity" assert ( bot.record[1] == "on_teams_messaging_extension_configuration_query_settings_url")
async def test_on_teams_messaging_extension_bot_message_send_activity_with_empty_string( self, ): # Arrange activity = Activity( type=ActivityTypes.invoke, name="composeExtension/submitAction", value={ "data": { "key": "value" }, "context": { "theme": "dark" }, "commandId": "test_command", "commandContext": "command_context_test", "botMessagePreviewAction": "", "botActivityPreview": [Activity().serialize()], "messagePayload": MessageActionsPayload().serialize(), }, ) turn_context = TurnContext(SimpleAdapter(), activity) # Act bot = TestingTeamsActivityHandler() await bot.on_turn(turn_context) # Assert assert len(bot.record) == 3 assert bot.record[0] == "on_invoke_activity" assert bot.record[ 1] == "on_teams_messaging_extension_submit_action_dispatch" assert bot.record[2] == "on_teams_messaging_extension_submit_action"
async def test_on_teams_messaging_extension_fetch_task(self): # Arrange activity = Activity( type=ActivityTypes.invoke, name="composeExtension/fetchTask", value={ "data": { "key": "value" }, "context": { "theme": "dark" }, "commandId": "test_command", "commandContext": "command_context_test", "botMessagePreviewAction": "message_action", "botActivityPreview": [{ "id": "123" }], "messagePayload": { "id": "abc123" }, }, ) turn_context = TurnContext(SimpleAdapter(), activity) # Act bot = TestingTeamsActivityHandler() await bot.on_turn(turn_context) # Assert assert len(bot.record) == 2 assert bot.record[0] == "on_invoke_activity" assert bot.record[1] == "on_teams_messaging_extension_fetch_task"
async def continue_conversation( self, reference: ConversationReference, callback: Callable, bot_id: str = None, claims_identity: ClaimsIdentity = None, audience: str = None, ): """ Sends a proactive message to a conversation. Call this method to proactively send a message to a conversation. Most _channels require a user to initiate a conversation with a bot before the bot can send activities to the user. :param bot_id: The application ID of the bot. This parameter is ignored in single tenant the Adpters (Console, Test, etc) but is critical to the BotFrameworkAdapter which is multi-tenant aware. </param> :param reference: A reference to the conversation to continue.</param> :param callback: The method to call for the resulting bot turn.</param> :param claims_identity: """ if not reference: raise Exception("ConversationReference is required") if not callback: raise Exception("callback is required") request = TurnContext.apply_conversation_reference( conversation_reference_extension.get_continuation_activity( reference), reference, ) context = TurnContext(self, request) return await self.run_pipeline(context, callback)
async def test_on_teams_members_removed_activity(self): # arrange activity = Activity( type=ActivityTypes.conversation_update, channel_data={ "eventType": "teamMemberRemoved", "team": { "id": "team_id_1", "name": "new_team_name" }, }, members_removed=[ ChannelAccount( id="123", name="test_user", aad_object_id="asdfqwerty", role="tester", ) ], channel_id=Channels.ms_teams, ) turn_context = TurnContext(SimpleAdapter(), activity) # Act bot = TestingTeamsActivityHandler() await bot.on_turn(turn_context) # Assert assert len(bot.record) == 2 assert bot.record[0] == "on_conversation_update_activity" assert bot.record[1] == "on_teams_members_removed"
async def test_get_team_details_works_without_team_id(self): adapter = SimpleAdapterWithCreateConversation() ACTIVITY.channel_data = {} turn_context = TurnContext(adapter, ACTIVITY) result = TeamsInfo.get_team_id(turn_context) assert result == ""
async def test_should_pass_force_flag_through_in_load_all_call(self): adapter = TestAdapter() context = TurnContext(adapter, Activity()) foo_state = BotStateMock({"foo": "bar"}) foo_state.assert_force = True autosave_middleware = AutoSaveStateMiddleware().add(foo_state) await autosave_middleware.bot_state_set.load_all(context, True)
async def test_should_fail_to_update_activity_if_serviceUrl_missing(self): adapter = AdapterUnderTest() context = TurnContext(adapter, incoming_message) cpy = deepcopy(incoming_message) cpy.service_url = None with self.assertRaises(Exception) as _: await adapter.update_activity(context, cpy)
async def _intercept_oauth_cards(self, claims_identity: ClaimsIdentity, activity: Activity) -> bool: if activity.attachments: oauth_card_attachment = next( (attachment for attachment in activity.attachments if attachment.content_type == ContentTypes.oauth_card), None, ) if oauth_card_attachment: target_skill = self._get_calling_skill(claims_identity) if target_skill: oauth_card = oauth_card_attachment.content token_exchange_resource = oauth_card.get( "TokenExchangeResource") or oauth_card.get( "tokenExchangeResource") if token_exchange_resource: context = TurnContext(self._adapter, activity) context.turn_state["BotIdentity"] = claims_identity # We need to know what connection name to use for the token exchange so we figure that out here connection_name = ( self._configuration.SSO_CONNECTION_NAME if target_skill.group == "Waterfall" else self._configuration.SSO_CONNECTION_NAME_TEAMS) if not connection_name: raise ValueError( "The SSO connection name cannot be null.") # AAD token exchange try: uri = token_exchange_resource.get("uri") result = await self._token_exchange_provider.exchange_token( context, connection_name, activity.recipient.id, TokenExchangeRequest(uri=uri), ) if result.token: # If token above is null, then SSO has failed and hence we return false. # If not, send an invoke to the skill with the token. return await self._send_token_exchange_invoke_to_skill( incoming_activity=activity, connection_name=oauth_card.get( "connectionName"), resource_id=token_exchange_resource.get( "id"), token=result.token, target_skill=target_skill, ) except Exception as exception: print(f"Unable to exchange token: {exception}") traceback.print_exc() return False return False
async def test_should_add_and_call_save_all_changes_on_a_single_plugin( self): adapter = TestAdapter() context = TurnContext(adapter, Activity()) foo_state = BotStateMock({"foo": "bar"}) bot_state_set = AutoSaveStateMiddleware().add(foo_state) await bot_state_set.bot_state_set.save_all_changes(context) assert foo_state.write_called, "write not called for plugin."
def test_should_not_be_able_to_set_responded_to_false(self): context = TurnContext(SimpleAdapter(), ACTIVITY) try: context.responded = False except ValueError: pass except Exception as error: raise error
async def test_send_message_to_teams_channel_works(self): adapter = SimpleAdapterWithCreateConversation() turn_context = TurnContext(adapter, ACTIVITY) result = await TeamsInfo.send_message_to_teams_channel( turn_context, ACTIVITY, "teamId123") assert result[0].activity_id == "new_conversation_id" assert result[1] == "reference123"
async def test_get_team_details_works_with_team_id(self): adapter = SimpleAdapterWithCreateConversation() team_id = "teamId123" ACTIVITY.channel_data = {"team": {"id": team_id}} turn_context = TurnContext(adapter, ACTIVITY) result = TeamsInfo.get_team_id(turn_context) assert result == team_id
def _get_context(utterance: str, bot_adapter: BotAdapter) -> TurnContext: activity = Activity( type=ActivityTypes.message, text=utterance, conversation=ConversationAccount(), recipient=ChannelAccount(), from_property=ChannelAccount(), ) return TurnContext(bot_adapter, activity)