diff --git a/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py b/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py index 7ff3cf4ec..6ab5f3830 100644 --- a/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py +++ b/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py @@ -6,10 +6,16 @@ from botbuilder.core.turn_context import TurnContext from botbuilder.core import ActivityHandler, InvokeResponse, BotFrameworkAdapter from botbuilder.schema.teams import ( + AppBasedLinkQuery, TeamInfo, ChannelInfo, + FileConsentCardResponse, TeamsChannelData, TeamsChannelAccount, + MessagingExtensionAction, + MessagingExtensionQuery, + O365ConnectorCardActionQuery, + TaskModuleRequest, ) from botframework.connector import Channels @@ -55,26 +61,28 @@ async def on_invoke_activity(self, turn_context: TurnContext): if turn_context.activity.name == "fileConsent/invoke": return await self.on_teams_file_consent( - turn_context, turn_context.activity.value + turn_context, FileConsentCardResponse(**turn_context.activity.value) ) if turn_context.activity.name == "actionableMessage/executeAction": await self.on_teams_o365_connector_card_action( - turn_context, turn_context.activity.value + turn_context, + O365ConnectorCardActionQuery(**turn_context.activity.value), ) return self._create_invoke_response() if turn_context.activity.name == "composeExtension/queryLink": return self._create_invoke_response( await self.on_teams_app_based_link_query( - turn_context, turn_context.activity.value + turn_context, AppBasedLinkQuery(**turn_context.activity.value) ) ) if turn_context.activity.name == "composeExtension/query": return self._create_invoke_response( await self.on_teams_messaging_extension_query( - turn_context, turn_context.activity.value + turn_context, + MessagingExtensionQuery(**turn_context.activity.value), ) ) @@ -88,21 +96,24 @@ async def on_invoke_activity(self, turn_context: TurnContext): if turn_context.activity.name == "composeExtension/submitAction": return self._create_invoke_response( await self.on_teams_messaging_extension_submit_action_dispatch( - turn_context, turn_context.activity.value + turn_context, + MessagingExtensionAction(**turn_context.activity.value), ) ) if turn_context.activity.name == "composeExtension/fetchTask": return self._create_invoke_response( await self.on_teams_messaging_extension_fetch_task( - turn_context, turn_context.activity.value + turn_context, + MessagingExtensionAction(**turn_context.activity.value), ) ) if turn_context.activity.name == "composeExtension/querySettingUrl": return self._create_invoke_response( await self.on_teams_messaging_extension_configuration_query_settings_url( - turn_context, turn_context.activity.value + turn_context, + MessagingExtensionQuery(**turn_context.activity.value), ) ) @@ -121,14 +132,14 @@ async def on_invoke_activity(self, turn_context: TurnContext): if turn_context.activity.name == "task/fetch": return self._create_invoke_response( await self.on_teams_task_module_fetch( - turn_context, turn_context.activity.value + turn_context, TaskModuleRequest(**turn_context.activity.value) ) ) if turn_context.activity.name == "task/submit": return self._create_invoke_response( await self.on_teams_task_module_submit( - turn_context, turn_context.activity.value + turn_context, TaskModuleRequest(**turn_context.activity.value) ) ) @@ -143,7 +154,9 @@ async def on_teams_signin_verify_state(self, turn_context: TurnContext): raise _InvokeResponseException(status_code=HTTPStatus.NOT_IMPLEMENTED) async def on_teams_file_consent( - self, turn_context: TurnContext, file_consent_card_response + self, + turn_context: TurnContext, + file_consent_card_response: FileConsentCardResponse, ): if file_consent_card_response.action == "accept": await self.on_teams_file_consent_accept_activity( @@ -163,27 +176,31 @@ async def on_teams_file_consent( ) async def on_teams_file_consent_accept_activity( # pylint: disable=unused-argument - self, turn_context: TurnContext, file_consent_card_response + self, + turn_context: TurnContext, + file_consent_card_response: FileConsentCardResponse, ): raise _InvokeResponseException(status_code=HTTPStatus.NOT_IMPLEMENTED) async def on_teams_file_consent_decline_activity( # pylint: disable=unused-argument - self, turn_context: TurnContext, file_consent_card_response + self, + turn_context: TurnContext, + file_consent_card_response: FileConsentCardResponse, ): raise _InvokeResponseException(status_code=HTTPStatus.NOT_IMPLEMENTED) async def on_teams_o365_connector_card_action( # pylint: disable=unused-argument - self, turn_context: TurnContext, query + self, turn_context: TurnContext, query: O365ConnectorCardActionQuery ): raise _InvokeResponseException(status_code=HTTPStatus.NOT_IMPLEMENTED) async def on_teams_app_based_link_query( # pylint: disable=unused-argument - self, turn_context: TurnContext, query + self, turn_context: TurnContext, query: AppBasedLinkQuery ): raise _InvokeResponseException(status_code=HTTPStatus.NOT_IMPLEMENTED) async def on_teams_messaging_extension_query( # pylint: disable=unused-argument - self, turn_context: TurnContext, query + self, turn_context: TurnContext, query: MessagingExtensionQuery ): raise _InvokeResponseException(status_code=HTTPStatus.NOT_IMPLEMENTED) @@ -193,9 +210,9 @@ async def on_teams_messaging_extension_select_item( # pylint: disable=unused-ar raise _InvokeResponseException(status_code=HTTPStatus.NOT_IMPLEMENTED) async def on_teams_messaging_extension_submit_action_dispatch( - self, turn_context: TurnContext, action + self, turn_context: TurnContext, action: MessagingExtensionAction ): - if not action: + if not action.bot_message_preview_action: return await self.on_teams_messaging_extension_submit_action_activity( turn_context, action ) @@ -226,17 +243,17 @@ async def on_teams_messaging_extension_bot_message_send_activity( # pylint: dis raise _InvokeResponseException(status_code=HTTPStatus.NOT_IMPLEMENTED) async def on_teams_messaging_extension_submit_action_activity( # pylint: disable=unused-argument - self, turn_context: TurnContext, action + self, turn_context: TurnContext, action: MessagingExtensionAction ): raise _InvokeResponseException(status_code=HTTPStatus.NOT_IMPLEMENTED) async def on_teams_messaging_extension_fetch_task( # pylint: disable=unused-argument - self, turn_context: TurnContext, task_module_request + self, turn_context: TurnContext, action: MessagingExtensionAction ): raise _InvokeResponseException(status_code=HTTPStatus.NOT_IMPLEMENTED) async def on_teams_messaging_extension_configuration_query_settings_url( # pylint: disable=unused-argument - self, turn_context: TurnContext, query + self, turn_context: TurnContext, query: MessagingExtensionQuery ): raise _InvokeResponseException(status_code=HTTPStatus.NOT_IMPLEMENTED) @@ -251,19 +268,19 @@ async def on_teams_messaging_extension_card_button_clicked( # pylint: disable=u raise _InvokeResponseException(status_code=HTTPStatus.NOT_IMPLEMENTED) async def on_teams_task_module_fetch( # pylint: disable=unused-argument - self, turn_context: TurnContext, task_module_request + self, turn_context: TurnContext, task_module_request: TaskModuleRequest ): raise _InvokeResponseException(status_code=HTTPStatus.NOT_IMPLEMENTED) async def on_teams_task_module_submit( # pylint: disable=unused-argument - self, turn_context: TurnContext, task_module_request + self, turn_context: TurnContext, task_module_request: TaskModuleRequest ): raise _InvokeResponseException(status_code=HTTPStatus.NOT_IMPLEMENTED) async def on_conversation_update_activity(self, turn_context: TurnContext): + if turn_context.activity.channel_id == Channels.ms_teams: channel_data = TeamsChannelData(**turn_context.activity.channel_data) - if turn_context.activity.members_added: return await self.on_teams_members_added_dispatch_activity( turn_context.activity.members_added, channel_data.team, turn_context @@ -279,7 +296,9 @@ async def on_conversation_update_activity(self, turn_context: TurnContext): if channel_data: if channel_data.event_type == "channelCreated": return await self.on_teams_channel_created_activity( - channel_data.channel, channel_data.team, turn_context + ChannelInfo(**channel_data.channel), + channel_data.team, + turn_context, ) if channel_data.event_type == "channelDeleted": return await self.on_teams_channel_deleted_activity( @@ -337,17 +356,26 @@ async def on_teams_members_added_dispatch_activity( # pylint: disable=unused-ar return await self.on_teams_members_added_activity(teams_members_added, team_info, turn_context) """ + team_accounts_added = [] for member in members_added: new_account_json = member.serialize() - del new_account_json["additional_properties"] + if "additional_properties" in new_account_json: + del new_account_json["additional_properties"] member = TeamsChannelAccount(**new_account_json) - return await self.on_teams_members_added_activity(members_added, turn_context) + team_accounts_added.append(member) + return await self.on_teams_members_added_activity( + team_accounts_added, turn_context + ) async def on_teams_members_added_activity( self, teams_members_added: [TeamsChannelAccount], turn_context: TurnContext ): - teams_members_added = [ChannelAccount(member) for member in teams_members_added] - return super().on_members_added_activity(teams_members_added, turn_context) + teams_members_added = [ + ChannelAccount(**member.serialize()) for member in teams_members_added + ] + return await super().on_members_added_activity( + teams_members_added, turn_context + ) async def on_teams_members_removed_dispatch_activity( # pylint: disable=unused-argument self, @@ -358,7 +386,8 @@ async def on_teams_members_removed_dispatch_activity( # pylint: disable=unused- teams_members_removed = [] for member in members_removed: new_account_json = member.serialize() - del new_account_json["additional_properties"] + if "additional_properties" in new_account_json: + del new_account_json["additional_properties"] teams_members_removed.append(TeamsChannelAccount(**new_account_json)) return await self.on_teams_members_removed_activity( @@ -368,8 +397,10 @@ async def on_teams_members_removed_dispatch_activity( # pylint: disable=unused- async def on_teams_members_removed_activity( self, teams_members_removed: [TeamsChannelAccount], turn_context: TurnContext ): - members_removed = [ChannelAccount(member) for member in teams_members_removed] - return super().on_members_removed_activity(members_removed, turn_context) + members_removed = [ + ChannelAccount(**member.serialize()) for member in teams_members_removed + ] + return await super().on_members_removed_activity(members_removed, turn_context) async def on_teams_channel_deleted_activity( # pylint: disable=unused-argument self, channel_info: ChannelInfo, team_info: TeamInfo, turn_context: TurnContext diff --git a/libraries/botbuilder-core/tests/teams/simple_adapter.py b/libraries/botbuilder-core/tests/teams/simple_adapter.py new file mode 100644 index 000000000..a80fa29b3 --- /dev/null +++ b/libraries/botbuilder-core/tests/teams/simple_adapter.py @@ -0,0 +1,60 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import unittest +from typing import List +from botbuilder.core import BotAdapter, TurnContext +from botbuilder.schema import Activity, ConversationReference, ResourceResponse + + +class SimpleAdapter(BotAdapter): + # pylint: disable=unused-argument + + def __init__(self, call_on_send=None, call_on_update=None, call_on_delete=None): + super(SimpleAdapter, self).__init__() + self.test_aux = unittest.TestCase("__init__") + self._call_on_send = call_on_send + self._call_on_update = call_on_update + self._call_on_delete = call_on_delete + + async def delete_activity( + self, context: TurnContext, reference: ConversationReference + ): + self.test_aux.assertIsNotNone( + reference, "SimpleAdapter.delete_activity: missing reference" + ) + if self._call_on_delete is not None: + self._call_on_delete(reference) + + async def send_activities( + self, context: TurnContext, activities: List[Activity] + ) -> List[ResourceResponse]: + self.test_aux.assertIsNotNone( + activities, "SimpleAdapter.delete_activity: missing reference" + ) + self.test_aux.assertTrue( + len(activities) > 0, + "SimpleAdapter.send_activities: empty activities array.", + ) + + if self._call_on_send is not None: + self._call_on_send(activities) + responses = [] + + for activity in activities: + responses.append(ResourceResponse(id=activity.id)) + + return responses + + async def update_activity(self, context: TurnContext, activity: Activity): + self.test_aux.assertIsNotNone( + activity, "SimpleAdapter.update_activity: missing activity" + ) + if self._call_on_update is not None: + self._call_on_update(activity) + + return ResourceResponse(activity.id) + + async def process_request(self, activity, handler): + context = TurnContext(self, activity) + return self.run_pipeline(context, handler) diff --git a/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py b/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py index 87b092e09..540d5742b 100644 --- a/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py +++ b/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py @@ -8,46 +8,52 @@ ActivityTypes, ChannelAccount, ConversationReference, - MessageReaction, ResourceResponse, ) +from botbuilder.schema.teams import ( + AppBasedLinkQuery, + ChannelInfo, + FileConsentCardResponse, + MessageActionsPayload, + MessagingExtensionAction, + MessagingExtensionQuery, + O365ConnectorCardActionQuery, + TaskModuleRequest, + TaskModuleRequestContext, + TeamInfo, + TeamsChannelAccount, +) +from botframework.connector import Channels +from simple_adapter import SimpleAdapter class TestingTeamsActivityHandler(TeamsActivityHandler): def __init__(self): self.record: List[str] = [] - async def on_message_activity(self, turn_context: TurnContext): - self.record.append("on_message_activity") - return await super().on_message_activity(turn_context) - - async def on_members_added_activity( - self, members_added: ChannelAccount, turn_context: TurnContext - ): - self.record.append("on_members_added_activity") - return await super().on_members_added_activity(members_added, turn_context) + async def on_conversation_update_activity(self, turn_context: TurnContext): + self.record.append("on_conversation_update_activity") + return await super().on_conversation_update_activity(turn_context) - async def on_members_removed_activity( - self, members_removed: ChannelAccount, turn_context: TurnContext + async def on_teams_members_added_activity( + self, teams_members_added: [TeamsChannelAccount], turn_context: TurnContext ): - self.record.append("on_members_removed_activity") - return await super().on_members_removed_activity(members_removed, turn_context) - - async def on_message_reaction_activity(self, turn_context: TurnContext): - self.record.append("on_message_reaction_activity") - return await super().on_message_reaction_activity(turn_context) + self.record.append("on_teams_members_added_activity") + return await super().on_teams_members_added_activity( + teams_members_added, turn_context + ) - async def on_reactions_added( - self, message_reactions: List[MessageReaction], turn_context: TurnContext + async def on_teams_members_removed_activity( + self, teams_members_removed: [TeamsChannelAccount], turn_context: TurnContext ): - self.record.append("on_reactions_added") - return await super().on_reactions_added(message_reactions, turn_context) + self.record.append("on_teams_members_removed_activity") + return await super().on_teams_members_removed_activity( + teams_members_removed, turn_context + ) - async def on_reactions_removed( - self, message_reactions: List[MessageReaction], turn_context: TurnContext - ): - self.record.append("on_reactions_removed") - return await super().on_reactions_removed(message_reactions, turn_context) + async def on_message_activity(self, turn_context: TurnContext): + self.record.append("on_message_activity") + return await super().on_message_activity(turn_context) async def on_token_response_event(self, turn_context: TurnContext): self.record.append("on_token_response_event") @@ -61,6 +67,176 @@ async def on_unrecognized_activity_type(self, turn_context: TurnContext): self.record.append("on_unrecognized_activity_type") return await super().on_unrecognized_activity_type(turn_context) + async def on_teams_channel_created_activity( + self, channel_info: ChannelInfo, team_info: TeamInfo, turn_context: TurnContext + ): + self.record.append("on_teams_channel_created_activity") + return await super().on_teams_channel_created_activity( + channel_info, team_info, turn_context + ) + + async def on_teams_channel_renamed_activity( + self, channel_info: ChannelInfo, team_info: TeamInfo, turn_context: TurnContext + ): + self.record.append("on_teams_channel_renamed_activity") + return await super().on_teams_channel_renamed_activity( + channel_info, team_info, turn_context + ) + + async def on_teams_channel_deleted_activity( + self, channel_info: ChannelInfo, team_info: TeamInfo, turn_context: TurnContext + ): + self.record.append("on_teams_channel_deleted_activity") + return await super().on_teams_channel_renamed_activity( + channel_info, team_info, turn_context + ) + + async def on_teams_team_renamed_activity( + self, team_info: TeamInfo, turn_context: TurnContext + ): + self.record.append("on_teams_team_renamed_activity") + return await super().on_teams_team_renamed_activity(team_info, turn_context) + + async def on_invoke_activity(self, turn_context: TurnContext): + self.record.append("on_invoke_activity") + return await super().on_invoke_activity(turn_context) + + async def on_teams_signin_verify_state(self, turn_context: TurnContext): + self.record.append("on_teams_signin_verify_state") + return await super().on_teams_signin_verify_state(turn_context) + + async def on_teams_file_consent( + self, + turn_context: TurnContext, + file_consent_card_response: FileConsentCardResponse, + ): + self.record.append("on_teams_file_consent") + return await super().on_teams_file_consent( + turn_context, file_consent_card_response + ) + + async def on_teams_file_consent_accept_activity( + self, + turn_context: TurnContext, + file_consent_card_response: FileConsentCardResponse, + ): + self.record.append("on_teams_file_consent_accept_activity") + return await super().on_teams_file_consent_accept_activity( + turn_context, file_consent_card_response + ) + + async def on_teams_file_consent_decline_activity( + self, + turn_context: TurnContext, + file_consent_card_response: FileConsentCardResponse, + ): + self.record.append("on_teams_file_consent_decline_activity") + return await super().on_teams_file_consent_decline_activity( + turn_context, file_consent_card_response + ) + + async def on_teams_o365_connector_card_action( + self, turn_context: TurnContext, query: O365ConnectorCardActionQuery + ): + self.record.append("on_teams_o365_connector_card_action") + return await super().on_teams_o365_connector_card_action(turn_context, query) + + async def on_teams_app_based_link_query( + self, turn_context: TurnContext, query: AppBasedLinkQuery + ): + self.record.append("on_teams_app_based_link_query") + return await super().on_teams_app_based_link_query(turn_context, query) + + async def on_teams_messaging_extension_query( + self, turn_context: TurnContext, query: MessagingExtensionQuery + ): + self.record.append("on_teams_messaging_extension_query") + return await super().on_teams_messaging_extension_query(turn_context, query) + + async def on_teams_messaging_extension_submit_action_dispatch( + self, turn_context: TurnContext, action: MessagingExtensionAction + ): + self.record.append("on_teams_messaging_extension_submit_action_dispatch") + return await super().on_teams_messaging_extension_submit_action_dispatch( + turn_context, action + ) + + async def on_teams_messaging_extension_submit_action_activity( + self, turn_context: TurnContext, action: MessagingExtensionAction + ): + self.record.append("on_teams_messaging_extension_submit_action_activity") + return await super().on_teams_messaging_extension_submit_action_activity( + turn_context, action + ) + + async def on_teams_messaging_extension_bot_message_preview_edit_activity( + self, turn_context: TurnContext, action: MessagingExtensionAction + ): + self.record.append( + "on_teams_messaging_extension_bot_message_preview_edit_activity" + ) + return await super().on_teams_messaging_extension_bot_message_preview_edit_activity( + turn_context, action + ) + + async def on_teams_messaging_extension_bot_message_send_activity( + self, turn_context: TurnContext, action: MessagingExtensionAction + ): + self.record.append("on_teams_messaging_extension_bot_message_send_activity") + return await super().on_teams_messaging_extension_bot_message_send_activity( + turn_context, action + ) + + async def on_teams_messaging_extension_fetch_task( + self, turn_context: TurnContext, action: MessagingExtensionAction + ): + self.record.append("on_teams_messaging_extension_fetch_task") + return await super().on_teams_messaging_extension_fetch_task( + turn_context, action + ) + + async def on_teams_messaging_extension_configuration_query_settings_url( + self, turn_context: TurnContext, query: MessagingExtensionQuery + ): + self.record.append( + "on_teams_messaging_extension_configuration_query_settings_url" + ) + return await super().on_teams_messaging_extension_configuration_query_settings_url( + turn_context, query + ) + + async def on_teams_messaging_extension_configuration_setting( + self, turn_context: TurnContext, settings + ): + self.record.append("on_teams_messaging_extension_configuration_setting") + return await super().on_teams_messaging_extension_configuration_setting( + turn_context, settings + ) + + async def on_teams_messaging_extension_card_button_clicked( + self, turn_context: TurnContext, card_data + ): + self.record.append("on_teams_messaging_extension_card_button_clicked") + return await super().on_teams_messaging_extension_card_button_clicked( + turn_context, card_data + ) + + async def on_teams_task_module_fetch( + self, turn_context: TurnContext, task_module_request + ): + self.record.append("on_teams_task_module_fetch") + return await super().on_teams_task_module_fetch( + turn_context, task_module_request + ) + + async def on_teams_task_module_submit( # pylint: disable=unused-argument + self, turn_context: TurnContext, task_module_request: TaskModuleRequest + ): + self.record.append("on_teams_task_module_submit") + return await super().on_teams_task_module_submit( + turn_context, task_module_request + ) + class NotImplementedAdapter(BotAdapter): async def delete_activity( @@ -78,16 +254,83 @@ async def update_activity(self, context: TurnContext, activity: Activity): class TestTeamsActivityHandler(aiounittest.AsyncTestCase): - async def test_message_reaction(self): - # Note the code supports multiple adds and removes in the same activity though - # a channel may decide to send separate activities for each. For example, Teams - # sends separate activities each with a single add and a single remove. + async def test_on_teams_channel_created_activity(self): + # arrange + activity = Activity( + type=ActivityTypes.conversation_update, + channel_data={ + "eventType": "channelCreated", + "channel": {"id": "asdfqwerty", "name": "new_channel"}, + }, + channel_id=Channels.ms_teams, + ) - # Arrange + turn_context = TurnContext(NotImplementedAdapter(), 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_channel_created_activity" + + async def test_on_teams_channel_renamed_activity(self): + # arrange + activity = Activity( + type=ActivityTypes.conversation_update, + channel_data={ + "eventType": "channelRenamed", + "channel": {"id": "asdfqwerty", "name": "new_channel"}, + }, + channel_id=Channels.ms_teams, + ) + + turn_context = TurnContext(NotImplementedAdapter(), 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_channel_renamed_activity" + + async def test_on_teams_channel_deleted_activity(self): + # arrange + activity = Activity( + type=ActivityTypes.conversation_update, + channel_data={ + "eventType": "channelDeleted", + "channel": {"id": "asdfqwerty", "name": "new_channel"}, + }, + channel_id=Channels.ms_teams, + ) + + turn_context = TurnContext(NotImplementedAdapter(), 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_channel_deleted_activity" + + async def test_on_teams_team_renamed_activity(self): + # arrange activity = Activity( - type=ActivityTypes.message_reaction, - reactions_added=[MessageReaction(type="sad")], + type=ActivityTypes.conversation_update, + channel_data={ + "eventType": "teamRenamed", + "team": {"id": "team_id_1", "name": "new_team_name"}, + }, + channel_id=Channels.ms_teams, ) + turn_context = TurnContext(NotImplementedAdapter(), activity) # Act @@ -96,5 +339,426 @@ async def test_message_reaction(self): # Assert assert len(bot.record) == 2 - assert bot.record[0] == "on_message_reaction_activity" - assert bot.record[1] == "on_reactions_added" + assert bot.record[0] == "on_conversation_update_activity" + assert bot.record[1] == "on_teams_team_renamed_activity" + + async def test_on_teams_members_added_activity(self): + # arrange + activity = Activity( + type=ActivityTypes.conversation_update, + channel_data={"eventType": "teamMemberAdded"}, + members_added=[ + ChannelAccount( + id="123", + name="test_user", + aad_object_id="asdfqwerty", + role="tester", + ) + ], + channel_id=Channels.ms_teams, + ) + + turn_context = TurnContext(NotImplementedAdapter(), 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_added_activity" + + async def test_on_teams_members_removed_activity(self): + # arrange + activity = Activity( + type=ActivityTypes.conversation_update, + channel_data={"eventType": "teamMemberRemoved"}, + members_removed=[ + ChannelAccount( + id="123", + name="test_user", + aad_object_id="asdfqwerty", + role="tester", + ) + ], + channel_id=Channels.ms_teams, + ) + + turn_context = TurnContext(NotImplementedAdapter(), 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_activity" + + async def test_on_signin_verify_state(self): + # arrange + activity = Activity(type=ActivityTypes.invoke, name="signin/verifyState") + + 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_signin_verify_state" + + async def test_on_file_consent_accept_activity(self): + # arrange + activity = Activity( + type=ActivityTypes.invoke, + name="fileConsent/invoke", + value={"action": "accept"}, + ) + + 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_file_consent" + assert bot.record[2] == "on_teams_file_consent_accept_activity" + + async def test_on_file_consent_decline_activity(self): + # Arrange + activity = Activity( + type=ActivityTypes.invoke, + name="fileConsent/invoke", + value={"action": "decline"}, + ) + + 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_file_consent" + assert bot.record[2] == "on_teams_file_consent_decline_activity" + + async def test_on_file_consent_bad_action_activity(self): + # Arrange + activity = Activity( + type=ActivityTypes.invoke, + name="fileConsent/invoke", + value={"action": "bad_action"}, + ) + + 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_file_consent" + + async def test_on_teams_o365_connector_card_action(self): + # arrange + activity = Activity( + type=ActivityTypes.invoke, + name="actionableMessage/executeAction", + value={"body": "body_here", "actionId": "action_id_here"}, + ) + + 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_o365_connector_card_action" + + async def test_on_app_based_link_query(self): + # arrange + activity = Activity( + type=ActivityTypes.invoke, + name="composeExtension/query", + value={"url": "http://www.test.com"}, + ) + + 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_query" + + async def test_on_teams_messaging_extension_bot_message_preview_edit_activity(self): + # Arrange + activity = Activity( + type=ActivityTypes.invoke, + name="composeExtension/submitAction", + value={ + "data": {"key": "value"}, + "context": {"theme": "dark"}, + "comamndId": "test_command", + "commandContext": "command_context_test", + "botMessagePreviewAction": "edit", + "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_bot_message_preview_edit_activity" + ) + + async def test_on_teams_messaging_extension_bot_message_send_activity(self): + # Arrange + activity = Activity( + type=ActivityTypes.invoke, + name="composeExtension/submitAction", + value={ + "data": {"key": "value"}, + "context": {"theme": "dark"}, + "comamndId": "test_command", + "commandContext": "command_context_test", + "botMessagePreviewAction": "send", + "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_bot_message_send_activity" + + async def test_on_teams_messaging_extension_bot_message_send_activity_with_none( + self, + ): + # Arrange + activity = Activity( + type=ActivityTypes.invoke, + name="composeExtension/submitAction", + value={ + "data": {"key": "value"}, + "context": {"theme": "dark"}, + "comamndId": "test_command", + "commandContext": "command_context_test", + "botMessagePreviewAction": None, + "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_activity" + + 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"}, + "comamndId": "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_activity" + + 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"}, + "comamndId": "test_command", + "commandContext": "command_context_test", + "botMessagePreviewAction": "message_action", + "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) == 2 + assert bot.record[0] == "on_invoke_activity" + assert bot.record[1] == "on_teams_messaging_extension_fetch_task" + + async def test_on_teams_messaging_extension_configuration_query_settings_url(self): + # Arrange + activity = Activity( + type=ActivityTypes.invoke, + name="composeExtension/querySettingUrl", + value={ + "comamndId": "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_configuration_setting(self): + # Arrange + activity = Activity( + type=ActivityTypes.invoke, + name="composeExtension/setting", + value={"key": "value"}, + ) + + 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_setting" + + async def test_on_teams_messaging_extension_card_button_clicked(self): + # Arrange + activity = Activity( + type=ActivityTypes.invoke, + name="composeExtension/onCardButtonClicked", + value={"key": "value"}, + ) + + 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_card_button_clicked" + + async def test_on_teams_task_module_fetch(self): + # Arrange + activity = Activity( + type=ActivityTypes.invoke, + name="task/fetch", + value={ + "data": {"key": "value"}, + "context": TaskModuleRequestContext().serialize(), + }, + ) + + 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_task_module_fetch" + + async def test_on_teams_task_module_submit(self): + # Arrange + activity = Activity( + type=ActivityTypes.invoke, + name="task/submit", + value={ + "data": {"key": "value"}, + "context": TaskModuleRequestContext().serialize(), + }, + ) + + 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_task_module_submit" diff --git a/libraries/botbuilder-schema/botbuilder/schema/_models.py b/libraries/botbuilder-schema/botbuilder/schema/_models.py index 736ddcf81..9574df14a 100644 --- a/libraries/botbuilder-schema/botbuilder/schema/_models.py +++ b/libraries/botbuilder-schema/botbuilder/schema/_models.py @@ -611,7 +611,7 @@ def __init__(self, **kwargs): super(ChannelAccount, self).__init__(**kwargs) self.id = kwargs.get("id", None) self.name = kwargs.get("name", None) - self.aad_object_id = kwargs.get("aad_object_id", None) + self.aad_object_id = kwargs.get("aadObjectId", None) self.role = kwargs.get("role", None) diff --git a/libraries/botbuilder-schema/botbuilder/schema/teams/_models.py b/libraries/botbuilder-schema/botbuilder/schema/teams/_models.py index 5e41c6fd4..e80cee5f9 100644 --- a/libraries/botbuilder-schema/botbuilder/schema/teams/_models.py +++ b/libraries/botbuilder-schema/botbuilder/schema/teams/_models.py @@ -559,7 +559,7 @@ def __init__(self, **kwargs): super(MessagingExtensionAction, self).__init__(**kwargs) self.command_id = kwargs.get("command_id", None) self.command_context = kwargs.get("command_context", None) - self.bot_message_preview_action = kwargs.get("bot_message_preview_action", None) + self.bot_message_preview_action = kwargs.get("botMessagePreviewAction", None) self.bot_activity_preview = kwargs.get("bot_activity_preview", None) self.message_payload = kwargs.get("message_payload", None) @@ -910,7 +910,8 @@ class O365ConnectorCardActionQuery(Model): def __init__(self, **kwargs): super(O365ConnectorCardActionQuery, self).__init__(**kwargs) self.body = kwargs.get("body", None) - self.action_id = kwargs.get("action_id", None) + # This is how it comes in from Teams + self.action_id = kwargs.get("actionId", None) class O365ConnectorCardDateInput(O365ConnectorCardInputBase): @@ -1535,7 +1536,7 @@ class TeamsChannelAccount(ChannelAccount): "given_name": {"key": "givenName", "type": "str"}, "surname": {"key": "surname", "type": "str"}, "email": {"key": "email", "type": "str"}, - "user_principal_name": {"key": "userPrincipalName", "type": "str"}, + "userPrincipalName": {"key": "userPrincipalName", "type": "str"}, } def __init__(self, **kwargs): @@ -1543,7 +1544,7 @@ def __init__(self, **kwargs): self.given_name = kwargs.get("given_name", None) self.surname = kwargs.get("surname", None) self.email = kwargs.get("email", None) - self.user_principal_name = kwargs.get("user_principal_name", None) + self.user_principal_name = kwargs.get("userPrincipalName", None) class TeamsChannelData(Model): @@ -1573,7 +1574,8 @@ class TeamsChannelData(Model): def __init__(self, **kwargs): super(TeamsChannelData, self).__init__(**kwargs) self.channel = kwargs.get("channel", None) - self.event_type = kwargs.get("event_type", None) + # doing camel case here since that's how the data comes in + self.event_type = kwargs.get("eventType", None) self.team = kwargs.get("team", None) self.notification = kwargs.get("notification", None) self.tenant = kwargs.get("tenant", None) diff --git a/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py b/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py index 80249f277..0f3a075a6 100644 --- a/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py +++ b/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py @@ -670,7 +670,7 @@ def __init__( context=None, command_id: str = None, command_context=None, - bot_message_preview_action=None, + botMessagePreviewAction=None, bot_activity_preview=None, message_payload=None, **kwargs @@ -680,7 +680,7 @@ def __init__( ) self.command_id = command_id self.command_context = command_context - self.bot_message_preview_action = bot_message_preview_action + self.bot_message_preview_action = botMessagePreviewAction self.bot_activity_preview = bot_activity_preview self.message_payload = message_payload @@ -1129,10 +1129,11 @@ class O365ConnectorCardActionQuery(Model): "action_id": {"key": "actionId", "type": "str"}, } - def __init__(self, *, body: str = None, action_id: str = None, **kwargs) -> None: + def __init__(self, *, body: str = None, actionId: str = None, **kwargs) -> None: super(O365ConnectorCardActionQuery, self).__init__(**kwargs) self.body = body - self.action_id = action_id + # This is how it comes in from Teams + self.action_id = actionId class O365ConnectorCardDateInput(O365ConnectorCardInputBase): @@ -1814,6 +1815,7 @@ def __init__( self.given_name = given_name self.surname = surname self.email = email + # changing to camel case due to how data comes in off the wire self.user_principal_name = user_principal_name @@ -1835,7 +1837,7 @@ class TeamsChannelData(Model): _attribute_map = { "channel": {"key": "channel", "type": "ChannelInfo"}, - "event_type": {"key": "eventType", "type": "str"}, + "eventType": {"key": "eventType", "type": "str"}, "team": {"key": "team", "type": "TeamInfo"}, "notification": {"key": "notification", "type": "NotificationInfo"}, "tenant": {"key": "tenant", "type": "TenantInfo"}, @@ -1845,7 +1847,7 @@ def __init__( self, *, channel=None, - event_type: str = None, + eventType: str = None, team=None, notification=None, tenant=None, @@ -1853,7 +1855,8 @@ def __init__( ) -> None: super(TeamsChannelData, self).__init__(**kwargs) self.channel = channel - self.event_type = event_type + # doing camel case here since that's how the data comes in + self.event_type = eventType self.team = team self.notification = notification self.tenant = tenant