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
3 changes: 3 additions & 0 deletions packages/api/src/microsoft/teams/api/clients/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from .conversation import __all__ as conversation_all
from .meeting import * # noqa: F403
from .meeting import __all__ as meeting_all
from .team import * # noqa: F403
from .team import __all__ as team_all
from .user import * # noqa: F403
from .user import __all__ as user_all

Expand All @@ -17,4 +19,5 @@
*user_all,
*bot_all,
*meeting_all,
*team_all,
]
8 changes: 8 additions & 0 deletions packages/api/src/microsoft/teams/api/clients/team/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT License.
"""

from .client import TeamClient

__all__ = ["TeamClient"]
56 changes: 56 additions & 0 deletions packages/api/src/microsoft/teams/api/clients/team/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT License.
"""

from typing import List, Optional, Union

from microsoft.teams.common.http import Client, ClientOptions

from ...models import ChannelInfo, TeamDetails
from ..base_client import BaseClient


class TeamClient(BaseClient):
"""Client for managing Teams teams."""

def __init__(
self,
service_url: str,
options: Optional[Union[Client, ClientOptions]] = None,
) -> None:
"""
Initialize the TeamClient.

Args:
service_url: The service URL for API calls.
options: Optional Client or ClientOptions instance. If not provided, a default Client will be created.
"""
super().__init__(options)
self.service_url = service_url

async def get_by_id(self, id: str) -> TeamDetails:
"""
Get team details by ID.

Args:
id: The team ID.

Returns:
The team details.
"""
response = await self.http.get(f"{self.service_url}/v3/teams/{id}")
return TeamDetails.model_validate(response.json())

async def get_conversations(self, id: str) -> List[ChannelInfo]:
"""
Get team conversations (channels).

Args:
id: The team ID.

Returns:
List of channel information.
"""
response = await self.http.get(f"{self.service_url}/v3/teams/{id}/conversations")
return [ChannelInfo.model_validate(channel) for channel in response.json()]
2 changes: 2 additions & 0 deletions packages/api/src/microsoft/teams/api/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from .messaging_extension import __all__ as messaging_extension_all
from .sign_in import * # noqa: F403
from .sign_in import __all__ as sign_in_all
from .team_details import TeamDetails
from .token import * # noqa: F403
from .token import __all__ as token_all
from .token_exchange import * # noqa: F403
Expand Down Expand Up @@ -61,6 +62,7 @@
"ChannelID",
*conversation_all,
*sign_in_all,
"TeamDetails",
*token_all,
*token_exchange_all,
]
32 changes: 32 additions & 0 deletions packages/api/src/microsoft/teams/api/models/team_details.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT License.
"""

from typing import Literal, Optional

from .custom_base_model import CustomBaseModel


class TeamDetails(CustomBaseModel):
"""
Details related to a team.
"""

id: str
"Unique identifier representing a team"

name: Optional[str] = None
"Name of team."

type: Literal["standard", "sharedChannel", "privateChannel"]
"The type of the team. Valid values are standard, sharedChannel and privateChannel."

aad_group_id: Optional[str] = None
"Azure Active Directory (AAD) Group Id for the team."

channel_count: Optional[int] = None
"Count of channels in the team."

member_count: Optional[int] = None
"Count of members in the team."
61 changes: 61 additions & 0 deletions packages/api/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,19 @@ def handler(request: httpx.Request) -> httpx.Response:
"signInLink": "https://mock-signin.url/auth",
"tokenExchangeResource": {"id": "mock_resource_id"},
}
elif "/v3/teams/" in str(request.url) and "/conversations" in str(request.url):
response_data = [
{
"id": "mock_channel_id_1",
"name": "General",
"type": "standard",
},
{
"id": "mock_channel_id_2",
"name": "Random",
"type": "standard",
},
]
elif "/conversations/" in str(request.url) and str(request.url).endswith("/members"):
response_data = [
{
Expand Down Expand Up @@ -133,6 +146,54 @@ def handler(request: httpx.Request) -> httpx.Response:
"expires_in": 3600,
"access_token": "mock_oauth_token_123",
}
elif "/v1/meetings/" in str(request.url) and "/participants/" in str(request.url):
response_data = {
"user": {
"id": "mock_participant_id",
"name": "Mock Participant",
"aadObjectId": "mock_participant_aad_id",
},
"meeting": {
"id": "mock_meeting_id",
"title": "Mock Meeting",
"type": "meetingChat",
},
"conversation": {
"id": "mock_conversation_id",
"conversationType": "groupChat",
"tenantId": "mock_tenant_id",
},
}
elif "/v1/meetings/" in str(request.url):
response_data = {
"id": "mock_meeting_id",
"details": {
"id": "mock_meeting_id",
"title": "Mock Meeting",
"type": "meetingChat",
"joinUrl": "https://teams.microsoft.com/l/meetup-join/mock_meeting",
"msGraphResourceId": "mock_graph_resource_id",
},
"conversation": {
"id": "mock_conversation_id",
"conversationType": "groupChat",
"tenantId": "mock_tenant_id",
},
"organizer": {
"id": "mock_organizer_id",
"name": "Mock Organizer",
"aadObjectId": "mock_organizer_aad_id",
},
}
elif "/v3/teams/" in str(request.url):
response_data = {
"id": "mock_team_id",
"name": "Mock Team",
"type": "standard",
"aadGroupId": "mock_aad_group_id",
"channelCount": 5,
"memberCount": 15,
}

return httpx.Response(
status_code=200,
Expand Down
50 changes: 50 additions & 0 deletions packages/api/tests/unit/test_meeting_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT License.
"""

import pytest
from microsoft.teams.api.clients.meeting import MeetingClient
from microsoft.teams.api.models import MeetingInfo, MeetingParticipant
from microsoft.teams.common.http import Client, ClientOptions


@pytest.mark.unit
class TestMeetingClient:
"""Unit tests for MeetingClient."""

@pytest.mark.asyncio
async def test_get_by_id(self, mock_http_client):
"""Test getting meeting by ID."""
service_url = "https://test.service.url"
client = MeetingClient(service_url, mock_http_client)
meeting_id = "test_meeting_id"

result = await client.get_by_id(meeting_id)

assert isinstance(result, MeetingInfo)

@pytest.mark.asyncio
async def test_get_participant(self, mock_http_client):
"""Test getting meeting participant."""
service_url = "https://test.service.url"
client = MeetingClient(service_url, mock_http_client)
meeting_id = "test_meeting_id"
participant_id = "test_participant_id"

result = await client.get_participant(meeting_id, participant_id)

assert isinstance(result, MeetingParticipant)

def test_http_client_property(self, mock_http_client):
"""Test HTTP client property getter and setter."""
service_url = "https://test.service.url"
client = MeetingClient(service_url, mock_http_client)

assert client.http == mock_http_client

# Test setter
new_http_client = Client(ClientOptions(base_url="https://new.api.com"))
client.http = new_http_client

assert client.http == new_http_client
50 changes: 50 additions & 0 deletions packages/api/tests/unit/test_team_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT License.
"""

import pytest
from microsoft.teams.api.clients.team import TeamClient
from microsoft.teams.api.models import ChannelInfo, TeamDetails
from microsoft.teams.common.http import Client, ClientOptions


@pytest.mark.unit
class TestTeamClient:
"""Unit tests for TeamClient."""

@pytest.mark.asyncio
async def test_get_by_id(self, mock_http_client):
"""Test getting team by ID."""
service_url = "https://test.service.url"
client = TeamClient(service_url, mock_http_client)
team_id = "test_team_id"

result = await client.get_by_id(team_id)

assert isinstance(result, TeamDetails)

@pytest.mark.asyncio
async def test_get_conversations(self, mock_http_client):
"""Test getting team conversations."""
service_url = "https://test.service.url"
client = TeamClient(service_url, mock_http_client)
team_id = "test_team_id"

result = await client.get_conversations(team_id)

assert isinstance(result, list)
assert all(isinstance(channel, ChannelInfo) for channel in result)

def test_http_client_property(self, mock_http_client):
"""Test HTTP client property getter and setter."""
service_url = "https://test.service.url"
client = TeamClient(service_url, mock_http_client)

assert client.http == mock_http_client

# Test setter
new_http_client = Client(ClientOptions(base_url="https://new.api.com"))
client.http = new_http_client

assert client.http == new_http_client