Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
ConversationReference,
TokenResponse,
ResourceResponse,
DeliveryModes,
)

from . import __version__
Expand Down Expand Up @@ -455,6 +456,8 @@ async def process_activity_with_identity(
if invoke_response is None:
return InvokeResponse(status=501)
return invoke_response.value
if context.activity.delivery_mode == DeliveryModes.buffered_replies:
return InvokeResponse(status=200, body=context.buffered_replies)

return None

Expand Down
18 changes: 18 additions & 0 deletions libraries/botbuilder-core/botbuilder/core/turn_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
InputHints,
Mention,
ResourceResponse,
DeliveryModes,
)
from .re_escape import escape

Expand Down Expand Up @@ -50,6 +51,9 @@ def __init__(self, adapter_or_context, request: Activity = None):

self._turn_state = {}

# A list of activities to send when `context.Activity.DeliveryMode == 'bufferedReplies'`
self.buffered_replies = []

@property
def turn_state(self) -> Dict[str, object]:
return self._turn_state
Expand Down Expand Up @@ -190,7 +194,21 @@ def activity_validator(activity: Activity) -> Activity:
for act in activities
]

# send activities through adapter
async def logic():
nonlocal sent_non_trace_activity

if self.activity.delivery_mode == DeliveryModes.buffered_replies:
responses = []
for activity in output:
self.buffered_replies.append(activity)
responses.append(ResourceResponse())

if sent_non_trace_activity:
self.responded = True

return responses

responses = await self.adapter.send_activities(self, output)
if sent_non_trace_activity:
self.responded = True
Expand Down
104 changes: 97 additions & 7 deletions libraries/botbuilder-core/tests/test_bot_framework_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
ConversationReference,
ConversationResourceResponse,
ChannelAccount,
DeliveryModes,
)
from botframework.connector.aio import ConnectorClient
from botframework.connector.auth import (
Expand Down Expand Up @@ -58,6 +59,7 @@ def __init__(self, settings=None):
self.fail_operation = False
self.expect_auth_header = ""
self.new_service_url = None
self.connector_client_mock = None

def aux_test_authenticate_request(self, request: Activity, auth_header: str):
return super()._authenticate_request(request, auth_header)
Expand Down Expand Up @@ -102,7 +104,10 @@ def _get_or_create_connector_client(
self.tester.assertIsNotNone(
service_url, "create_connector_client() not passed service_url."
)
connector_client_mock = Mock()

if self.connector_client_mock:
return self.connector_client_mock
self.connector_client_mock = Mock()

async def mock_reply_to_activity(conversation_id, activity_id, activity):
nonlocal self
Expand Down Expand Up @@ -160,23 +165,23 @@ async def mock_create_conversation(parameters):
)
return response

connector_client_mock.conversations.reply_to_activity.side_effect = (
self.connector_client_mock.conversations.reply_to_activity.side_effect = (
mock_reply_to_activity
)
connector_client_mock.conversations.send_to_conversation.side_effect = (
self.connector_client_mock.conversations.send_to_conversation.side_effect = (
mock_send_to_conversation
)
connector_client_mock.conversations.update_activity.side_effect = (
self.connector_client_mock.conversations.update_activity.side_effect = (
mock_update_activity
)
connector_client_mock.conversations.delete_activity.side_effect = (
self.connector_client_mock.conversations.delete_activity.side_effect = (
mock_delete_activity
)
connector_client_mock.conversations.create_conversation.side_effect = (
self.connector_client_mock.conversations.create_conversation.side_effect = (
mock_create_conversation
)

return connector_client_mock
return self.connector_client_mock


async def process_activity(
Expand Down Expand Up @@ -572,3 +577,88 @@ async def callback(context: TurnContext):
await adapter.continue_conversation(
refs, callback, claims_identity=skills_identity, audience=skill_2_app_id
)

async def test_delivery_mode_buffered_replies(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.buffered_replies,
text="hello world",
)

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 invoke_response
assert invoke_response.status == 200
activities = invoke_response.body
assert len(activities) == 3
assert activities[0].text == "activity 1"
assert activities[1].text == "activity 2"
assert activities[2].text == "activity 3"
assert (
adapter.connector_client_mock.conversations.send_to_conversation.call_count
== 0
)

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
)
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ class DeliveryModes(str, Enum):

normal = "normal"
notification = "notification"
buffered_replies = "bufferedReplies"


class ContactRelationUpdateActionTypes(str, Enum):
Expand Down