diff --git a/libraries/botbuilder-core/botbuilder/core/adapters/test_adapter.py b/libraries/botbuilder-core/botbuilder/core/adapters/test_adapter.py index 786c8f179..44d754969 100644 --- a/libraries/botbuilder-core/botbuilder/core/adapters/test_adapter.py +++ b/libraries/botbuilder-core/botbuilder/core/adapters/test_adapter.py @@ -112,6 +112,9 @@ async def receive_activity(self, activity): context = TurnContext(self, request) return await self.run_pipeline(context, self.logic) + def get_next_activity(self) -> Activity: + return self.activity_buffer.pop(0) + async def send(self, user_says) -> object: """ Sends something to the bot. This returns a new `TestFlow` instance which can be used to add diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/attachment_prompt.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/attachment_prompt.py index b59a69aae..5a6b9f7bb 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/attachment_prompt.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/attachment_prompt.py @@ -18,7 +18,7 @@ class AttachmentPrompt(Prompt): By default the prompt will return to the calling dialog an `[Attachment]` """ - def __init__(self, dialog_id: str, validator: Callable[[Attachment], bool]): + def __init__(self, dialog_id: str, validator: Callable[[Attachment], bool] = None): super().__init__(dialog_id, validator) async def on_prompt( @@ -26,7 +26,7 @@ async def on_prompt( context: TurnContext, state: Dict[str, object], options: PromptOptions, - isRetry: bool + is_retry: bool ): if not context: raise TypeError('AttachmentPrompt.on_prompt(): TurnContext cannot be None.') @@ -34,7 +34,7 @@ async def on_prompt( if not isinstance(options, PromptOptions): raise TypeError('AttachmentPrompt.on_prompt(): PromptOptions are required for Attachment Prompt dialogs.') - if isRetry and options.retry_prompt: + if is_retry and options.retry_prompt: options.retry_prompt.input_hint = InputHints.expecting_input await context.send_activity(options.retry_prompt) elif options.prompt: diff --git a/libraries/botbuilder-dialogs/tests/test_attachment_prompt.py b/libraries/botbuilder-dialogs/tests/test_attachment_prompt.py index cbc45a6a8..001d07469 100644 --- a/libraries/botbuilder-dialogs/tests/test_attachment_prompt.py +++ b/libraries/botbuilder-dialogs/tests/test_attachment_prompt.py @@ -2,11 +2,12 @@ # Licensed under the MIT License. import aiounittest -from botbuilder.dialogs.prompts import AttachmentPrompt, PromptOptions, PromptRecognizerResult -from botbuilder.schema import Activity, InputHints +from botbuilder.dialogs.prompts import AttachmentPrompt, PromptOptions, PromptRecognizerResult, PromptValidatorContext +from botbuilder.schema import Activity, ActivityTypes, Attachment, InputHints -from botbuilder.core import TurnContext, ConversationState +from botbuilder.core import TurnContext, ConversationState, MemoryStorage, MessageFactory from botbuilder.core.adapters import TestAdapter +from botbuilder.dialogs import DialogSet, DialogTurnStatus class AttachmentPromptTests(aiounittest.AsyncTestCase): def test_attachment_prompt_with_empty_id_should_fail(self): @@ -18,6 +19,245 @@ def test_attachment_prompt_with_empty_id_should_fail(self): def test_attachment_prompt_with_none_id_should_fail(self): with self.assertRaises(TypeError): AttachmentPrompt(None) - - # TODO other tests require TestFlow - \ No newline at end of file + + async def test_basic_attachment_prompt(self): + async def exec_test(turn_context: TurnContext): + dc = await dialogs.create_context(turn_context) + + results = await dc.continue_dialog() + if results.status == DialogTurnStatus.Empty: + options = PromptOptions(prompt=Activity(type=ActivityTypes.message, text='please add an attachment.')) + await dc.prompt('AttachmentPrompt', options) + elif results.status == DialogTurnStatus.Complete: + attachment = results.result[0] + content = MessageFactory.text(attachment.content) + await turn_context.send_activity(content) + + await convo_state.save_changes(turn_context) + + # Initialize TestAdapter. + adapter = TestAdapter(exec_test) + + # Create ConversationState with MemoryStorage and register the state as middleware. + convo_state = ConversationState(MemoryStorage()) + + # Create a DialogState property, DialogSet and AttachmentPrompt. + dialog_state = convo_state.create_property('dialog_state') + dialogs = DialogSet(dialog_state) + dialogs.add(AttachmentPrompt('AttachmentPrompt')) + + # Create incoming activity with attachment. + attachment = Attachment(content='some content', content_type='text/plain') + attachment_activity = Activity(type=ActivityTypes.message, attachments=[attachment]) + + step1 = await adapter.send('hello') + step2 = await step1.assert_reply('please add an attachment.') + step3 = await step2.send(attachment_activity) + await step3.assert_reply('some content') + + async def test_attachment_prompt_with_validator(self): + async def exec_test(turn_context: TurnContext): + dc = await dialogs.create_context(turn_context) + + results = await dc.continue_dialog() + if results.status == DialogTurnStatus.Empty: + options = PromptOptions(prompt=Activity(type=ActivityTypes.message, text='please add an attachment.')) + await dc.prompt('AttachmentPrompt', options) + elif results.status == DialogTurnStatus.Complete: + attachment = results.result[0] + content = MessageFactory.text(attachment.content) + await turn_context.send_activity(content) + + await convo_state.save_changes(turn_context) + + # Initialize TestAdapter. + adapter = TestAdapter(exec_test) + + # Create ConversationState with MemoryStorage and register the state as middleware. + convo_state = ConversationState(MemoryStorage()) + + # Create a DialogState property, DialogSet and AttachmentPrompt. + dialog_state = convo_state.create_property('dialog_state') + dialogs = DialogSet(dialog_state) + + async def aux_validator(prompt_context: PromptValidatorContext): + assert prompt_context, 'Validator missing prompt_context' + return prompt_context.recognized.succeeded + + dialogs.add(AttachmentPrompt('AttachmentPrompt', aux_validator)) + + # Create incoming activity with attachment. + attachment = Attachment(content='some content', content_type='text/plain') + attachment_activity = Activity(type=ActivityTypes.message, attachments=[attachment]) + + step1 = await adapter.send('hello') + step2 = await step1.assert_reply('please add an attachment.') + step3 = await step2.send(attachment_activity) + await step3.assert_reply('some content') + + async def test_retry_attachment_prompt(self): + async def exec_test(turn_context: TurnContext): + dc = await dialogs.create_context(turn_context) + + results = await dc.continue_dialog() + if results.status == DialogTurnStatus.Empty: + options = PromptOptions(prompt=Activity(type=ActivityTypes.message, text='please add an attachment.')) + await dc.prompt('AttachmentPrompt', options) + elif results.status == DialogTurnStatus.Complete: + attachment = results.result[0] + content = MessageFactory.text(attachment.content) + await turn_context.send_activity(content) + + await convo_state.save_changes(turn_context) + + # Initialize TestAdapter. + adapter = TestAdapter(exec_test) + + # Create ConversationState with MemoryStorage and register the state as middleware. + convo_state = ConversationState(MemoryStorage()) + + # Create a DialogState property, DialogSet and AttachmentPrompt. + dialog_state = convo_state.create_property('dialog_state') + dialogs = DialogSet(dialog_state) + dialogs.add(AttachmentPrompt('AttachmentPrompt')) + + # Create incoming activity with attachment. + attachment = Attachment(content='some content', content_type='text/plain') + attachment_activity = Activity(type=ActivityTypes.message, attachments=[attachment]) + + step1 = await adapter.send('hello') + step2 = await step1.assert_reply('please add an attachment.') + step3 = await step2.send('hello again') + step4 = await step3.assert_reply('please add an attachment.') + step5 = await step4.send(attachment_activity) + await step5.assert_reply('some content') + + async def test_attachment_prompt_with_custom_retry(self): + async def exec_test(turn_context: TurnContext): + dc = await dialogs.create_context(turn_context) + + results = await dc.continue_dialog() + if results.status == DialogTurnStatus.Empty: + options = PromptOptions( + prompt=Activity(type=ActivityTypes.message, text='please add an attachment.'), + retry_prompt=Activity(type=ActivityTypes.message, text='please try again.') + ) + await dc.prompt('AttachmentPrompt', options) + elif results.status == DialogTurnStatus.Complete: + attachment = results.result[0] + content = MessageFactory.text(attachment.content) + await turn_context.send_activity(content) + + await convo_state.save_changes(turn_context) + + # Initialize TestAdapter. + adapter = TestAdapter(exec_test) + + # Create ConversationState with MemoryStorage and register the state as middleware. + convo_state = ConversationState(MemoryStorage()) + + # Create a DialogState property, DialogSet and AttachmentPrompt. + dialog_state = convo_state.create_property('dialog_state') + dialogs = DialogSet(dialog_state) + + async def aux_validator(prompt_context: PromptValidatorContext): + assert prompt_context, 'Validator missing prompt_context' + return prompt_context.recognized.succeeded + + dialogs.add(AttachmentPrompt('AttachmentPrompt', aux_validator)) + + # Create incoming activity with attachment. + attachment = Attachment(content='some content', content_type='text/plain') + attachment_activity = Activity(type=ActivityTypes.message, attachments=[attachment]) + invalid_activty = Activity(type=ActivityTypes.message, text='invalid') + + step1 = await adapter.send('hello') + step2 = await step1.assert_reply('please add an attachment.') + step3 = await step2.send(invalid_activty) + step4 = await step3.assert_reply('please try again.') + step5 = await step4.send(attachment_activity) + await step5.assert_reply('some content') + + async def test_should_send_ignore_retry_rompt_if_validator_replies(self): + async def exec_test(turn_context: TurnContext): + dc = await dialogs.create_context(turn_context) + + results = await dc.continue_dialog() + if results.status == DialogTurnStatus.Empty: + options = PromptOptions( + prompt=Activity(type=ActivityTypes.message, text='please add an attachment.'), + retry_prompt=Activity(type=ActivityTypes.message, text='please try again.') + ) + await dc.prompt('AttachmentPrompt', options) + elif results.status == DialogTurnStatus.Complete: + attachment = results.result[0] + content = MessageFactory.text(attachment.content) + await turn_context.send_activity(content) + + await convo_state.save_changes(turn_context) + + # Initialize TestAdapter. + adapter = TestAdapter(exec_test) + + # Create ConversationState with MemoryStorage and register the state as middleware. + convo_state = ConversationState(MemoryStorage()) + + # Create a DialogState property, DialogSet and AttachmentPrompt. + dialog_state = convo_state.create_property('dialog_state') + dialogs = DialogSet(dialog_state) + + async def aux_validator(prompt_context: PromptValidatorContext): + assert prompt_context, 'Validator missing prompt_context' + + if not prompt_context.recognized.succeeded: + await prompt_context.context.send_activity('Bad input.') + + return prompt_context.recognized.succeeded + + dialogs.add(AttachmentPrompt('AttachmentPrompt', aux_validator)) + + # Create incoming activity with attachment. + attachment = Attachment(content='some content', content_type='text/plain') + attachment_activity = Activity(type=ActivityTypes.message, attachments=[attachment]) + invalid_activty = Activity(type=ActivityTypes.message, text='invalid') + + step1 = await adapter.send('hello') + step2 = await step1.assert_reply('please add an attachment.') + step3 = await step2.send(invalid_activty) + step4 = await step3.assert_reply('Bad input.') + step5 = await step4.send(attachment_activity) + await step5.assert_reply('some content') + + async def test_should_not_send_retry_if_not_specified(self): + async def exec_test(turn_context: TurnContext): + dc = await dialogs.create_context(turn_context) + + results = await dc.continue_dialog() + if results.status == DialogTurnStatus.Empty: + await dc.begin_dialog('AttachmentPrompt', PromptOptions()) + elif results.status == DialogTurnStatus.Complete: + attachment = results.result[0] + content = MessageFactory.text(attachment.content) + await turn_context.send_activity(content) + + await convo_state.save_changes(turn_context) + + # Initialize TestAdapter. + adapter = TestAdapter(exec_test) + + # Create ConversationState with MemoryStorage and register the state as middleware. + convo_state = ConversationState(MemoryStorage()) + + # Create a DialogState property, DialogSet and AttachmentPrompt. + dialog_state = convo_state.create_property('dialog_state') + dialogs = DialogSet(dialog_state) + dialogs.add(AttachmentPrompt('AttachmentPrompt')) + + # Create incoming activity with attachment. + attachment = Attachment(content='some content', content_type='text/plain') + attachment_activity = Activity(type=ActivityTypes.message, attachments=[attachment]) + + step1 = await adapter.send('hello') + step2 = await step1.send('what?') + step3 = await step2.send(attachment_activity) + await step3.assert_reply('some content')