From 81d2677ca19199a9f1acc395b73b28d0d088e9a7 Mon Sep 17 00:00:00 2001 From: lilydu Date: Wed, 4 Mar 2026 14:56:39 -0800 Subject: [PATCH 1/3] fix keyboard interrupt traace --- packages/apps/src/microsoft_teams/apps/app.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/apps/src/microsoft_teams/apps/app.py b/packages/apps/src/microsoft_teams/apps/app.py index 76cf9b3b..5e35efb7 100644 --- a/packages/apps/src/microsoft_teams/apps/app.py +++ b/packages/apps/src/microsoft_teams/apps/app.py @@ -231,6 +231,10 @@ async def on_http_ready() -> None: tasks.append(plugin.on_start(event)) await asyncio.gather(*tasks) + except (asyncio.CancelledError, KeyboardInterrupt): + self.log.info("Teams app shutting down") + await self.stop() + except Exception as error: self._running = False self.log.error(f"Failed to start app: {error}") From 39a5c5850a6734ccedf8422f5f4e3d5e6fb1bb9c Mon Sep 17 00:00:00 2001 From: lilydu Date: Wed, 4 Mar 2026 15:49:00 -0800 Subject: [PATCH 2/3] fix for multiple plugins and add test --- packages/apps/src/microsoft_teams/apps/app.py | 8 ++- packages/apps/tests/test_app.py | 62 ++++++++++++++++++- 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/packages/apps/src/microsoft_teams/apps/app.py b/packages/apps/src/microsoft_teams/apps/app.py index 3a444027..3ea4a3e2 100644 --- a/packages/apps/src/microsoft_teams/apps/app.py +++ b/packages/apps/src/microsoft_teams/apps/app.py @@ -234,7 +234,13 @@ async def on_http_ready() -> None: except (asyncio.CancelledError, KeyboardInterrupt): self.log.info("Teams app shutting down") - await self.stop() + try: + for plugin in reversed(self.plugins): + if hasattr(plugin, "on_stop") and callable(plugin.on_stop): + await plugin.on_stop() + finally: + self._running = False + self._events.emit("stop", StopEvent()) except Exception as error: self._running = False diff --git a/packages/apps/tests/test_app.py b/packages/apps/tests/test_app.py index a27f4188..a905fb1f 100644 --- a/packages/apps/tests/test_app.py +++ b/packages/apps/tests/test_app.py @@ -21,7 +21,7 @@ TokenProtocol, TypingActivity, ) -from microsoft_teams.apps import ActivityContext, ActivityEvent, App, AppOptions +from microsoft_teams.apps import ActivityContext, ActivityEvent, App, AppOptions, Plugin, PluginBase, PluginStartEvent class FakeToken(TokenProtocol): @@ -167,6 +167,66 @@ async def mock_on_stop(): await app_with_options.stop() assert not app_with_options.is_running + @pytest.mark.asyncio + async def test_app_start_with_multiple_plugins_cancelled(self, mock_logger, mock_storage): + @Plugin(name="PluginTwo", version="1.0", description="plugin") + class PluginTwo(PluginBase): + def __init__(self): + super().__init__() + self.stop_called = False + + async def on_start(self, event: PluginStartEvent) -> None: # noqa: D102 + pass + + async def on_stop(self) -> None: # noqa: D102 + self.stop_called = True + + plugin_two = PluginTwo() + + options = AppOptions( + logger=mock_logger, + storage=mock_storage, + client_id="test-client-id", + client_secret="test-secret", + plugins=[plugin_two], + ) + app = App(**options) + + mock_stream = MagicMock() + mock_stream.events = MagicMock() + mock_stream.events.on = MagicMock() + mock_stream.close = AsyncMock() + app.http.create_stream = MagicMock(return_value=mock_stream) + + block = asyncio.Event() + + async def mock_on_start_blocking(event): + if app.http.on_ready_callback: + await app.http.on_ready_callback() + await block.wait() + + with patch.object(app.http, "on_start", new_callable=AsyncMock, side_effect=mock_on_start_blocking): + app.http.on_stop = AsyncMock() + + start_task = asyncio.create_task(app.start(3978)) + + for _ in range(50): + await asyncio.sleep(0.01) + if app.is_running: + break + + assert app.is_running, "App should be running before cancellation" + + start_task.cancel() + try: + await start_task + except asyncio.CancelledError: + pass + + mock_logger.info.assert_any_call("Teams app shutting down") + + assert plugin_two.stop_called, "plugin two on_stop was called." + # Event Testing - Focus on functional behavior @pytest.mark.asyncio From 6f7c2fa0a0d86dc93fa95007d0d09fbf6b783a50 Mon Sep 17 00:00:00 2001 From: lilydu Date: Mon, 9 Mar 2026 14:16:44 -0700 Subject: [PATCH 3/3] add assert that app is not running in test case --- packages/apps/tests/test_app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/apps/tests/test_app.py b/packages/apps/tests/test_app.py index a905fb1f..720e6d2a 100644 --- a/packages/apps/tests/test_app.py +++ b/packages/apps/tests/test_app.py @@ -226,6 +226,7 @@ async def mock_on_start_blocking(event): mock_logger.info.assert_any_call("Teams app shutting down") assert plugin_two.stop_called, "plugin two on_stop was called." + assert not app.is_running, "App should not be running after cancellation" # Event Testing - Focus on functional behavior