8000 Merge pull request #1401 from microsoft/axsuarez/test-skill-http-client · itsmokha/botbuilder-python@26df914 · GitHub
[go: up one dir, main page]

Skip to content

Commit 26df914

Browse files
authored
Merge pull request microsoft#1401 from microsoft/axsuarez/test-skill-http-client
Testing SkillHttpClient
2 parents 5807899 + f2bbd03 commit 26df914

File tree

3 files changed

+207
-2
lines changed

3 files changed

+207
-2
lines changed

libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/skills/skill_http_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ async def post_activity_to_skill(
5050
originating_audience = (
5151
GovernmentConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE
5252
if self._channel_provider is not None
53-
and self._channel_provider.IsGovernment()
53+
and self._channel_provider.is_government()
5454
else AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE
5555
)
5656

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
msrest==0.6.10
22
botframework-connector==4.10.0
33
botbuilder-schema==4.10.0
4-
aiohttp==3.6.2
4+
aiohttp==3.6.2
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from uuid import uuid4
5+
from typing import Awaitable, Callable, Dict, Union
6+
7+
8+
from unittest.mock import Mock
9+
import aiounittest
10+
11+
from botbuilder.core import MessageFactory, InvokeResponse
12+
from botbuilder.core.skills import (
13+
BotFrameworkSkill,
14+
ConversationIdFactoryBase,
15+
SkillConversationIdFactoryOptions,
16+
SkillConversationReference,
17+
)
18+
from botbuilder.integration.aiohttp.skills import SkillHttpClient
19+
from botbuilder.schema import Activity, ConversationAccount, ConversationReference
20+
from botframework.connector.auth import (
21+
AuthenticationConstants,
22+
ChannelProvider,
23+
GovernmentConstants,
24+
)
25+
26+
27+
class SimpleConversationIdFactory(ConversationIdFactoryBase):
28+
def __init__(self, conversation_id: str):
29+
self._conversation_id = conversation_id
30+
self._conversation_refs: Dict[str, SkillConversationReference] = {}
31+
# Public property to capture and assert the options passed to CreateSkillConversationIdAsync.
32+
self.creation_options: SkillConversationIdFactoryOptions = None
33+
34+
async def create_skill_conversation_id(
35+
self,
36+
options_or_conversation_reference: Union[
37+
SkillConversationIdFactoryOptions, ConversationReference
38+
],
39+
) -> str:
40+
self.creation_options = options_or_conversation_reference
41+
42+
key = self._conversation_id
43+
self._conversation_refs[key] = self._conversation_refs.get(
44+
key,
45+
SkillConversationReference(
46+
conversation_reference=options_or_conversation_reference.activity.get_conversation_reference(),
47+
oauth_scope=options_or_conversation_reference.from_bot_oauth_scope,
48+
),
49+
)
50+
return key
51+
52+
async def get_conversation_reference(
53+
self, skill_conversation_id: str
54+
) -> SkillConversationReference:
55+
return self._conversation_refs[skill_conversation_id]
56+
57+
async def delete_conversation_reference(self, skill_conversation_id: str):
58+
raise NotImplementedError()
59+
60+
61+
class TestSkillHttpClientTests(aiounittest.AsyncTestCase):
62+
async def test_post_activity_with_originating_audience(self):
63+
conversation_id = str(uuid4())
64+
conversation_id_factory = SimpleConversationIdFactory(conversation_id)
65+
test_activity = MessageFactory.text("some message")
66+
test_activity.conversation = ConversationAccount()
67+
skill = BotFrameworkSkill(
68+
id="SomeSkill",
69+
app_id="",
70+
skill_endpoint="https://someskill.com/api/messages",
71+
)
72+
73+
async def _mock_post_content(
74+
to_url: str,
75+
token: str, # pylint: disable=unused-argument
76+
activity: Activity,
77+
) -> (int, object):
78+
nonlocal self
79+
self.assertEqual(skill.skill_endpoint, to_url)
80+
# Assert that the activity being sent has what we expect.
81+
self.assertEqual(conversation_id, activity.conversation.id)
82+
self.assertEqual("https://parentbot.com/api/messages", activity.service_url)
83+
84+
# Create mock response.
85+
return 200, None
86+
87+
sut = await self._create_http_client_with_mock_handler(
88+
_mock_post_content, conversation_id_factory
89+
)
90+
91+
result = await sut.post_activity_to_skill(
92+
"",
93+
skill,
94+
"https://parentbot.com/api/messages",
95+
test_activity,
96+
"someOriginatingAudience",
97+
)
98+
99+
# Assert factory options
100+
self.assertEqual("", conversation_id_factory.creation_options.from_bot_id)
101+
self.assertEqual(
102+
"someOriginatingAudience",
103+
conversation_id_factory.creation_options.from_bot_oauth_scope,
104+
)
105+
self.assertEqual(
106+
test_activity, conversation_id_factory.creation_options.activity
107+
)
108+
self.assertEqual(
109+
skill, conversation_id_factory.creation_options.bot_framework_skill
110+
)
111+
112+
# Assert result
113+
self.assertIsInstance(result, InvokeResponse)
114+
self.assertEqual(200, result.status)
115+
116+
async def test_post_activity_using_invoke_response(self):
117+
for is_gov in [True, False]:
118+
with self.subTest(is_government=is_gov):
119+
# pylint: disable=undefined-variable
120+
# pylint: disable=cell-var-from-loop
121+
conversation_id = str(uuid4())
122+
conversation_id_factory = SimpleConversationIdFactory(conversation_id)
123+
test_activity = MessageFactory.text("some message")
124+
test_activity.conversation = ConversationAccount()
125+
expected_oauth_scope = (
126+
AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE
127+
)
128+
mock_channel_provider: ChannelProvider = Mock(spec=ChannelProvider)
129+
130+
def is_government_mock():
131+
nonlocal expected_oauth_scope
132+
if is_government:
133+
expected_oauth_scope = (
134+
GovernmentConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE
135+
)
136+
137+
return is_government
138+
139+
mock_channel_provider.is_government = Mock(
140+
side_effect=is_government_mock
141+
)
142+
143+
skill = BotFrameworkSkill(
144+
id="SomeSkill",
145+
app_id="",
146+
skill_endpoint="https://someskill.com/api/messages",
147+
)
148+
149+
async def _mock_post_content(
150+
to_url: str,
151+
token: str, # pylint: disable=unused-argument
152+
activity: Activity,
153+
) -> (int, object):
154+
nonlocal self
155+
156+
self.assertEqual(skill.skill_endpoint, to_url)
157+
# Assert that the activity being sent has what we expect.
158+
self.assertEqual(conversation_id, activity.conversation.id)
159+
self.assertEqual(
160+
"https://parentbot.com/api/messages", activity.service_url
161+
)
162+
163+
# Create mock response.
164+
return 200, None
165+
166+
sut = await self._create_http_client_with_mock_handler(
167+
_mock_post_content, conversation_id_factory
168+
)
169+
result = await sut.post_activity_to_skill(
170+
"", skill, "https://parentbot.com/api/messages", test_activity
171+
)
172+
173+
# Assert factory options
174+
self.assertEqual(
175+
"", conversation_id_factory.creation_options.from_bot_id
176+
)
177+
self.assertEqual(
178+
expected_oauth_scope,
179+
conversation_id_factory.creation_options.from_bot_oauth_scope,
180+
)
181+
self.assertEqual(
182+
test_activity, conversation_id_factory.creation_options.activity
183+
)
184+
self.assertEqual(
185+
skill, conversation_id_factory.creation_options.bot_framework_skill
186+
)
187+
188+
# Assert result
189+
self.assertIsInstance(result, InvokeResponse)
190+
self.assertEqual(200, result.status)
191+
192+
# Helper to create an HttpClient with a mock message handler that executes function argument to validate the request
193+
# and mock a response.
194+
async def _create_http_client_with_mock_handler(
195+
self,
196+
value_function: Callable[[object], Awaitable[object]],
197+
id_factory: ConversationIdFactoryBase,
198+
channel_provider: ChannelProvider = None,
199+
) -> SkillHttpClient:
200+
# pylint: disable=protected-access
201+
client = SkillHttpClient(Mock(), id_factory, channel_provider)
202+
client._post_content = value_function
203+
await client._session.close()
204+
205+
return client

0 commit comments

Comments
 (0)
0