From 9f70ddbccbeae2ed527beb90e245176f38841774 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Thu, 23 Oct 2025 10:00:15 -0700 Subject: [PATCH 1/6] Implementing _DeferredString utility --- .../activity/_utils/__init__.py | 4 ++++ .../activity/_utils/_deferred_string.py | 11 ++++++++++ .../authentication/msal/msal_auth.py | 20 +++++++------------ 3 files changed, 22 insertions(+), 13 deletions(-) create mode 100644 libraries/microsoft-agents-activity/microsoft_agents/activity/_utils/__init__.py create mode 100644 libraries/microsoft-agents-activity/microsoft_agents/activity/_utils/_deferred_string.py diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/_utils/__init__.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/_utils/__init__.py new file mode 100644 index 00000000..a3c0a277 --- /dev/null +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/_utils/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from ._deferred_string import _DeferredString \ No newline at end of file diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/_utils/_deferred_string.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/_utils/_deferred_string.py new file mode 100644 index 00000000..550fd881 --- /dev/null +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/_utils/_deferred_string.py @@ -0,0 +1,11 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +class _DeferredString: + def __init__(self, func, *args, **kwargs): + self.func = func + self.args = args + self.kwargs = kwargs + + def __str__(self): + return str(self.func(*self.args, **self.kwargs)) \ No newline at end of file diff --git a/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_auth.py b/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_auth.py index f9486cc4..a6cedee9 100644 --- a/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_auth.py +++ b/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_auth.py @@ -19,6 +19,8 @@ from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes +from microsoft_agents.activity._utils import DeferredString + from microsoft_agents.hosting.core import ( AuthTypes, AccessTokenProviderBase, @@ -28,18 +30,6 @@ logger = logging.getLogger(__name__) -# this is deferred because jwt.decode is expensive and we don't want to do it unless we -# have logging.DEBUG enabled -class _DeferredLogOfBlueprintId: - def __init__(self, jwt_token: str): - self.jwt_token = jwt_token - - def __str__(self): - payload = jwt.decode(self.jwt_token, options={"verify_signature": False}) - agentic_blueprint_id = payload.get("xms_par_app_azp") - return f"Agentic blueprint id: {agentic_blueprint_id}" - - async def _async_acquire_token_for_client(msal_auth_client, *args, **kwargs): """MSAL in Python does not support async, so we use asyncio.to_thread to run it in a separate thread and avoid blocking the event loop @@ -328,7 +318,11 @@ async def get_agentic_instance_token( ) raise ValueError(f"Failed to acquire token. {str(agentic_instance_token)}") - logger.debug(_DeferredLogOfBlueprintId(token)) + logger.debug("Agentic blueprint id: %s", + _DeferredString( + lambda: jwt.decode(token, options={"verify_signature": False}).get("xms_par_app_azp") + ) + ) return agentic_instance_token["access_token"], agent_token_result From 54986c4c414f3d69f41634808ff3e09d1492cc26 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Thu, 23 Oct 2025 11:41:07 -0700 Subject: [PATCH 2/6] Reformatting --- .../microsoft_agents/activity/_utils/__init__.py | 4 +++- .../microsoft_agents/activity/_utils/_deferred_string.py | 5 +++-- .../microsoft_agents/authentication/msal/msal_auth.py | 9 ++++++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/_utils/__init__.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/_utils/__init__.py index a3c0a277..bedb475d 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/_utils/__init__.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/_utils/__init__.py @@ -1,4 +1,6 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -from ._deferred_string import _DeferredString \ No newline at end of file +from ._deferred_string import _DeferredString + +__all__ = ["_DeferredString"] diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/_utils/_deferred_string.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/_utils/_deferred_string.py index 550fd881..0fa13bd6 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/_utils/_deferred_string.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/_utils/_deferred_string.py @@ -1,6 +1,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. - + + class _DeferredString: def __init__(self, func, *args, **kwargs): self.func = func @@ -8,4 +9,4 @@ def __init__(self, func, *args, **kwargs): self.kwargs = kwargs def __str__(self): - return str(self.func(*self.args, **self.kwargs)) \ No newline at end of file + return str(self.func(*self.args, **self.kwargs)) diff --git a/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_auth.py b/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_auth.py index a6cedee9..ea3e35de 100644 --- a/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_auth.py +++ b/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_auth.py @@ -318,10 +318,13 @@ async def get_agentic_instance_token( ) raise ValueError(f"Failed to acquire token. {str(agentic_instance_token)}") - logger.debug("Agentic blueprint id: %s", + logger.debug( + "Agentic blueprint id: %s", _DeferredString( - lambda: jwt.decode(token, options={"verify_signature": False}).get("xms_par_app_azp") - ) + lambda: jwt.decode(token, options={"verify_signature": False}).get( + "xms_par_app_azp" + ) + ), ) return agentic_instance_token["access_token"], agent_token_result From e94198f6074ad57c7f0e818e193dec215a8cf9a5 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Thu, 23 Oct 2025 14:22:21 -0700 Subject: [PATCH 3/6] Formatting --- .../microsoft_agents/authentication/msal/msal_auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_auth.py b/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_auth.py index ea3e35de..073c3a6a 100644 --- a/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_auth.py +++ b/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_auth.py @@ -19,7 +19,7 @@ from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes -from microsoft_agents.activity._utils import DeferredString +from microsoft_agents.activity._utils import _DeferredString from microsoft_agents.hosting.core import ( AuthTypes, From 86c33ae4c06668821f0b5e809c3bb14b1bf98374 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Thu, 23 Oct 2025 14:27:28 -0700 Subject: [PATCH 4/6] Adding error handling to avoid raising exceptions from deferred strings --- .../activity/_utils/_deferred_string.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/_utils/_deferred_string.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/_utils/_deferred_string.py index 0fa13bd6..39eb10ad 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/_utils/_deferred_string.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/_utils/_deferred_string.py @@ -1,6 +1,10 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +import logging + +logger = logging.getLogger(__name__) + class _DeferredString: def __init__(self, func, *args, **kwargs): @@ -9,4 +13,8 @@ def __init__(self, func, *args, **kwargs): self.kwargs = kwargs def __str__(self): - return str(self.func(*self.args, **self.kwargs)) + try: + return str(self.func(*self.args, **self.kwargs)) + except Exception as e: + logger.error("Error evaluating deferred string", exc_info=e) + return "_DeferredString: error evaluating deferred string" From 13f2d046a2a825aa9b5480138ae8f0d59b7dbf18 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Fri, 24 Oct 2025 07:49:06 -0700 Subject: [PATCH 5/6] Adding tests --- tests/activity/utils/__init__.py | 0 tests/activity/utils/test_deferred_string.py | 254 +++++++++++++++++++ 2 files changed, 254 insertions(+) create mode 100644 tests/activity/utils/__init__.py create mode 100644 tests/activity/utils/test_deferred_string.py diff --git a/tests/activity/utils/__init__.py b/tests/activity/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/activity/utils/test_deferred_string.py b/tests/activity/utils/test_deferred_string.py new file mode 100644 index 00000000..ae694b54 --- /dev/null +++ b/tests/activity/utils/test_deferred_string.py @@ -0,0 +1,254 @@ +import pytest +import logging +from unittest.mock import Mock, patch +from io import StringIO + +from microsoft_agents.activity._utils import _DeferredString + + +class TestDeferredString: + """Test suite for _DeferredString class.""" + + def test_deferred_string_evaluation_basic(self): + """Test basic string evaluation with function and args.""" + def sample_func(x, y): + return f"Result is {x + y}" + + deferred = _DeferredString(sample_func, 2, 3) + assert str(deferred) == "Result is 5" + + def test_deferred_string_evaluation_with_kwargs(self): + """Test string evaluation with keyword arguments.""" + def sample_func(a, b=0, c=1): + return f"Sum is {a + b + c}" + + deferred = _DeferredString(sample_func, 5, b=10, c=15) + assert str(deferred) == "Sum is 30" + + def test_deferred_string_evaluation_mixed_args(self): + """Test string evaluation with both positional and keyword arguments.""" + def sample_func(prefix, value, suffix="end"): + return f"{prefix}-{value}-{suffix}" + + deferred = _DeferredString(sample_func, "start", 42, suffix="finish") + assert str(deferred) == "start-42-finish" + + def test_deferred_string_no_args(self): + """Test string evaluation with no arguments.""" + def simple_func(): + return "No args here" + + deferred = _DeferredString(simple_func) + assert str(deferred) == "No args here" + + def test_deferred_string_complex_return_type(self): + """Test that non-string return values are converted to strings.""" + def return_number(): + return 42 + + deferred = _DeferredString(return_number) + assert str(deferred) == "42" + + def test_deferred_string_none_return(self): + """Test handling of None return value.""" + def return_none(): + return None + + deferred = _DeferredString(return_none) + assert str(deferred) == "None" + + def test_deferred_string_exception_handling(self, caplog): + """Test exception handling during function evaluation.""" + def faulty_func(): + raise ValueError("Intentional error") + + deferred = _DeferredString(faulty_func) + + with caplog.at_level(logging.ERROR): + result = str(deferred) + + assert result == "_DeferredString: error evaluating deferred string" + assert any("Error evaluating deferred string" in message for message in caplog.messages) + + def test_deferred_string_exception_with_args(self, caplog): + """Test exception handling when function is called with arguments.""" + def faulty_func(x, y): + raise RuntimeError("Something went wrong") + + deferred = _DeferredString(faulty_func, 1, 2) + + with caplog.at_level(logging.ERROR): + result = str(deferred) + + assert result == "_DeferredString: error evaluating deferred string" + assert "Error evaluating deferred string" in caplog.text + + def test_deferred_string_logging_integration(self): + """Test integration with logging module using deferred string in log messages.""" + # Create a string buffer to capture log output + log_capture_string = StringIO() + ch = logging.StreamHandler(log_capture_string) + ch.setLevel(logging.INFO) + + # Create a logger and add the handler + test_logger = logging.getLogger('test_deferred_logger') + test_logger.setLevel(logging.INFO) + test_logger.addHandler(ch) + + def expensive_operation(): + return "Expensive computation result" + + deferred = _DeferredString(expensive_operation) + + # Log a message with the deferred string + test_logger.info("Processing complete: %s", deferred) + + # Get the log output and verify the deferred string was evaluated + log_contents = log_capture_string.getvalue() + assert "Processing complete: Expensive computation result" in log_contents + + # Clean up + test_logger.removeHandler(ch) + + def test_deferred_string_lazy_evaluation(self): + """Test that the function is only called when string conversion occurs.""" + call_count = 0 + + def counting_func(): + nonlocal call_count + call_count += 1 + return f"Called {call_count} times" + + deferred = _DeferredString(counting_func) + + # Function should not be called yet + assert call_count == 0 + + # First string conversion should call the function + result1 = str(deferred) + assert call_count == 1 + assert result1 == "Called 1 times" + + # Second string conversion should call the function again + result2 = str(deferred) + assert call_count == 2 + assert result2 == "Called 2 times" + + def test_deferred_string_with_logger_level_filtering(self): + """Test that deferred strings are only evaluated when log level allows it.""" + call_count = 0 + + def expensive_func(): + nonlocal call_count + call_count += 1 + return "Expensive result" + + deferred = _DeferredString(expensive_func) + + # Create logger with high level (ERROR) + test_logger = logging.getLogger('level_test_logger') + test_logger.setLevel(logging.ERROR) + + # Log at DEBUG level - should not evaluate deferred string due to level filtering + test_logger.debug("Debug message: %s", deferred) + assert call_count == 0 + + # Log at ERROR level - should evaluate deferred string + test_logger.error("Error message: %s", deferred) + assert call_count == 1 + + def test_deferred_string_with_multiple_evaluations_in_logging(self): + """Test multiple deferred strings in the same log message.""" + def func1(): + return "First" + + def func2(): + return "Second" + + deferred1 = _DeferredString(func1) + deferred2 = _DeferredString(func2) + + log_capture_string = StringIO() + ch = logging.StreamHandler(log_capture_string) + test_logger = logging.getLogger('multi_deferred_logger') + test_logger.setLevel(logging.INFO) + test_logger.addHandler(ch) + + test_logger.info("Results: %s and %s", deferred1, deferred2) + + log_contents = log_capture_string.getvalue() + assert "Results: First and Second" in log_contents + + test_logger.removeHandler(ch) + + def test_deferred_string_error_logging_details(self, caplog): + """Test that error logging includes exception details.""" + def func_with_specific_error(): + raise KeyError("Missing key 'test'") + + deferred = _DeferredString(func_with_specific_error) + + with caplog.at_level(logging.ERROR): + result = str(deferred) + + assert result == "_DeferredString: error evaluating deferred string" + + # Check that the original exception information is preserved in logs + assert len(caplog.records) == 1 + error_record = caplog.records[0] + assert error_record.levelname == "ERROR" + assert error_record.getMessage() == "Error evaluating deferred string" + assert error_record.exc_info is not None + assert issubclass(error_record.exc_info[0], KeyError) + + @patch('microsoft_agents.activity._utils._deferred_string.logger') + def test_deferred_string_logger_mock(self, mock_logger): + """Test that the correct logger is used for error reporting.""" + def failing_func(): + raise Exception("Test exception") + + deferred = _DeferredString(failing_func) + str(deferred) + + mock_logger.error.assert_called_once_with( + "Error evaluating deferred string", + exc_info=Exception("Test exception") + ) + + def test_deferred_string_with_lambda(self): + """Test deferred string with lambda functions.""" + deferred = _DeferredString(lambda x: f"Lambda result: {x}", "test") + assert str(deferred) == "Lambda result: test" + + def test_deferred_string_with_method_call(self): + """Test deferred string with method calls.""" + class TestClass: + def __init__(self, value): + self.value = value + + def get_description(self): + return f"Value is {self.value}" + + obj = TestClass(100) + deferred = _DeferredString(obj.get_description) + assert str(deferred) == "Value is 100" + + def test_deferred_string_preserves_function_state(self): + """Test that deferred string preserves the state when function is created.""" + values = [] + + def append_and_return(): + values.append(len(values)) + return f"List length: {len(values)}" + + deferred = _DeferredString(append_and_return) + + # Each evaluation should append to the list + result1 = str(deferred) + assert result1 == "List length: 1" + + result2 = str(deferred) + assert result2 == "List length: 2" + + assert len(values) == 2 + assert values == [0, 1] \ No newline at end of file From ff42ecc5ad830974195318249160c5f28c9d30fb Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Fri, 24 Oct 2025 11:11:58 -0700 Subject: [PATCH 6/6] Adding DeferredString unit tests --- tests/activity/utils/test_deferred_string.py | 150 ++++-------------- .../hosting_core/app/test_typing_indicator.py | 4 +- 2 files changed, 35 insertions(+), 119 deletions(-) diff --git a/tests/activity/utils/test_deferred_string.py b/tests/activity/utils/test_deferred_string.py index ae694b54..75ac24b7 100644 --- a/tests/activity/utils/test_deferred_string.py +++ b/tests/activity/utils/test_deferred_string.py @@ -1,6 +1,5 @@ -import pytest import logging -from unittest.mock import Mock, patch +from unittest.mock import patch from io import StringIO from microsoft_agents.activity._utils import _DeferredString @@ -11,6 +10,7 @@ class TestDeferredString: def test_deferred_string_evaluation_basic(self): """Test basic string evaluation with function and args.""" + def sample_func(x, y): return f"Result is {x + y}" @@ -19,6 +19,7 @@ def sample_func(x, y): def test_deferred_string_evaluation_with_kwargs(self): """Test string evaluation with keyword arguments.""" + def sample_func(a, b=0, c=1): return f"Sum is {a + b + c}" @@ -27,6 +28,7 @@ def sample_func(a, b=0, c=1): def test_deferred_string_evaluation_mixed_args(self): """Test string evaluation with both positional and keyword arguments.""" + def sample_func(prefix, value, suffix="end"): return f"{prefix}-{value}-{suffix}" @@ -35,6 +37,7 @@ def sample_func(prefix, value, suffix="end"): def test_deferred_string_no_args(self): """Test string evaluation with no arguments.""" + def simple_func(): return "No args here" @@ -43,6 +46,7 @@ def simple_func(): def test_deferred_string_complex_return_type(self): """Test that non-string return values are converted to strings.""" + def return_number(): return 42 @@ -51,6 +55,7 @@ def return_number(): def test_deferred_string_none_return(self): """Test handling of None return value.""" + def return_none(): return None @@ -59,6 +64,7 @@ def return_none(): def test_deferred_string_exception_handling(self, caplog): """Test exception handling during function evaluation.""" + def faulty_func(): raise ValueError("Intentional error") @@ -68,10 +74,13 @@ def faulty_func(): result = str(deferred) assert result == "_DeferredString: error evaluating deferred string" - assert any("Error evaluating deferred string" in message for message in caplog.messages) + assert any( + "Error evaluating deferred string" in message for message in caplog.messages + ) def test_deferred_string_exception_with_args(self, caplog): """Test exception handling when function is called with arguments.""" + def faulty_func(x, y): raise RuntimeError("Something went wrong") @@ -89,9 +98,9 @@ def test_deferred_string_logging_integration(self): log_capture_string = StringIO() ch = logging.StreamHandler(log_capture_string) ch.setLevel(logging.INFO) - + # Create a logger and add the handler - test_logger = logging.getLogger('test_deferred_logger') + test_logger = logging.getLogger("test_deferred_logger") test_logger.setLevel(logging.INFO) test_logger.addHandler(ch) @@ -99,36 +108,36 @@ def expensive_operation(): return "Expensive computation result" deferred = _DeferredString(expensive_operation) - + # Log a message with the deferred string test_logger.info("Processing complete: %s", deferred) - + # Get the log output and verify the deferred string was evaluated log_contents = log_capture_string.getvalue() assert "Processing complete: Expensive computation result" in log_contents - + # Clean up test_logger.removeHandler(ch) def test_deferred_string_lazy_evaluation(self): """Test that the function is only called when string conversion occurs.""" call_count = 0 - + def counting_func(): nonlocal call_count call_count += 1 return f"Called {call_count} times" deferred = _DeferredString(counting_func) - + # Function should not be called yet assert call_count == 0 - + # First string conversion should call the function result1 = str(deferred) assert call_count == 1 assert result1 == "Called 1 times" - + # Second string conversion should call the function again result2 = str(deferred) assert call_count == 2 @@ -136,119 +145,24 @@ def counting_func(): def test_deferred_string_with_logger_level_filtering(self): """Test that deferred strings are only evaluated when log level allows it.""" - call_count = 0 - - def expensive_func(): - nonlocal call_count - call_count += 1 - return "Expensive result" - - deferred = _DeferredString(expensive_func) - - # Create logger with high level (ERROR) - test_logger = logging.getLogger('level_test_logger') - test_logger.setLevel(logging.ERROR) - - # Log at DEBUG level - should not evaluate deferred string due to level filtering - test_logger.debug("Debug message: %s", deferred) - assert call_count == 0 - - # Log at ERROR level - should evaluate deferred string - test_logger.error("Error message: %s", deferred) - assert call_count == 1 - def test_deferred_string_with_multiple_evaluations_in_logging(self): - """Test multiple deferred strings in the same log message.""" - def func1(): - return "First" - - def func2(): - return "Second" - - deferred1 = _DeferredString(func1) - deferred2 = _DeferredString(func2) - log_capture_string = StringIO() ch = logging.StreamHandler(log_capture_string) - test_logger = logging.getLogger('multi_deferred_logger') + test_logger = logging.getLogger("multi_deferred_logger") test_logger.setLevel(logging.INFO) test_logger.addHandler(ch) - - test_logger.info("Results: %s and %s", deferred1, deferred2) - - log_contents = log_capture_string.getvalue() - assert "Results: First and Second" in log_contents - - test_logger.removeHandler(ch) - def test_deferred_string_error_logging_details(self, caplog): - """Test that error logging includes exception details.""" - def func_with_specific_error(): - raise KeyError("Missing key 'test'") + def expensive_func(): + return "test" - deferred = _DeferredString(func_with_specific_error) + deferred = _DeferredString(expensive_func) - with caplog.at_level(logging.ERROR): - result = str(deferred) + # Log at DEBUG level - should not evaluate deferred string due to level filtering + test_logger.debug("Debug message: %s", deferred) + assert log_capture_string.getvalue() == "" - assert result == "_DeferredString: error evaluating deferred string" - - # Check that the original exception information is preserved in logs - assert len(caplog.records) == 1 - error_record = caplog.records[0] - assert error_record.levelname == "ERROR" - assert error_record.getMessage() == "Error evaluating deferred string" - assert error_record.exc_info is not None - assert issubclass(error_record.exc_info[0], KeyError) - - @patch('microsoft_agents.activity._utils._deferred_string.logger') - def test_deferred_string_logger_mock(self, mock_logger): - """Test that the correct logger is used for error reporting.""" - def failing_func(): - raise Exception("Test exception") - - deferred = _DeferredString(failing_func) - str(deferred) - - mock_logger.error.assert_called_once_with( - "Error evaluating deferred string", - exc_info=Exception("Test exception") - ) + # Log at ERROR level - should evaluate deferred string + test_logger.error("Error message: %s", deferred) + assert log_capture_string.getvalue() == "Error message: test\n" - def test_deferred_string_with_lambda(self): - """Test deferred string with lambda functions.""" - deferred = _DeferredString(lambda x: f"Lambda result: {x}", "test") - assert str(deferred) == "Lambda result: test" - - def test_deferred_string_with_method_call(self): - """Test deferred string with method calls.""" - class TestClass: - def __init__(self, value): - self.value = value - - def get_description(self): - return f"Value is {self.value}" - - obj = TestClass(100) - deferred = _DeferredString(obj.get_description) - assert str(deferred) == "Value is 100" - - def test_deferred_string_preserves_function_state(self): - """Test that deferred string preserves the state when function is created.""" - values = [] - - def append_and_return(): - values.append(len(values)) - return f"List length: {len(values)}" - - deferred = _DeferredString(append_and_return) - - # Each evaluation should append to the list - result1 = str(deferred) - assert result1 == "List length: 1" - - result2 = str(deferred) - assert result2 == "List length: 2" - - assert len(values) == 2 - assert values == [0, 1] \ No newline at end of file + test_logger.removeHandler(ch) diff --git a/tests/hosting_core/app/test_typing_indicator.py b/tests/hosting_core/app/test_typing_indicator.py index 22a09c8f..c58c4ed4 100644 --- a/tests/hosting_core/app/test_typing_indicator.py +++ b/tests/hosting_core/app/test_typing_indicator.py @@ -30,7 +30,9 @@ async def test_start_sends_typing_activity(): await indicator.stop() assert len(context.sent_activities) >= 1 - assert all(activity.type == ActivityTypes.typing for activity in context.sent_activities) + assert all( + activity.type == ActivityTypes.typing for activity in context.sent_activities + ) @pytest.mark.asyncio