Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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 @@ -5,7 +5,8 @@

from typing import Generic, Optional, overload

from microsoft_teams.api.models.meetings import MeetingDetails
from microsoft_teams.api.activities.event.meeting_end import MeetingEndEventValue
from microsoft_teams.api.activities.event.meeting_start import MeetingStartEventValue

from microsoft_agents.activity import ActivityTypes
from microsoft_agents.activity.teams import MeetingParticipantsEventDetails
Expand Down Expand Up @@ -79,7 +80,9 @@ def __call(func: MeetingStartHandler[StateT]) -> MeetingStartHandler[StateT]:
async def __handler(context: TurnContext, state: StateT) -> None:
"""Adapt the core turn context and dispatch to the meeting start handler."""
teams_context = TeamsTurnContext(context, self._app)
meeting = MeetingDetails.model_validate(context.activity.value or {})
meeting = MeetingStartEventValue.model_validate(
context.activity.value or {}
)
await func(teams_context, state, meeting)

self._app.add_route(
Expand Down Expand Up @@ -130,7 +133,9 @@ def __call(func: MeetingEndHandler[StateT]) -> MeetingEndHandler[StateT]:
async def __handler(context: TurnContext, state: StateT) -> None:
"""Adapt the core turn context and dispatch to the meeting end handler."""
teams_context = TeamsTurnContext(context, self._app)
meeting = MeetingDetails.model_validate(context.activity.value or {})
meeting = MeetingEndEventValue.model_validate(
context.activity.value or {}
)
await func(teams_context, state, meeting)

self._app.add_route(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

from typing import Awaitable, Protocol

from microsoft_teams.api.models.meetings import MeetingDetails
from microsoft_teams.api.activities.event.meeting_end import MeetingEndEventValue
from microsoft_teams.api.activities.event.meeting_start import MeetingStartEventValue
from microsoft_agents.activity.teams import MeetingParticipantsEventDetails

from microsoft_agents.hosting.core.app._type_defs import _StateContra
Expand All @@ -19,7 +20,7 @@ def __call__(
self,
context: TeamsTurnContext,
state: _StateContra,
meeting: MeetingDetails,
meeting: MeetingStartEventValue,
/,
) -> Awaitable[None]:
"""Handle a meeting start event.
Expand All @@ -39,7 +40,7 @@ def __call__(
self,
context: TeamsTurnContext,
state: _StateContra,
meeting: MeetingDetails,
meeting: MeetingEndEventValue,
/,
) -> Awaitable[None]:
"""Handle a meeting end event.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,22 @@
"constructsOrValidates": true
},
{
"upstreamSymbol": "microsoft_teams.api.models.meetings.MeetingDetails",
"upstreamSymbol": "microsoft_teams.api.activities.event.meeting_start.MeetingStartEventValue",
"usage": "parsed-model",
"exposure": "publicly-exposed",
"files": [
"microsoft_agents/hosting/msteams/meeting/meeting.py",
"microsoft_agents/hosting/msteams/meeting/route_handlers.py"
],
"propertiesRead": [],
"propertiesWritten": [],
"methodsCalled": [
"model_validate"
],
"constructsOrValidates": true
},
{
"upstreamSymbol": "microsoft_teams.api.activities.event.meeting_end.MeetingEndEventValue",
"usage": "parsed-model",
"exposure": "publicly-exposed",
"files": [
Expand Down
59 changes: 46 additions & 13 deletions tests/hosting_msteams/test_meetings.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

"""Tests for TeamsAgentExtension.meetings (meeting lifecycle events)."""

from datetime import datetime, timezone

import pytest
from unittest.mock import AsyncMock, MagicMock

Expand All @@ -16,18 +18,31 @@
)

if is_supported_version:
from microsoft_teams.api.models.meetings import MeetingDetails
from microsoft_teams.api.activities.event.meeting_end import MeetingEndEventValue
from microsoft_teams.api.activities.event.meeting_start import (
MeetingStartEventValue,
)
from microsoft_agents.activity.teams import MeetingParticipantsEventDetails
from microsoft_agents.hosting.msteams import TeamsAgentExtension


def _meeting_details_value() -> dict:
def _meeting_start_value() -> dict:
return {
"id": "meeting-id",
"type": "scheduled",
"joinUrl": "https://example.com/meet",
"title": "Test Meeting",
"msGraphResourceId": "graph-id",
"Id": "meeting-id",
"MeetingType": "Scheduled",
"JoinUrl": "https://example.com/meet",
"Title": "Test Meeting",
"StartTime": "2026-10-02T20:48:16.7632115Z",
}


def _meeting_end_value() -> dict:
return {
"Id": "meeting-id",
"MeetingType": "Scheduled",
"JoinUrl": "https://example.com/meet",
"Title": "Test Meeting",
"EndTime": "2026-10-02T20:49:20.330067Z",
}


Expand Down Expand Up @@ -98,34 +113,52 @@ async def test_start_handler_parses_meeting_details(self):
user_handler = AsyncMock()

@self.ext.meetings.start()
async def handler(ctx, state, meeting: MeetingDetails):
async def handler(ctx, state, meeting: MeetingStartEventValue):
await user_handler(ctx, state, meeting)

route_handler = self.app._routes[0]["handler"]
ctx = _make_context(
ActivityTypes.event,
name="application/vnd.microsoft.meetingStart",
value=_meeting_details_value(),
value=_meeting_start_value(),
)
await route_handler(ctx, MagicMock())
assert isinstance(user_handler.call_args[0][2], MeetingDetails)
meeting = user_handler.call_args[0][2]
assert isinstance(meeting, MeetingStartEventValue)
assert meeting.id == "meeting-id"
assert meeting.meeting_type == "Scheduled"
assert meeting.join_url == "https://example.com/meet"
assert meeting.title == "Test Meeting"
Comment thread
rodrigobr-msft marked this conversation as resolved.
assert isinstance(meeting.start_time, datetime)
assert meeting.start_time == datetime(
2026, 10, 2, 20, 48, 16, 763211, tzinfo=timezone.utc
)

@pytest.mark.asyncio
async def test_end_handler_parses_meeting_details(self):
Comment thread
rodrigobr-msft marked this conversation as resolved.
user_handler = AsyncMock()

@self.ext.meetings.end()
async def handler(ctx, state, meeting: MeetingDetails):
async def handler(ctx, state, meeting: MeetingEndEventValue):
await user_handler(ctx, state, meeting)

route_handler = self.app._routes[0]["handler"]
ctx = _make_context(
ActivityTypes.event,
name="application/vnd.microsoft.meetingEnd",
value=_meeting_details_value(),
value=_meeting_end_value(),
)
await route_handler(ctx, MagicMock())
assert isinstance(user_handler.call_args[0][2], MeetingDetails)
meeting = user_handler.call_args[0][2]
assert isinstance(meeting, MeetingEndEventValue)
assert meeting.id == "meeting-id"
assert meeting.meeting_type == "Scheduled"
assert meeting.join_url == "https://example.com/meet"
assert meeting.title == "Test Meeting"
assert isinstance(meeting.end_time, datetime)
assert meeting.end_time == datetime(
2026, 10, 2, 20, 49, 20, 330067, tzinfo=timezone.utc
)


class TestMeetingParticipants:
Expand Down
Loading