Repository: microsoft/botbuilder-python
Pull request #2175 (2024-10-03 14:13:27)

Port: TeamsChannelData need OnBehalfOf [#6609]
Fixes #minor

## Description
This PR ports the changes from https://github.com/microsoft/botbuilder-dotnet/pull/6609 

- Adds the OnBehalfOf property to send messages via bots on behalf of another user in Teams.
- It also adds unit tests to cover the new code.

## Specific Changes
  - Added the `OnBehalfOf `property in the `TeamsChannelData `class.
  - Added the `teams_get_team_on_behalf_of `method to return the current activity's _OnBehalfOf_ list.

## Testing
The following image shows the related unit test passing.
<img width="884" alt="image" src="https://github.com/user-attachments/assets/8e614ed8-f29b-444a-80eb-f3ca5a22a183">




Patch:
diff --git a/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_extensions.py b/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_extensions.py
index 04e11583..e604cb08 100644
--- a/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_extensions.py
+++ b/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_extensions.py
@@ -1,12 +1,14 @@
 # Copyright (c) Microsoft Corporation. All rights reserved.
 # Licensed under the MIT License.
 
+from typing import List
 from botbuilder.schema import Activity
 from botbuilder.schema.teams import (
     NotificationInfo,
     TeamsChannelData,
     TeamInfo,
     TeamsMeetingInfo,
+    OnBehalfOf,
 )
 
 
@@ -84,3 +86,14 @@ def teams_get_meeting_info(activity: Activity) -> TeamsMeetingInfo:
         return channel_data.meeting
 
     return None
+
+
+def teams_get_team_on_behalf_of(activity: Activity) -> List[OnBehalfOf]:
+    if not activity:
+        return None
+
+    if activity.channel_data:
+        channel_data = TeamsChannelData().deserialize(activity.channel_data)
+        return channel_data.on_behalf_of
+
+    return None
diff --git a/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py b/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py
index 7c837b24..be9aa11c 100644
--- a/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py
+++ b/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py
@@ -88,6 +88,7 @@ from ._models_py3 import ConfigResponse
 from ._models_py3 import ConfigTaskResponse
 from ._models_py3 import MeetingNotificationBase
 from ._models_py3 import MeetingNotificationResponse
+from ._models_py3 import OnBehalfOf
 
 __all__ = [
     "AppBasedLinkQuery",
@@ -177,4 +178,5 @@ __all__ = [
     "ConfigTaskResponse",
     "MeetingNotificationBase",
     "MeetingNotificationResponse",
+    "OnBehalfOf",
 ]
diff --git a/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py b/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py
index 72f48d5b..0b6e0e89 100644
--- a/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py
+++ b/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py
@@ -2006,8 +2006,10 @@ class TeamsChannelData(Model):
     :type tenant: ~botframework.connector.teams.models.TenantInfo
     :param meeting: Information about the meeting in which the message was sent
     :type meeting: ~botframework.connector.teams.models.TeamsMeetingInfo
-    :param meeting: Information about the about the settings in which the message was sent
-    :type meeting: ~botframework.connector.teams.models.TeamsChannelDataSettings
+    :param settings: Information about the about the settings in which the message was sent
+    :type settings: ~botframework.connector.teams.models.TeamsChannelDataSettings
+    :param on_behalf_of: The OnBehalfOf list for user attribution
+    :type on_behalf_of: list[~botframework.connector.teams.models.OnBehalfOf]
     """
 
     _attribute_map = {
@@ -2018,6 +2020,7 @@ class TeamsChannelData(Model):
         "tenant": {"key": "tenant", "type": "TenantInfo"},
         "meeting": {"key": "meeting", "type": "TeamsMeetingInfo"},
         "settings": {"key": "settings", "type": "TeamsChannelDataSettings"},
+        "on_behalf_of": {"key": "onBehalfOf", "type": "[OnBehalfOf]"},
     }
 
     def __init__(
@@ -2030,6 +2033,7 @@ class TeamsChannelData(Model):
         tenant=None,
         meeting=None,
         settings: TeamsChannelDataSettings = None,
+        on_behalf_of: List["OnBehalfOf"] = None,
         **kwargs
     ) -> None:
         super(TeamsChannelData, self).__init__(**kwargs)
@@ -2041,6 +2045,7 @@ class TeamsChannelData(Model):
         self.tenant = tenant
         self.meeting = meeting
         self.settings = settings
+        self.on_behalf_of = on_behalf_of if on_behalf_of is not None else []
 
 
 class TenantInfo(Model):


Test patch:
diff --git a/libraries/botbuilder-core/tests/teams/test_teams_channel_data.py b/libraries/botbuilder-core/tests/teams/test_teams_channel_data.py
index e468526b..324749ce 100644
--- a/libraries/botbuilder-core/tests/teams/test_teams_channel_data.py
+++ b/libraries/botbuilder-core/tests/teams/test_teams_channel_data.py
@@ -1,11 +1,21 @@
 # Copyright (c) Microsoft Corporation. All rights reserved.
 # Licensed under the MIT License.
 
+from uuid import uuid4
 import aiounittest
 
 from botbuilder.schema import Activity
 from botbuilder.schema.teams import TeamsChannelData
 from botbuilder.core.teams import teams_get_team_info
+from botbuilder.schema.teams._models_py3 import (
+    ChannelInfo,
+    NotificationInfo,
+    OnBehalfOf,
+    TeamInfo,
+    TeamsChannelDataSettings,
+    TeamsMeetingInfo,
+    TenantInfo,
+)
 
 
 class TestTeamsChannelData(aiounittest.AsyncTestCase):
@@ -28,3 +38,49 @@ class TestTeamsChannelData(aiounittest.AsyncTestCase):
 
         # Assert
         assert team_info.aad_group_id == "teamGroup123"
+
+    def test_teams_channel_data_inits(self):
+        # Arrange
+        channel = ChannelInfo(id="general", name="General")
+        event_type = "eventType"
+        team = TeamInfo(id="supportEngineers", name="Support Engineers")
+        notification = NotificationInfo(alert=True)
+        tenant = TenantInfo(id="uniqueTenantId")
+        meeting = TeamsMeetingInfo(id="BFSE Stand Up")
+        settings = TeamsChannelDataSettings(selected_channel=channel)
+        on_behalf_of = [
+            OnBehalfOf(
+                display_name="onBehalfOfTest",
+                item_id=0,
+                mention_type="person",
+                mri=str(uuid4()),
+            )
+        ]
+
+        # Act
+        channel_data = TeamsChannelData(
+            channel=channel,
+            event_type=event_type,
+            team=team,
+            notification=notification,
+            tenant=tenant,
+            meeting=meeting,
+            settings=settings,
+            on_behalf_of=on_behalf_of,
+        )
+
+        # Assert
+        self.assertIsNotNone(channel_data)
+        self.assertIsInstance(channel_data, TeamsChannelData)
+        self.assertEqual(channel, channel_data.channel)
+        self.assertEqual(event_type, channel_data.event_type)
+        self.assertEqual(team, channel_data.team)
+        self.assertEqual(notification, channel_data.notification)
+        self.assertEqual(tenant, channel_data.tenant)
+        self.assertEqual(meeting, channel_data.meeting)
+        self.assertEqual(settings, channel_data.settings)
+        self.assertEqual(on_behalf_of, channel_data.on_behalf_of)
+        self.assertEqual(on_behalf_of[0].display_name, "onBehalfOfTest")
+        self.assertEqual(on_behalf_of[0].mention_type, "person")
+        self.assertIsNotNone(on_behalf_of[0].mri)
+        self.assertEqual(on_behalf_of[0].item_id, 0)
diff --git a/libraries/botbuilder-core/tests/teams/test_teams_extension.py b/libraries/botbuilder-core/tests/teams/test_teams_extension.py
index ac55cb9a..1d2520bc 100644
--- a/libraries/botbuilder-core/tests/teams/test_teams_extension.py
+++ b/libraries/botbuilder-core/tests/teams/test_teams_extension.py
@@ -1,6 +1,7 @@
 # Copyright (c) Microsoft Corporation. All rights reserved.
 # Licensed under the MIT License.
 
+from uuid import uuid4
 import aiounittest
 
 from botbuilder.schema import Activity
@@ -11,7 +12,11 @@ from botbuilder.core.teams import (
     teams_get_team_info,
     teams_notify_user,
 )
-from botbuilder.core.teams.teams_activity_extensions import teams_get_meeting_info
+from botbuilder.core.teams.teams_activity_extensions import (
+    teams_get_meeting_info,
+    teams_get_team_on_behalf_of,
+)
+from botbuilder.schema.teams._models_py3 import OnBehalfOf
 
 
 class TestTeamsActivityHandler(aiounittest.AsyncTestCase):
@@ -190,3 +195,23 @@ class TestTeamsActivityHandler(aiounittest.AsyncTestCase):
 
         # Assert
         assert meeting_id == "meeting123"
+
+    def test_teams_channel_data_existing_on_behalf_of(self):
+        # Arrange
+        on_behalf_of_list = [
+            OnBehalfOf(
+                display_name="onBehalfOfTest",
+                item_id=0,
+                mention_type="person",
+                mri=str(uuid4()),
+            )
+        ]
+
+        activity = Activity(channel_data={"onBehalfOf": on_behalf_of_list})
+
+        # Act
+        on_behalf_of_list = teams_get_team_on_behalf_of(activity)
+
+        # Assert
+        self.assertEqual(1, len(on_behalf_of_list))
+        self.assertEqual("onBehalfOfTest", on_behalf_of_list[0].display_name)