From 714923418c0decf2ae6b5e629682ab8320859146 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Fri, 2 Oct 2026 11:09:37 -0700 Subject: [PATCH] Ensuring headers get set across rest clients --- .../core/connector/client/connector_client.py | 7 +- .../connector/client/user_token_client.py | 6 +- .../connector/mcs/mcs_connector_client.py | 8 +++ .../connector/test_connector_client.py | 70 +++++++++++++++++++ .../connector/test_mcs_connector_client.py | 55 +++++++++++++++ .../connector/test_user_token_client.py | 35 ++++++++++ 6 files changed, 173 insertions(+), 8 deletions(-) create mode 100644 tests/hosting_core/connector/test_mcs_connector_client.py diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/connector_client.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/connector_client.py index 0844c578a..3a67335a1 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/connector_client.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/connector_client.py @@ -789,10 +789,9 @@ def __init__( } # Create session with the base URL - session = session or ClientSession( - base_url=endpoint, - headers=headers, - ) + session = session or ClientSession(base_url=endpoint) + session.headers.update(headers) + logger.debug( "ConnectorClient initialized with endpoint: %s and headers: %s", endpoint, diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/user_token_client.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/user_token_client.py index 0a00543c0..7ddae2c3e 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/user_token_client.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/user_token_client.py @@ -65,10 +65,8 @@ def __init__( } # Create session with the base URL - session = session or ClientSession( - base_url=endpoint, - headers=headers, - ) + session = session or ClientSession(base_url=endpoint) + session.headers.update(headers) logger.debug( "Creating UserTokenClient with endpoint: %s and headers: %s", endpoint, diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/mcs/mcs_connector_client.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/mcs/mcs_connector_client.py index 707bc5c29..ba9eba2bc 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/mcs/mcs_connector_client.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/mcs/mcs_connector_client.py @@ -14,6 +14,7 @@ from ..conversations_base import ConversationsBase from ..client._base_client import _BaseClient from .._utils import _handle_request_error +from ..get_product_info import get_product_info logger = logging.getLogger(__name__) @@ -224,6 +225,13 @@ def __init__(self, endpoint: str, client: Optional[ClientSession] = None): self._endpoint = endpoint self._client = client or ClientSession() + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + "User-Agent": get_product_info(), + } + self._client.headers.update(headers) + self._conversations = MCSConversations(self._client, self._endpoint) self._attachments = MCSAttachments() diff --git a/tests/hosting_core/connector/test_connector_client.py b/tests/hosting_core/connector/test_connector_client.py index ab7a03095..9ca0fae58 100644 --- a/tests/hosting_core/connector/test_connector_client.py +++ b/tests/hosting_core/connector/test_connector_client.py @@ -21,6 +21,8 @@ ConnectorClient, ConversationsOperations, ) +from microsoft_agents.hosting.core.connector.get_product_info import get_product_info +from microsoft_agents.hosting.core.connector.teams import TeamsConnectorClient from microsoft_agents.hosting.core.header_propagation import HeaderPropagationContext @@ -945,3 +947,71 @@ async def handler(request): assert "conv_sub_id" in captured["raw_path"] finally: await server.close() + + +class TestUserAgentHeader: + """Ensures ConnectorClient and TeamsConnectorClient send a User-Agent header.""" + + @pytest.mark.asyncio + async def test_connector_client_sets_user_agent_header(self): + client = ConnectorClient(endpoint="https://example.org/", token="token") + try: + assert client.client.headers["User-Agent"] == get_product_info() + finally: + await client.client.close() + + @pytest.mark.asyncio + async def test_connector_client_sends_user_agent_header_on_request(self): + captured = {} + + async def handler(request): + captured["user_agent"] = request.headers.get("User-Agent") + return web.json_response({"id": "activity-id-123"}) + + routes = [web.post("/v3/conversations/{conversation_id}/activities", handler)] + app = _create_app(routes) + + server = TestServer(app) + await server.start_server() + try: + client = ConnectorClient(endpoint=str(server.make_url("/")), token="token") + await client.conversations.send_to_conversation( + "conv-1", Activity(type="message", text="hi") + ) + finally: + await server.close() + await client.client.close() + + assert captured["user_agent"] == get_product_info() + + @pytest.mark.asyncio + async def test_teams_connector_client_sets_user_agent_header(self): + client = TeamsConnectorClient(endpoint="https://example.org/", token="token") + try: + assert client.client.headers["User-Agent"] == get_product_info() + finally: + await client.client.close() + + @pytest.mark.asyncio + async def test_teams_connector_client_sends_user_agent_header_on_request(self): + captured = {} + + async def handler(request): + captured["user_agent"] = request.headers.get("User-Agent") + return web.json_response({"id": "team-1"}) + + routes = [web.get("/v3/teams/{team_id}", handler)] + app = _create_app(routes) + + server = TestServer(app) + await server.start_server() + try: + client = TeamsConnectorClient( + endpoint=str(server.make_url("/")), token="token" + ) + await client.fetch_team_details("team-1") + finally: + await server.close() + await client.client.close() + + assert captured["user_agent"] == get_product_info() diff --git a/tests/hosting_core/connector/test_mcs_connector_client.py b/tests/hosting_core/connector/test_mcs_connector_client.py new file mode 100644 index 000000000..efeada7ab --- /dev/null +++ b/tests/hosting_core/connector/test_mcs_connector_client.py @@ -0,0 +1,55 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +"""Tests for MCSConnectorClient User-Agent header behavior.""" + +import pytest +from aiohttp import web +from aiohttp.test_utils import TestServer + +from microsoft_agents.activity import Activity +from microsoft_agents.hosting.core.connector.get_product_info import get_product_info +from microsoft_agents.hosting.core.connector.mcs import MCSConnectorClient + + +class TestMCSConnectorClientUserAgentHeader: + """Ensures MCSConnectorClient sends a User-Agent header. + + NOTE: Unlike ConnectorClient/TeamsConnectorClient and UserTokenClient, + MCSConnectorClient does not currently set a custom "agents-sdk-py" User-Agent + header on its session or outgoing requests, so these tests currently fail. + """ + + @pytest.mark.asyncio + async def test_sets_user_agent_header(self): + client = MCSConnectorClient(endpoint="https://example.org/endpoint") + try: + assert client._client.headers.get("User-Agent") == get_product_info() + finally: + await client.close() + + @pytest.mark.asyncio + async def test_sends_user_agent_header_on_request(self): + captured = {} + + async def handler(request): + captured["user_agent"] = request.headers.get("User-Agent") + return web.json_response({"id": "activity-id-123"}) + + app = web.Application() + app.router.add_post("/endpoint", handler) + server = TestServer(app) + await server.start_server() + try: + endpoint = str(server.make_url("/endpoint")) + client = MCSConnectorClient(endpoint=endpoint) + try: + await client.conversations.send_to_conversation( + "conv-1", Activity(type="message", text="hi") + ) + finally: + await client.close() + finally: + await server.close() + + assert captured["user_agent"] == get_product_info() diff --git a/tests/hosting_core/connector/test_user_token_client.py b/tests/hosting_core/connector/test_user_token_client.py index ff08f7faf..9de278ded 100644 --- a/tests/hosting_core/connector/test_user_token_client.py +++ b/tests/hosting_core/connector/test_user_token_client.py @@ -21,6 +21,7 @@ UserToken, UserTokenClient, ) +from microsoft_agents.hosting.core.connector.get_product_info import get_product_info from microsoft_agents.hosting.core.header_propagation import HeaderPropagationContext @@ -407,3 +408,37 @@ async def handler(request): await server.close() assert exc_info.value.status == 500 + + +class TestUserTokenClientUserAgentHeader: + """Ensures UserTokenClient sends a User-Agent header.""" + + @pytest.mark.asyncio + async def test_sets_user_agent_header(self): + client = UserTokenClient("https://example.org/", token="", app_id="app-id") + try: + assert client.client.headers["User-Agent"] == get_product_info() + finally: + await client.close() + + @pytest.mark.asyncio + async def test_sends_user_agent_header_on_request(self): + captured = {} + + async def handler(request): + captured["user_agent"] = request.headers.get("User-Agent") + return web.json_response({"token": "token"}) + + app = web.Application() + app.router.add_get("/api/usertoken/GetToken", handler) + server = TestServer(app) + await server.start_server() + + client = UserTokenClient(str(server.make_url("/")), token="", app_id="app-id") + try: + await client.get_user_token("user", "connection", "msteams") + finally: + await client.close() + await server.close() + + assert captured["user_agent"] == get_product_info()