From a3af8eb205eccbbb8c8a498977d51fcacfd496c3 Mon Sep 17 00:00:00 2001 From: Chris Mullins Date: Tue, 14 Oct 2025 10:40:56 -0700 Subject: [PATCH 1/3] Cleanup test classes to eliminate warnings coming from PyTest. - Introduced `MockTestingAdapter` and `MockTestingCustomState` for unit testing. - Replaced instances of `TestingAdapter` with `MockTestingAdapter` in various test files. - Updated `test_agent_state.py` to utilize `_MockTestDataItem` instead of `TestDataItem`. - Added `ObsoleteTestClient` for potential future testing scenarios. - Refactored `test_turn_context.py` to use `_SimpleTestingAdapter` for context testing. - Ensured all tests are aligned with the new mock implementations for better isolation and control. --- ...test_client.py => obsolete_test_client.py} | 6 +- tests/_common/testing_objects/__init__.py | 8 +-- .../testing_objects/adapters/__init__.py | 4 +- ...ing_adapter.py => mock_testing_adapter.py} | 2 +- ..._state.py => mock_testing_custom_state.py} | 2 +- .../common/testing_environment.py | 4 +- tests/hosting_core/state/test_agent_state.py | 62 ++++++++++--------- .../test_transcript_logger_middleware.py | 10 +-- tests/hosting_core/test_turn_context.py | 32 +++++----- 9 files changed, 67 insertions(+), 63 deletions(-) rename tests/_common/{test_client.py => obsolete_test_client.py} (98%) rename tests/_common/testing_objects/adapters/{testing_adapter.py => mock_testing_adapter.py} (99%) rename tests/_common/testing_objects/{testing_custom_state.py => mock_testing_custom_state.py} (94%) diff --git a/tests/_common/test_client.py b/tests/_common/obsolete_test_client.py similarity index 98% rename from tests/_common/test_client.py rename to tests/_common/obsolete_test_client.py index 01d09ff4..d74e201d 100644 --- a/tests/_common/test_client.py +++ b/tests/_common/obsolete_test_client.py @@ -13,9 +13,9 @@ T = TypeVar("T") -# currently unused (probably outdated) -# but may be useful to bring up-to-date for future testing scenarios -class TestClient: +# This is a currently unused (probably outdated & deleted) test client that +# may be useful to bring up-to-date for future testing scenarios. +class ObsoleteTestClient: """ A testing channel that connects directly to an adapter. diff --git a/tests/_common/testing_objects/__init__.py b/tests/_common/testing_objects/__init__.py index 875165dc..76242453 100644 --- a/tests/_common/testing_objects/__init__.py +++ b/tests/_common/testing_objects/__init__.py @@ -1,4 +1,4 @@ -from .adapters import TestingAdapter +from .adapters import MockTestingAdapter from .mocks import ( MockMsalAuth, @@ -14,7 +14,7 @@ from .testing_authorization import TestingAuthorization from .testing_connection_manager import TestingConnectionManager -from .testing_custom_state import TestingCustomState +from .mock_testing_custom_state import MockTestingCustomState from .testing_token_provider import TestingTokenProvider from .testing_user_token_client import TestingUserTokenClient @@ -26,10 +26,10 @@ "mock_class_UserTokenClient", "TestingAuthorization", "TestingConnectionManager", - "TestingCustomState", + "MockTestingCustomState", "TestingTokenProvider", "TestingUserTokenClient", - "TestingAdapter", + "MockTestingAdapter", "mock_class_UserAuthorization", "mock_class_AgenticUserAuthorization", "mock_class_Authorization", diff --git a/tests/_common/testing_objects/adapters/__init__.py b/tests/_common/testing_objects/adapters/__init__.py index 4670c1df..c13a6564 100644 --- a/tests/_common/testing_objects/adapters/__init__.py +++ b/tests/_common/testing_objects/adapters/__init__.py @@ -1,3 +1,3 @@ -from .testing_adapter import TestingAdapter +from .mock_testing_adapter import MockTestingAdapter -__all__ = ["TestingAdapter"] +__all__ = ["MockTestingAdapter"] diff --git a/tests/_common/testing_objects/adapters/testing_adapter.py b/tests/_common/testing_objects/adapters/mock_testing_adapter.py similarity index 99% rename from tests/_common/testing_objects/adapters/testing_adapter.py rename to tests/_common/testing_objects/adapters/mock_testing_adapter.py index 38021bc4..4cd38b45 100644 --- a/tests/_common/testing_objects/adapters/testing_adapter.py +++ b/tests/_common/testing_objects/adapters/mock_testing_adapter.py @@ -27,7 +27,7 @@ AgentCallbackHandler = Callable[[TurnContext], Awaitable] -class TestingAdapter(ChannelAdapter): +class MockTestingAdapter(ChannelAdapter): """ A mock adapter that can be used for unit testing of agent logic. """ diff --git a/tests/_common/testing_objects/testing_custom_state.py b/tests/_common/testing_objects/mock_testing_custom_state.py similarity index 94% rename from tests/_common/testing_objects/testing_custom_state.py rename to tests/_common/testing_objects/mock_testing_custom_state.py index 1c6b9f93..8f856529 100644 --- a/tests/_common/testing_objects/testing_custom_state.py +++ b/tests/_common/testing_objects/mock_testing_custom_state.py @@ -1,7 +1,7 @@ from microsoft_agents.hosting.core import AgentState, Storage, StoreItem, TurnContext -class TestingCustomState(AgentState): +class MockTestingCustomState(AgentState): """Custom state implementation for testing.""" def __init__(self, storage: Storage, namespace: str = ""): diff --git a/tests/_integration/common/testing_environment.py b/tests/_integration/common/testing_environment.py index 7be1a0f1..562b57ff 100644 --- a/tests/_integration/common/testing_environment.py +++ b/tests/_integration/common/testing_environment.py @@ -14,7 +14,7 @@ from tests._common.testing_objects import ( TestingConnectionManager, - TestingAdapter, + MockTestingAdapter, ) @@ -62,7 +62,7 @@ def __init__(self, mocker): self.storage = MemoryStorage() self.connection_manager = TestingConnectionManager() - self.adapter = TestingAdapter() + self.adapter = MockTestingAdapter() self.authorization = Authorization( self.storage, self.connection_manager, **agents_sdk_config ) diff --git a/tests/hosting_core/state/test_agent_state.py b/tests/hosting_core/state/test_agent_state.py index 4a7b069d..c5b2a971 100644 --- a/tests/hosting_core/state/test_agent_state.py +++ b/tests/hosting_core/state/test_agent_state.py @@ -23,10 +23,10 @@ ChannelAccount, ConversationAccount, ) -from tests._common.testing_objects import TestingAdapter, TestingCustomState +from tests._common.testing_objects import MockTestingAdapter, MockTestingCustomState -class TestDataItem(StoreItem): +class _MockTestDataItem(StoreItem): """Test data item for testing state functionality.""" def __init__(self, value: str = None): @@ -36,8 +36,8 @@ def store_item_to_json(self) -> dict: return {"value": self.value} @staticmethod - def from_json_to_store_item(json_data: dict) -> "TestDataItem": - return TestDataItem(json_data.get("value", "default")) + def from_json_to_store_item(json_data: dict) -> "_MockTestDataItem": + return _MockTestDataItem(json_data.get("value", "default")) class TestAgentState: @@ -52,10 +52,10 @@ def setup_method(self): self.storage = MemoryStorage() self.user_state = UserState(self.storage) self.conversation_state = ConversationState(self.storage) - self.custom_state = TestingCustomState(self.storage) + self.custom_state = MockTestingCustomState(self.storage) # Create a test context - self.adapter = TestingAdapter() + self.adapter = MockTestingAdapter() self.activity = Activity( type=ActivityTypes.message, channel_id="test-channel", @@ -105,19 +105,19 @@ async def test_get_property_with_default_value(self): async def test_get_property_with_default_factory(self): """Test getting property with default factory function.""" property_name = "test_property" - default_factory = lambda: TestDataItem("factory_value") + default_factory = lambda: _MockTestDataItem("factory_value") property_accessor = self.user_state.create_property(property_name) # Test getting with factory value = await property_accessor.get(self.context, default_factory) - assert isinstance(value, TestDataItem) + assert isinstance(value, _MockTestDataItem) assert value.value == "factory_value" @pytest.mark.asyncio async def test_set_property_works(self): """Test setting property values.""" property_name = "test_property" - test_value = TestDataItem("test_value") + test_value = _MockTestDataItem("test_value") property_accessor = self.user_state.create_property(property_name) # Set the property @@ -125,14 +125,14 @@ async def test_set_property_works(self): # Verify it was set retrieved_value = await property_accessor.get(self.context) - assert isinstance(retrieved_value, TestDataItem) + assert isinstance(retrieved_value, _MockTestDataItem) assert retrieved_value.value == "test_value" @pytest.mark.asyncio async def test_delete_property_works(self): """Test deleting property values.""" property_name = "test_property" - test_value = TestDataItem("test_value") + test_value = _MockTestDataItem("test_value") property_accessor = self.user_state.create_property(property_name) # Set then delete the property @@ -187,7 +187,7 @@ async def test_state_save_with_changes(self): # Make a change property_accessor = self.user_state.create_property("test_property") - await property_accessor.set(self.context, TestDataItem("changed_value")) + await property_accessor.set(self.context, _MockTestDataItem("changed_value")) # Save changes await self.user_state.save(self.context) @@ -196,9 +196,9 @@ async def test_state_save_with_changes(self): fresh_context = TurnContext(self.adapter, self.activity) await self.user_state.load(fresh_context) fresh_accessor = self.user_state.create_property("test_property") - value = await fresh_accessor.get(fresh_context, target_cls=TestDataItem) + value = await fresh_accessor.get(fresh_context, target_cls=_MockTestDataItem) - assert isinstance(value, TestDataItem) + assert isinstance(value, _MockTestDataItem) assert value.value == "changed_value" @pytest.mark.asyncio @@ -231,8 +231,8 @@ async def test_multiple_state_instances(self): prop_1 = user_state_1.create_property("test_prop") prop_2 = user_state_2.create_property("test_prop") - await prop_1.set(self.context, TestDataItem("value1")) - await prop_2.set(self.context, TestDataItem("value2")) + await prop_1.set(self.context, _MockTestDataItem("value1")) + await prop_2.set(self.context, _MockTestDataItem("value2")) # Verify they are independent val_1 = await prop_1.get(self.context) @@ -247,7 +247,7 @@ async def test_state_persistence_across_contexts(self): # Set a value in first context property_accessor = self.user_state.create_property("persistent_prop") await self.user_state.load(self.context) - await property_accessor.set(self.context, TestDataItem("persistent_value")) + await property_accessor.set(self.context, _MockTestDataItem("persistent_value")) await self.user_state.save(self.context) # Create a new context with same user @@ -257,9 +257,11 @@ async def test_state_persistence_across_contexts(self): # Load state in new context await new_user_state.load(new_context) - value = await new_property_accessor.get(new_context, target_cls=TestDataItem) + value = await new_property_accessor.get( + new_context, target_cls=_MockTestDataItem + ) - assert isinstance(value, TestDataItem) + assert isinstance(value, _MockTestDataItem) assert value.value == "persistent_value" @pytest.mark.asyncio @@ -268,7 +270,7 @@ async def test_clear(self): # Set some values await self.user_state.load(self.context) prop_accessor = self.user_state.create_property("test_prop") - await prop_accessor.set(self.context, TestDataItem("test_value")) + await prop_accessor.set(self.context, _MockTestDataItem("test_value")) # Clear state self.user_state.clear(self.context) @@ -284,7 +286,7 @@ async def test_delete_state(self): # Set and save a value await self.user_state.load(self.context) prop_accessor = self.user_state.create_property("test_prop") - await prop_accessor.set(self.context, TestDataItem("test_value")) + await prop_accessor.set(self.context, _MockTestDataItem("test_value")) await self.user_state.save(self.context) # Delete state @@ -320,7 +322,7 @@ async def test_custom_state_implementation(self): await self.custom_state.load(self.context) prop_accessor = self.custom_state.create_property("custom_prop") - await prop_accessor.set(self.context, TestDataItem("custom_value")) + await prop_accessor.set(self.context, _MockTestDataItem("custom_value")) await self.custom_state.save(self.context) # Verify storage key format @@ -381,7 +383,7 @@ async def test_cached_state_hash_computation(self): # Make a change prop_accessor = self.user_state.create_property("test_prop") - await prop_accessor.set(self.context, TestDataItem("test_value")) + await prop_accessor.set(self.context, _MockTestDataItem("test_value")) # State should now be changed assert cached_state.is_changed @@ -397,7 +399,7 @@ async def test_concurrent_state_operations(self): # Set values concurrently await asyncio.gather( *[ - accessor.set(self.context, TestDataItem(f"value_{i}")) + accessor.set(self.context, _MockTestDataItem(f"value_{i}")) for i, accessor in enumerate(accessors) ] ) @@ -408,7 +410,7 @@ async def test_concurrent_state_operations(self): ) for i, value in enumerate(values): - assert isinstance(value, TestDataItem) + assert isinstance(value, _MockTestDataItem) assert value.value == f"value_{i}" @pytest.mark.asyncio @@ -422,8 +424,8 @@ async def test_state_isolation_between_different_state_types(self): user_prop = self.user_state.create_property("shared_prop") conv_prop = self.conversation_state.create_property("shared_prop") - await user_prop.set(self.context, TestDataItem("user_value")) - await conv_prop.set(self.context, TestDataItem("conversation_value")) + await user_prop.set(self.context, _MockTestDataItem("user_value")) + await conv_prop.set(self.context, _MockTestDataItem("conversation_value")) # Verify they are isolated user_value = await user_prop.get(self.context) @@ -461,12 +463,14 @@ async def test_memory_storage_integration(self): await user_state.load(self.context) prop_accessor = user_state.create_property("memory_test") - await prop_accessor.set(self.context, TestDataItem("memory_value")) + await prop_accessor.set(self.context, _MockTestDataItem("memory_value")) await user_state.save(self.context) # Verify data exists in memory storage storage_key = user_state.get_storage_key(self.context) - stored_data = await memory_storage.read([storage_key], target_cls=TestDataItem) + stored_data = await memory_storage.read( + [storage_key], target_cls=_MockTestDataItem + ) assert storage_key in stored_data assert stored_data[storage_key] is not None diff --git a/tests/hosting_core/storage/test_transcript_logger_middleware.py b/tests/hosting_core/storage/test_transcript_logger_middleware.py index 7065baa2..30ed2928 100644 --- a/tests/hosting_core/storage/test_transcript_logger_middleware.py +++ b/tests/hosting_core/storage/test_transcript_logger_middleware.py @@ -16,9 +16,9 @@ ) import pytest -from tests._common.testing_objects.adapters.testing_adapter import ( +from tests._common.testing_objects.adapters.mock_testing_adapter import ( AgentCallbackHandler, - TestingAdapter, + MockTestingAdapter, ) @@ -29,7 +29,7 @@ async def test_should_round_trip_via_middleware(): transcript_middleware = TranscriptLoggerMiddleware(transcript_store) channelName = "Channel1" - adapter = TestingAdapter(channelName) + adapter = MockTestingAdapter(channelName) adapter.use(transcript_middleware) id = ClaimsIdentity({}, True) @@ -66,7 +66,7 @@ async def test_should_write_to_file(): transcript_middleware = TranscriptLoggerMiddleware(file_store) channelName = "Channel1" - adapter = TestingAdapter(channelName) + adapter = MockTestingAdapter(channelName) adapter.use(transcript_middleware) id = ClaimsIdentity({}, True) @@ -94,7 +94,7 @@ async def test_should_write_to_console(): transcript_middleware = TranscriptLoggerMiddleware(store) channelName = "Channel1" - adapter = TestingAdapter(channelName) + adapter = MockTestingAdapter(channelName) adapter.use(transcript_middleware) id = ClaimsIdentity({}, True) diff --git a/tests/hosting_core/test_turn_context.py b/tests/hosting_core/test_turn_context.py index 263b856f..f4532653 100644 --- a/tests/hosting_core/test_turn_context.py +++ b/tests/hosting_core/test_turn_context.py @@ -27,7 +27,7 @@ ACTIVITY = Activity(**activity_data) -class TestingSimpleAdapter(ChannelAdapter): +class _SimpleTestingAdapter(ChannelAdapter): async def send_activities(self, context, activities) -> list[ResourceResponse]: responses = [] assert context is not None @@ -54,11 +54,11 @@ async def delete_activity(self, context, reference): class TestTurnContext: def test_should_create_context_with_request_and_adapter(self): - TurnContext(TestingSimpleAdapter(), ACTIVITY) + TurnContext(_SimpleTestingAdapter(), ACTIVITY) def test_should_not_create_context_without_request(self): try: - TurnContext(TestingSimpleAdapter(), None) + TurnContext(_SimpleTestingAdapter(), None) except TypeError: pass except Exception as error: @@ -73,12 +73,12 @@ def test_should_not_create_context_without_adapter(self): raise error def test_should_create_context_with_older_context(self): - context = TurnContext(TestingSimpleAdapter(), ACTIVITY) + context = TurnContext(_SimpleTestingAdapter(), ACTIVITY) TurnContext(context) def test_copy_to_should_copy_all_references(self): # pylint: disable=protected-access - old_adapter = TestingSimpleAdapter() + old_adapter = _SimpleTestingAdapter() old_activity = Activity(id="2", type="message", text="test copy") old_context = TurnContext(old_adapter, old_activity) old_context.responded = True @@ -105,7 +105,7 @@ async def update_activity_handler(context, activity, next_handler): old_context.on_delete_activity(delete_activity_handler) old_context.on_update_activity(update_activity_handler) - adapter = TestingSimpleAdapter() + adapter = _SimpleTestingAdapter() new_context = TurnContext(adapter, ACTIVITY) assert not new_context._on_send_activities # pylint: disable=protected-access assert not new_context._on_update_activity # pylint: disable=protected-access @@ -127,17 +127,17 @@ async def update_activity_handler(context, activity, next_handler): ) # pylint: disable=protected-access def test_responded_should_be_automatically_set_to_false(self): - context = TurnContext(TestingSimpleAdapter(), ACTIVITY) + context = TurnContext(_SimpleTestingAdapter(), ACTIVITY) assert context.responded is False def test_should_be_able_to_set_responded_to_true(self): - context = TurnContext(TestingSimpleAdapter(), ACTIVITY) + context = TurnContext(_SimpleTestingAdapter(), ACTIVITY) assert context.responded is False context.responded = True assert context.responded def test_should_not_be_able_to_set_responded_to_false(self): - context = TurnContext(TestingSimpleAdapter(), ACTIVITY) + context = TurnContext(_SimpleTestingAdapter(), ACTIVITY) try: context.responded = False except ValueError: @@ -147,7 +147,7 @@ def test_should_not_be_able_to_set_responded_to_false(self): @pytest.mark.asyncio async def test_should_call_on_delete_activity_handlers_before_deletion(self): - context = TurnContext(TestingSimpleAdapter(), ACTIVITY) + context = TurnContext(_SimpleTestingAdapter(), ACTIVITY) called = False async def delete_handler(context, reference, next_handler_coroutine): @@ -164,7 +164,7 @@ async def delete_handler(context, reference, next_handler_coroutine): @pytest.mark.asyncio async def test_should_call_multiple_on_delete_activity_handlers_in_order(self): - context = TurnContext(TestingSimpleAdapter(), ACTIVITY) + context = TurnContext(_SimpleTestingAdapter(), ACTIVITY) called_first = False called_second = False @@ -202,7 +202,7 @@ async def second_delete_handler(context, reference, next_handler_coroutine): @pytest.mark.asyncio async def test_should_call_send_on_activities_handler_before_send(self): - context = TurnContext(TestingSimpleAdapter(), ACTIVITY) + context = TurnContext(_SimpleTestingAdapter(), ACTIVITY) called = False async def send_handler(context, activities, next_handler_coroutine): @@ -219,7 +219,7 @@ async def send_handler(context, activities, next_handler_coroutine): @pytest.mark.asyncio async def test_should_call_on_update_activity_handler_before_update(self): - context = TurnContext(TestingSimpleAdapter(), ACTIVITY) + context = TurnContext(_SimpleTestingAdapter(), ACTIVITY) called = False async def update_handler(context, activity, next_handler_coroutine): @@ -237,7 +237,7 @@ async def update_handler(context, activity, next_handler_coroutine): @pytest.mark.asyncio async def test_update_activity_should_apply_conversation_reference(self): activity_id = "activity ID" - context = TurnContext(TestingSimpleAdapter(), ACTIVITY) + context = TurnContext(_SimpleTestingAdapter(), ACTIVITY) called = False async def update_handler(context, activity, next_handler_coroutine): @@ -300,7 +300,7 @@ def test_apply_conversation_reference_when_is_incoming_is_true_should_not_prepar async def test_should_get_conversation_reference_using_get_reply_conversation_reference( self, ): - context = TurnContext(TestingSimpleAdapter(), ACTIVITY) + context = TurnContext(_SimpleTestingAdapter(), ACTIVITY) reply = await context.send_activity("test") assert reply is not None @@ -398,7 +398,7 @@ def test_should_remove_custom_mention_from_activity(self): @pytest.mark.asyncio async def test_should_send_a_trace_activity(self): - context = TurnContext(TestingSimpleAdapter(), ACTIVITY) + context = TurnContext(_SimpleTestingAdapter(), ACTIVITY) called = False # pylint: disable=unused-argument From 53c172f97162cc4f50d35a8e67b3726a3bd610b3 Mon Sep 17 00:00:00 2001 From: Chris Mullins Date: Tue, 14 Oct 2025 12:52:21 -0700 Subject: [PATCH 2/3] Refactor test data organization and update configurations for consistency across tests Enable strict "warnings as errors" in PyTest. Fix associated issues and errors that came up. --- .../cosmos/cosmos_db_storage_config.py | 3 +- pytest.ini | 39 +++++++++++++++++++ tests/README.md | 21 ++++++++++ tests/_common/data/__init__.py | 30 +++++++------- .../{test_auth_data.py => auth_test_data.py} | 2 +- tests/_common/data/configs/__init__.py | 11 ++++-- .../data/configs/test_agentic_auth_config.py | 10 ++--- .../_common/data/configs/test_auth_config.py | 10 ++--- ...est_defaults.py => default_test_values.py} | 2 +- .../{test_flow_data.py => flow_test_data.py} | 6 +-- ...t_storage_data.py => storage_test_data.py} | 8 ++-- tests/_common/fixtures/flow_state_fixtures.py | 4 +- .../testing_objects/mocks/mock_oauth_flow.py | 4 +- tests/activity/test_activity.py | 5 +-- tests/activity/test_load_configuration.py | 4 +- .../_oauth/test_flow_storage_client.py | 4 +- tests/hosting_core/_oauth/test_oauth_flow.py | 8 ++-- tests/hosting_core/app/_oauth/_common.py | 4 +- tests/hosting_core/app/_oauth/_env.py | 4 +- .../test_agentic_user_authorization.py | 6 +-- .../_handlers/test_user_authorization.py | 21 +++++----- .../app/_oauth/test_auth_handler.py | 14 ++++--- .../app/_oauth/test_authorization.py | 26 ++++++------- 23 files changed, 158 insertions(+), 88 deletions(-) create mode 100644 pytest.ini rename tests/_common/data/{test_auth_data.py => auth_test_data.py} (98%) rename tests/_common/data/{test_defaults.py => default_test_values.py} (98%) rename tests/_common/data/{test_flow_data.py => flow_test_data.py} (96%) rename tests/_common/data/{test_storage_data.py => storage_test_data.py} (93%) diff --git a/libraries/microsoft-agents-storage-cosmos/microsoft_agents/storage/cosmos/cosmos_db_storage_config.py b/libraries/microsoft-agents-storage-cosmos/microsoft_agents/storage/cosmos/cosmos_db_storage_config.py index e70ec138..a5476e64 100644 --- a/libraries/microsoft-agents-storage-cosmos/microsoft_agents/storage/cosmos/cosmos_db_storage_config.py +++ b/libraries/microsoft-agents-storage-cosmos/microsoft_agents/storage/cosmos/cosmos_db_storage_config.py @@ -42,7 +42,8 @@ def __init__( """ config_file: str = kwargs.get("filename", "") if config_file: - kwargs = json.load(open(config_file)) + with open(config_file) as f: + kwargs = json.load(f) self.cosmos_db_endpoint: str = cosmos_db_endpoint or kwargs.get( "cosmos_db_endpoint", "" ) diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..479894a8 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,39 @@ +[pytest] +# Pytest configuration for Microsoft Agents for Python + +# Treat all warnings as errors by default +# This ensures that any code generating warnings will fail tests, +# promoting cleaner code and early detection of issues +filterwarnings = + error + # Ignore specific warnings that are not actionable or are from dependencies + ignore::DeprecationWarning:pkg_resources.* + ignore::DeprecationWarning:setuptools.* + ignore::PendingDeprecationWarning + # pytest-asyncio warnings that are safe to ignore + ignore:.*deprecated.*asyncio.*:DeprecationWarning:pytest_asyncio.* + +# Test discovery configuration +testpaths = tests +python_files = test_*.py *_test.py +python_classes = Test* +python_functions = test_* + +# Output configuration +addopts = + --strict-markers + --strict-config + --verbose + --tb=short + --durations=10 + +# Minimum version requirement +minversion = 6.0 + +# Markers for test categorization +markers = + unit: Unit tests + integration: Integration tests + slow: Slow tests that may take longer to run + requires_network: Tests that require network access + requires_auth: Tests that require authentication \ No newline at end of file diff --git a/tests/README.md b/tests/README.md index 5836ce59..bdf6e084 100644 --- a/tests/README.md +++ b/tests/README.md @@ -2,6 +2,27 @@ This document serves as a quick guide to the utilities we provide internally to test this SDK. More information will come with work on integration testing. +## Pytest Configuration + +The project uses a `pytest.ini` configuration file in the root directory that sets up the following testing standards: + +- **Warnings as Errors**: All warnings are treated as errors (`filterwarnings = error`) to ensure clean code and early detection of issues +- **Strict Configuration**: Uses `--strict-markers` and `--strict-config` to catch configuration issues +- **Test Discovery**: Automatically discovers tests in the `tests/` directory following standard naming conventions +- **Verbose Output**: Shows detailed test results with short tracebacks and duration information + +To run tests locally: +```bash +# Run all tests +python -m pytest + +# Run tests for a specific module +python -m pytest tests/activity/ + +# Run with custom options (these will be added to the configured defaults) +python -m pytest -x --lf # Stop on first failure, run last failed tests +``` + ## Storage Tests More info soon. For now, there are flags defined in the code that dictate whether the Cosmos DB and the Blob storage tests are run, as these tests rely on local emulators. diff --git a/tests/_common/data/__init__.py b/tests/_common/data/__init__.py index 0d43733e..b268dca8 100644 --- a/tests/_common/data/__init__.py +++ b/tests/_common/data/__init__.py @@ -1,21 +1,21 @@ -from .test_defaults import TEST_DEFAULTS -from .test_auth_data import ( - TEST_AUTH_DATA, +from .default_test_values import DEFAULT_TEST_VALUES +from .auth_test_data import ( + AUTH_TEST_DATA, create_test_auth_handler, ) -from .test_storage_data import TEST_STORAGE_DATA -from .test_flow_data import TEST_FLOW_DATA -from .configs import TEST_ENV_DICT, TEST_ENV -from .configs import TEST_AGENTIC_ENV_DICT, TEST_AGENTIC_ENV +from .storage_test_data import STORAGE_TEST_DATA +from .flow_test_data import FLOW_TEST_DATA +from .configs import NON_AGENTIC_TEST_ENV_DICT, NON_AGENTIC_TEST_ENV +from .configs import AGENTIC_TEST_ENV_DICT, AGENTIC_TEST_ENV __all__ = [ - "TEST_DEFAULTS", - "TEST_AUTH_DATA", - "TEST_STORAGE_DATA", - "TEST_FLOW_DATA", + "DEFAULT_TEST_VALUES", + "AUTH_TEST_DATA", + "STORAGE_TEST_DATA", + "FLOW_TEST_DATA", "create_test_auth_handler", - "TEST_ENV_DICT", - "TEST_ENV", - "TEST_AGENTIC_ENV_DICT", - "TEST_AGENTIC_ENV", + "NON_AGENTIC_TEST_ENV_DICT", + "NON_AGENTIC_TEST_ENV", + "AGENTIC_TEST_ENV_DICT", + "AGENTIC_TEST_ENV", ] diff --git a/tests/_common/data/test_auth_data.py b/tests/_common/data/auth_test_data.py similarity index 98% rename from tests/_common/data/test_auth_data.py rename to tests/_common/data/auth_test_data.py index 024fda0a..698cc1f5 100644 --- a/tests/_common/data/test_auth_data.py +++ b/tests/_common/data/auth_test_data.py @@ -29,7 +29,7 @@ def create_test_auth_handler( ) -class TEST_AUTH_DATA: +class AUTH_TEST_DATA: def __init__(self): self.auth_handler: AuthHandler = create_test_auth_handler("graph") diff --git a/tests/_common/data/configs/__init__.py b/tests/_common/data/configs/__init__.py index 450fb5c6..2b6ac80e 100644 --- a/tests/_common/data/configs/__init__.py +++ b/tests/_common/data/configs/__init__.py @@ -1,4 +1,9 @@ -from .test_auth_config import TEST_ENV_DICT, TEST_ENV -from .test_agentic_auth_config import TEST_AGENTIC_ENV_DICT, TEST_AGENTIC_ENV +from .test_auth_config import NON_AGENTIC_TEST_ENV_DICT, NON_AGENTIC_TEST_ENV +from .test_agentic_auth_config import AGENTIC_TEST_ENV_DICT, AGENTIC_TEST_ENV -__all__ = ["TEST_ENV_DICT", "TEST_ENV", "TEST_AGENTIC_ENV_DICT", "TEST_AGENTIC_ENV"] +__all__ = [ + "NON_AGENTIC_TEST_ENV_DICT", + "NON_AGENTIC_TEST_ENV", + "AGENTIC_TEST_ENV_DICT", + "AGENTIC_TEST_ENV", +] diff --git a/tests/_common/data/configs/test_agentic_auth_config.py b/tests/_common/data/configs/test_agentic_auth_config.py index 9de0f8bd..baa90b0b 100644 --- a/tests/_common/data/configs/test_agentic_auth_config.py +++ b/tests/_common/data/configs/test_agentic_auth_config.py @@ -1,9 +1,9 @@ from microsoft_agents.activity import load_configuration_from_env from ...create_env_var_dict import create_env_var_dict -from ..test_defaults import TEST_DEFAULTS +from ..default_test_values import DEFAULT_TEST_VALUES -DEFAULTS = TEST_DEFAULTS() +DEFAULTS = DEFAULT_TEST_VALUES() _TEST_AGENTIC_ENV_RAW = """ CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID=service-tenant-id @@ -46,9 +46,9 @@ ) -def TEST_AGENTIC_ENV(): +def AGENTIC_TEST_ENV(): return create_env_var_dict(_TEST_AGENTIC_ENV_RAW) -def TEST_AGENTIC_ENV_DICT(): - return load_configuration_from_env(TEST_AGENTIC_ENV()) +def AGENTIC_TEST_ENV_DICT(): + return load_configuration_from_env(AGENTIC_TEST_ENV()) diff --git a/tests/_common/data/configs/test_auth_config.py b/tests/_common/data/configs/test_auth_config.py index 67152bad..fbf86773 100644 --- a/tests/_common/data/configs/test_auth_config.py +++ b/tests/_common/data/configs/test_auth_config.py @@ -1,9 +1,9 @@ from microsoft_agents.activity import load_configuration_from_env from ...create_env_var_dict import create_env_var_dict -from ..test_defaults import TEST_DEFAULTS +from ..default_test_values import DEFAULT_TEST_VALUES -DEFAULTS = TEST_DEFAULTS() +DEFAULTS = DEFAULT_TEST_VALUES() _TEST_ENV_RAW = """ AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__{auth_handler_id}__SETTINGS__AZUREBOTOAUTHCONNECTIONNAME={abs_oauth_connection_name} @@ -20,9 +20,9 @@ ) -def TEST_ENV(): +def NON_AGENTIC_TEST_ENV(): return create_env_var_dict(_TEST_ENV_RAW) -def TEST_ENV_DICT(): - return load_configuration_from_env(TEST_ENV()) +def NON_AGENTIC_TEST_ENV_DICT(): + return load_configuration_from_env(NON_AGENTIC_TEST_ENV()) diff --git a/tests/_common/data/test_defaults.py b/tests/_common/data/default_test_values.py similarity index 98% rename from tests/_common/data/test_defaults.py rename to tests/_common/data/default_test_values.py index 58164e12..9687c5a6 100644 --- a/tests/_common/data/test_defaults.py +++ b/tests/_common/data/default_test_values.py @@ -6,7 +6,7 @@ ) -class TEST_DEFAULTS: +class DEFAULT_TEST_VALUES: def __init__(self): self.token = "__token" diff --git a/tests/_common/data/test_flow_data.py b/tests/_common/data/flow_test_data.py similarity index 96% rename from tests/_common/data/test_flow_data.py rename to tests/_common/data/flow_test_data.py index 5cfc6910..3f53befb 100644 --- a/tests/_common/data/test_flow_data.py +++ b/tests/_common/data/flow_test_data.py @@ -3,9 +3,9 @@ from microsoft_agents.hosting.core._oauth import _FlowState, _FlowStateTag from tests._common.storage import MockStoreItem -from tests._common.data.test_defaults import TEST_DEFAULTS +from tests._common.data.default_test_values import DEFAULT_TEST_VALUES -DEFAULTS = TEST_DEFAULTS() +DEFAULTS = DEFAULT_TEST_VALUES() DEF_FLOW_ARGS = { "ms_app_id": DEFAULTS.ms_app_id, @@ -15,7 +15,7 @@ } -class TEST_FLOW_DATA: +class FLOW_TEST_DATA: def __init__(self): self.not_started = _FlowState( diff --git a/tests/_common/data/test_storage_data.py b/tests/_common/data/storage_test_data.py similarity index 93% rename from tests/_common/data/test_storage_data.py rename to tests/_common/data/storage_test_data.py index 31cb0030..dd933cda 100644 --- a/tests/_common/data/test_storage_data.py +++ b/tests/_common/data/storage_test_data.py @@ -2,16 +2,16 @@ from tests._common.storage import MockStoreItem -from .test_flow_data import ( - TEST_FLOW_DATA, +from .flow_test_data import ( + FLOW_TEST_DATA, update_flow_state_handler, flow_key, ) -FLOW_DATA = TEST_FLOW_DATA() +FLOW_DATA = FLOW_TEST_DATA() -class TEST_STORAGE_DATA: +class STORAGE_TEST_DATA: def __init__(self): self.dict = { diff --git a/tests/_common/fixtures/flow_state_fixtures.py b/tests/_common/fixtures/flow_state_fixtures.py index 345235be..342618cf 100644 --- a/tests/_common/fixtures/flow_state_fixtures.py +++ b/tests/_common/fixtures/flow_state_fixtures.py @@ -2,9 +2,9 @@ from microsoft_agents.hosting.core._oauth import _FlowStateTag -from tests._common.data import TEST_FLOW_DATA +from tests._common.data import FLOW_TEST_DATA -FLOW_STATES = TEST_FLOW_DATA() +FLOW_STATES = FLOW_TEST_DATA() class FlowStateFixtures: diff --git a/tests/_common/testing_objects/mocks/mock_oauth_flow.py b/tests/_common/testing_objects/mocks/mock_oauth_flow.py index 53a066d3..e860f948 100644 --- a/tests/_common/testing_objects/mocks/mock_oauth_flow.py +++ b/tests/_common/testing_objects/mocks/mock_oauth_flow.py @@ -1,9 +1,9 @@ from microsoft_agents.activity import TokenResponse from microsoft_agents.hosting.core._oauth import _OAuthFlow -from tests._common.data import TEST_DEFAULTS +from tests._common.data import DEFAULT_TEST_VALUES -DEFAULTS = TEST_DEFAULTS() +DEFAULTS = DEFAULT_TEST_VALUES() def mock_OAuthFlow( diff --git a/tests/activity/test_activity.py b/tests/activity/test_activity.py index 40d695fe..2ad7d42c 100644 --- a/tests/activity/test_activity.py +++ b/tests/activity/test_activity.py @@ -21,9 +21,9 @@ from tests.activity._common.my_channel_data import MyChannelData from tests.activity._common.testing_activity import create_test_activity -from tests._common.data import TEST_DEFAULTS +from tests._common.data import DEFAULT_TEST_VALUES -DEFAULTS = TEST_DEFAULTS() +DEFAULTS = DEFAULT_TEST_VALUES() def helper_validate_recipient_and_from( @@ -375,7 +375,6 @@ def test_get_mentions(self): class TestActivityAgenticOps: - @pytest.fixture(params=[RoleTypes.user, RoleTypes.skill, RoleTypes.agent]) def non_agentic_role(self, request): return request.param diff --git a/tests/activity/test_load_configuration.py b/tests/activity/test_load_configuration.py index eb9dccac..26b881b1 100644 --- a/tests/activity/test_load_configuration.py +++ b/tests/activity/test_load_configuration.py @@ -1,9 +1,9 @@ from microsoft_agents.activity import load_configuration_from_env from tests._common import create_env_var_dict -from tests._common.data import TEST_DEFAULTS +from tests._common.data import DEFAULT_TEST_VALUES -DEFAULTS = TEST_DEFAULTS() +DEFAULTS = DEFAULT_TEST_VALUES() ENV_DICT = { "CONNECTIONS": { diff --git a/tests/hosting_core/_oauth/test_flow_storage_client.py b/tests/hosting_core/_oauth/test_flow_storage_client.py index c1710de1..0acd448c 100644 --- a/tests/hosting_core/_oauth/test_flow_storage_client.py +++ b/tests/hosting_core/_oauth/test_flow_storage_client.py @@ -4,9 +4,9 @@ from microsoft_agents.hosting.core._oauth import _FlowState, _FlowStorageClient from tests._common.storage.utils import MockStoreItem -from tests._common.data import TEST_DEFAULTS +from tests._common.data import DEFAULT_TEST_VALUES -DEFAULTS = TEST_DEFAULTS() +DEFAULTS = DEFAULT_TEST_VALUES() class TestFlowStorageClient: diff --git a/tests/hosting_core/_oauth/test_oauth_flow.py b/tests/hosting_core/_oauth/test_oauth_flow.py index 8e9681a1..9c27641e 100644 --- a/tests/hosting_core/_oauth/test_oauth_flow.py +++ b/tests/hosting_core/_oauth/test_oauth_flow.py @@ -16,13 +16,13 @@ _FlowResponse, ) -from tests._common.data import TEST_DEFAULTS, TEST_FLOW_DATA -from tests._common.data.test_storage_data import FLOW_DATA +from tests._common.data import DEFAULT_TEST_VALUES, FLOW_TEST_DATA +from tests._common.data.storage_test_data import STORAGE_TEST_DATA from tests._common.fixtures import FlowStateFixtures from tests._common.testing_objects import mock_UserTokenClient -DEFAULTS = TEST_DEFAULTS() -FLOW_DATA = TEST_FLOW_DATA() +DEFAULTS = DEFAULT_TEST_VALUES() +FLOW_DATA = FLOW_TEST_DATA() def create_testing_Activity( diff --git a/tests/hosting_core/app/_oauth/_common.py b/tests/hosting_core/app/_oauth/_common.py index 1a0dc422..c2a6d2f0 100644 --- a/tests/hosting_core/app/_oauth/_common.py +++ b/tests/hosting_core/app/_oauth/_common.py @@ -2,10 +2,10 @@ from microsoft_agents.hosting.core import TurnContext -from tests._common.data import TEST_DEFAULTS +from tests._common.data import DEFAULT_TEST_VALUES from tests._common.testing_objects import mock_UserTokenClient -DEFAULTS = TEST_DEFAULTS() +DEFAULTS = DEFAULT_TEST_VALUES() def create_testing_Activity(): diff --git a/tests/hosting_core/app/_oauth/_env.py b/tests/hosting_core/app/_oauth/_env.py index 160373d3..d9329fc6 100644 --- a/tests/hosting_core/app/_oauth/_env.py +++ b/tests/hosting_core/app/_oauth/_env.py @@ -1,6 +1,6 @@ -from tests._common.data import TEST_DEFAULTS +from tests._common.data import DEFAULT_TEST_VALUES -DEFAULTS = TEST_DEFAULTS() +DEFAULTS = DEFAULT_TEST_VALUES() def ENV_CONFIG(): diff --git a/tests/hosting_core/app/_oauth/_handlers/test_agentic_user_authorization.py b/tests/hosting_core/app/_oauth/_handlers/test_agentic_user_authorization.py index 260f0c87..78f2c512 100644 --- a/tests/hosting_core/app/_oauth/_handlers/test_agentic_user_authorization.py +++ b/tests/hosting_core/app/_oauth/_handlers/test_agentic_user_authorization.py @@ -14,15 +14,15 @@ from microsoft_agents.hosting.core.storage import MemoryStorage from microsoft_agents.hosting.core._oauth import _FlowStateTag -from tests._common.data import TEST_DEFAULTS, TEST_AGENTIC_ENV_DICT +from tests._common.data import DEFAULT_TEST_VALUES, AGENTIC_TEST_ENV_DICT from tests._common.mock_utils import mock_class from .._common import ( create_testing_TurnContext_magic, ) -DEFAULTS = TEST_DEFAULTS() -AGENTIC_ENV_DICT = TEST_AGENTIC_ENV_DICT() +DEFAULTS = DEFAULT_TEST_VALUES() +AGENTIC_ENV_DICT = AGENTIC_TEST_ENV_DICT() class TestUtils: diff --git a/tests/hosting_core/app/_oauth/_handlers/test_user_authorization.py b/tests/hosting_core/app/_oauth/_handlers/test_user_authorization.py index cc3dd7bd..61ae3ccf 100644 --- a/tests/hosting_core/app/_oauth/_handlers/test_user_authorization.py +++ b/tests/hosting_core/app/_oauth/_handlers/test_user_authorization.py @@ -17,12 +17,13 @@ # test constants from tests._common.data import ( - TEST_FLOW_DATA, - TEST_AUTH_DATA, - TEST_STORAGE_DATA, - TEST_DEFAULTS, - TEST_AGENTIC_ENV_DICT, + FLOW_TEST_DATA, + AUTH_TEST_DATA, + STORAGE_TEST_DATA, + DEFAULT_TEST_VALUES, + AGENTIC_TEST_ENV_DICT, ) +from tests._common.data.storage_test_data import STORAGE_TEST_DATA from tests._common.mock_utils import mock_instance from tests._common.fixtures import FlowStateFixtures from tests._common.testing_objects import ( @@ -31,10 +32,10 @@ ) from tests.hosting_core._common import flow_state_eq -DEFAULTS = TEST_DEFAULTS() -FLOW_DATA = TEST_FLOW_DATA() -STORAGE_DATA = TEST_STORAGE_DATA() -AGENTIC_ENV_DICT = TEST_AGENTIC_ENV_DICT() +DEFAULTS = DEFAULT_TEST_VALUES() +FLOW_DATA = FLOW_TEST_DATA() +STORAGE_DATA = STORAGE_TEST_DATA() +AGENTIC_ENV_DICT = AGENTIC_TEST_ENV_DICT() def make_jwt(token: str = DEFAULTS.token, aud="api://default"): @@ -110,7 +111,7 @@ def connection_manager(self): @pytest.fixture def auth_handlers(self): - return TEST_AUTH_DATA().auth_handlers + return AUTH_TEST_DATA().auth_handlers @pytest.fixture def auth_handler_settings(self): diff --git a/tests/hosting_core/app/_oauth/test_auth_handler.py b/tests/hosting_core/app/_oauth/test_auth_handler.py index ccaf15ec..b362891e 100644 --- a/tests/hosting_core/app/_oauth/test_auth_handler.py +++ b/tests/hosting_core/app/_oauth/test_auth_handler.py @@ -2,11 +2,15 @@ from microsoft_agents.hosting.core import AuthHandler -from tests._common.data import TEST_DEFAULTS, TEST_ENV_DICT, TEST_AGENTIC_ENV_DICT - -DEFAULTS = TEST_DEFAULTS() -ENV_DICT = TEST_ENV_DICT() -AGENTIC_ENV_DICT = TEST_AGENTIC_ENV_DICT() +from tests._common.data import ( + DEFAULT_TEST_VALUES, + NON_AGENTIC_TEST_ENV_DICT, + AGENTIC_TEST_ENV_DICT, +) + +DEFAULTS = DEFAULT_TEST_VALUES() +ENV_DICT = NON_AGENTIC_TEST_ENV_DICT() +AGENTIC_ENV_DICT = AGENTIC_TEST_ENV_DICT() class TestAuthHandler: diff --git a/tests/hosting_core/app/_oauth/test_authorization.py b/tests/hosting_core/app/_oauth/test_authorization.py index 8b0d99e6..56610879 100644 --- a/tests/hosting_core/app/_oauth/test_authorization.py +++ b/tests/hosting_core/app/_oauth/test_authorization.py @@ -28,12 +28,12 @@ # test constants from tests._common.data import ( - TEST_FLOW_DATA, - TEST_AUTH_DATA, - TEST_STORAGE_DATA, - TEST_DEFAULTS, - TEST_ENV_DICT, - TEST_AGENTIC_ENV_DICT, + FLOW_TEST_DATA, + AUTH_TEST_DATA, + STORAGE_TEST_DATA, + DEFAULT_TEST_VALUES, + NON_AGENTIC_TEST_ENV_DICT, + AGENTIC_TEST_ENV_DICT, ) from tests._common.fixtures import FlowStateFixtures from tests._common.testing_objects import ( @@ -46,11 +46,11 @@ from ._common import create_testing_TurnContext, create_testing_Activity -DEFAULTS = TEST_DEFAULTS() -FLOW_DATA = TEST_FLOW_DATA() -STORAGE_DATA = TEST_STORAGE_DATA() -ENV_DICT = TEST_ENV_DICT() -AGENTIC_ENV_DICT = TEST_AGENTIC_ENV_DICT() +DEFAULTS = DEFAULT_TEST_VALUES() +FLOW_DATA = FLOW_TEST_DATA() +STORAGE_DATA = STORAGE_TEST_DATA() +ENV_DICT = NON_AGENTIC_TEST_ENV_DICT() +AGENTIC_ENV_DICT = AGENTIC_TEST_ENV_DICT() def make_jwt(token: str = DEFAULTS.token, aud="api://default"): @@ -123,7 +123,7 @@ def activity(self): @pytest.fixture def baseline_storage(self): - return StorageBaseline(TEST_STORAGE_DATA().dict) + return StorageBaseline(STORAGE_TEST_DATA().dict) @pytest.fixture def storage(self): @@ -135,7 +135,7 @@ def connection_manager(self, mocker): @pytest.fixture def auth_handlers(self): - return TEST_AUTH_DATA().auth_handlers + return AUTH_TEST_DATA().auth_handlers @pytest.fixture def authorization(self, connection_manager, storage): From 75fdaa762fc15f6c998bcd76d81eb58c09e23850 Mon Sep 17 00:00:00 2001 From: Chris Mullins Date: Tue, 14 Oct 2025 13:07:01 -0700 Subject: [PATCH 3/3] Fix import statement to use DEFAULT_TEST_VALUES for consistency in test setup --- tests/activity/test_channel_id.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/activity/test_channel_id.py b/tests/activity/test_channel_id.py index ef592243..5c9ce25c 100644 --- a/tests/activity/test_channel_id.py +++ b/tests/activity/test_channel_id.py @@ -2,13 +2,12 @@ from microsoft_agents.activity import ChannelId -from tests._common.data import TEST_DEFAULTS +from tests._common.data import DEFAULT_TEST_VALUES -DEFAULTS = TEST_DEFAULTS() +DEFAULTS = DEFAULT_TEST_VALUES() class TestChannelId: - def test_init_from_str(self): channel_id = ChannelId("email:support") assert channel_id.channel == "email"