diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/agent_application.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/agent_application.py index 9f8bf220..1368f624 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/agent_application.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/agent_application.py @@ -76,6 +76,7 @@ class AgentApplication(Agent, Generic[StateT]): _route_list: _RouteList[StateT] _error: Optional[Callable[[TurnContext, Exception], Awaitable[None]]] = None _turn_state_factory: Optional[Callable[[TurnContext], StateT]] = None + _connection_manager: Connections def __init__( self, @@ -159,7 +160,15 @@ def __init__( # TODO: decide how to initialize the Authorization (params vs options vs kwargs) if authorization: + if connection_manager: + logger.error( + "AgentApplication: connection_manager is not needed when authorization is provided." + ) + raise ApplicationError( + "The `AgentApplication` does not take a `connection_manager` when `authorization` is provided." + ) self._auth = authorization + self._connection_manager = self._auth.connection_manager else: if not connection_manager: logger.error( @@ -178,9 +187,20 @@ def __init__( self._auth = Authorization( storage=self._options.storage, connection_manager=connection_manager, - handlers=options.authorization_handlers, + auth_handlers=options.authorization_handlers, **auth_options, ) + self._connection_manager = connection_manager + + @property + def connection_manager(self) -> Connections: + """ + The application's connection manager. + + :return: The connection manager for the application. + :rtype: :class:`microsoft_agents.hosting.core.authorization.Connections` + """ + return self._connection_manager @property def adapter(self) -> ChannelServiceAdapter: diff --git a/tests/hosting_core/app/test_agent_application.py b/tests/hosting_core/app/test_agent_application.py index 54871803..30c56f73 100644 --- a/tests/hosting_core/app/test_agent_application.py +++ b/tests/hosting_core/app/test_agent_application.py @@ -433,3 +433,50 @@ async def on_event(ctx, state): await app.on_turn(StubTurnContext(_make_event_activity())) assert calls == ["event", "after"] + + +# --------------------------------------------------------------------------- +# connection_manager property and constructor guard +# --------------------------------------------------------------------------- + + +def test_init_raises_when_both_authorization_and_connection_manager_provided(): + """Providing both authorization and connection_manager is ambiguous and should raise.""" + with pytest.raises(ApplicationError): + AgentApplication[TurnState]( + options=ApplicationOptions(storage=MemoryStorage()), + authorization=make_auth(), + connection_manager=_ConnectionManager(), + ) + + +def test_connection_manager_property_returns_manager_from_authorization(): + """When authorization is provided, connection_manager property reflects auth's manager.""" + cm = _ConnectionManager() + auth = Authorization(storage=MemoryStorage(), connection_manager=cm) + app = AgentApplication[TurnState]( + options=ApplicationOptions(storage=MemoryStorage()), + authorization=auth, + ) + assert app.connection_manager is cm + + +def test_connection_manager_property_returns_directly_passed_manager(): + """When connection_manager kwarg is used (no authorization), the property returns it.""" + cm = _ConnectionManager() + app = AgentApplication[TurnState]( + options=ApplicationOptions(storage=MemoryStorage()), + connection_manager=cm, + ) + assert app.connection_manager is cm + + +def test_init_with_connection_manager_only_creates_auth(): + """Passing only connection_manager should auto-create an Authorization instance.""" + cm = _ConnectionManager() + app = AgentApplication[TurnState]( + options=ApplicationOptions(storage=MemoryStorage()), + connection_manager=cm, + ) + assert app.auth is not None + assert app.connection_manager is cm