diff --git a/.github/workflows/publish-artifacts-examples-tests.yml b/.github/workflows/publish-artifacts-examples-tests.yml index cc08b5f73..ef25f1cca 100644 --- a/.github/workflows/publish-artifacts-examples-tests.yml +++ b/.github/workflows/publish-artifacts-examples-tests.yml @@ -79,6 +79,7 @@ jobs: # Ensure we preserve access to NuGet.org - name: Configure NuGet.org source + continue-on-error: true run: | dotnet nuget add source https://api.nuget.org/v3/index.json --name nuget.org diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/ConsoleWrapper.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/ConsoleWrapper.cs index c71c05aec..c2c93eb4b 100644 --- a/libraries/src/AWS.Lambda.Powertools.Common/Core/ConsoleWrapper.cs +++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/ConsoleWrapper.cs @@ -22,41 +22,77 @@ namespace AWS.Lambda.Powertools.Common; public class ConsoleWrapper : IConsoleWrapper { private static bool _override; + private static TextWriter _testOutputStream; + private static bool _outputResetPerformed = false; + private static bool _inTestMode = false; /// public void WriteLine(string message) { - OverrideLambdaLogger(); - Console.WriteLine(message); + if (_inTestMode && _testOutputStream != null) + { + _testOutputStream.WriteLine(message); + } + else + { + EnsureConsoleOutputOnce(); + Console.WriteLine(message); + } } /// public void Debug(string message) { - OverrideLambdaLogger(); - System.Diagnostics.Debug.WriteLine(message); + if (_inTestMode && _testOutputStream != null) + { + _testOutputStream.WriteLine(message); + } + else + { + EnsureConsoleOutputOnce(); + System.Diagnostics.Debug.WriteLine(message); + } } /// public void Error(string message) { - if (!_override) + if (_inTestMode && _testOutputStream != null) { - var errordOutput = new StreamWriter(Console.OpenStandardError()); - errordOutput.AutoFlush = true; - Console.SetError(errordOutput); + _testOutputStream.WriteLine(message); + } + else + { + if (!_override) + { + var errordOutput = new StreamWriter(Console.OpenStandardError()); + errordOutput.AutoFlush = true; + Console.SetError(errordOutput); + } + Console.Error.WriteLine(message); } - - Console.Error.WriteLine(message); } - internal static void SetOut(StringWriter consoleOut) + /// + /// Set the ConsoleWrapper to use a different TextWriter + /// This is useful for unit tests where you want to capture the output + /// + public static void SetOut(TextWriter consoleOut) { + _testOutputStream = consoleOut; + _inTestMode = true; _override = true; Console.SetOut(consoleOut); } - private void OverrideLambdaLogger() + private static void EnsureConsoleOutputOnce() + { + if (_outputResetPerformed) return; + OverrideLambdaLogger(); + _outputResetPerformed = true; + } + + private static void OverrideLambdaLogger() { if (_override) { @@ -73,8 +109,22 @@ internal static void WriteLine(string logLevel, string message) Console.WriteLine($"{DateTime.UtcNow:yyyy-MM-ddTHH:mm:ss.fffZ}\t{logLevel}\t{message}"); } + /// + /// Reset the ConsoleWrapper to its original state + /// public static void ResetForTest() { _override = false; + _inTestMode = false; + _testOutputStream = null; + _outputResetPerformed = false; + } + + /// + /// Clear the output reset flag + /// + public static void ClearOutputResetFlag() + { + _outputResetPerformed = false; } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Internal/BatchProcessingInternalTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Internal/BatchProcessingInternalTests.cs index c218e419b..daf430826 100644 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Internal/BatchProcessingInternalTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Internal/BatchProcessingInternalTests.cs @@ -35,7 +35,7 @@ public void BatchProcessing_Set_Execution_Environment_Context_SQS() var sqsBatchProcessor = new SqsBatchProcessor(conf); // Assert - Assert.Equal($"{Constants.FeatureContextIdentifier}/BatchProcessing/1.0.0", + Assert.Equal($"{Constants.FeatureContextIdentifier}/BatchProcessing/{env.GetAssemblyVersion(this)}", env.GetEnvironmentVariable("AWS_EXECUTION_ENV")); Assert.NotNull(sqsBatchProcessor); @@ -52,7 +52,7 @@ public void BatchProcessing_Set_Execution_Environment_Context_Kinesis() var KinesisEventBatchProcessor = new KinesisEventBatchProcessor(conf); // Assert - Assert.Equal($"{Constants.FeatureContextIdentifier}/BatchProcessing/1.0.0", + Assert.Equal($"{Constants.FeatureContextIdentifier}/BatchProcessing/{env.GetAssemblyVersion(this)}", env.GetEnvironmentVariable("AWS_EXECUTION_ENV")); Assert.NotNull(KinesisEventBatchProcessor); @@ -69,7 +69,7 @@ public void BatchProcessing_Set_Execution_Environment_Context_DynamoDB() var dynamoDbStreamBatchProcessor = new DynamoDbStreamBatchProcessor(conf); // Assert - Assert.Equal($"{Constants.FeatureContextIdentifier}/BatchProcessing/1.0.0", + Assert.Equal($"{Constants.FeatureContextIdentifier}/BatchProcessing/{env.GetAssemblyVersion(this)}", env.GetEnvironmentVariable("AWS_EXECUTION_ENV")); Assert.NotNull(dynamoDbStreamBatchProcessor); diff --git a/libraries/tests/AWS.Lambda.Powertools.Common.Tests/ConsoleWrapperTests.cs b/libraries/tests/AWS.Lambda.Powertools.Common.Tests/ConsoleWrapperTests.cs index fdc79d957..020630a33 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Common.Tests/ConsoleWrapperTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Common.Tests/ConsoleWrapperTests.cs @@ -6,19 +6,28 @@ namespace AWS.Lambda.Powertools.Common.Tests; public class ConsoleWrapperTests : IDisposable { + private StringWriter _writer; + + public ConsoleWrapperTests() + { + // Setup a new StringWriter for each test + _writer = new StringWriter(); + // Reset static state for clean testing + ConsoleWrapper.ResetForTest(); + } + [Fact] public void WriteLine_Should_Write_To_Console() { // Arrange var consoleWrapper = new ConsoleWrapper(); - var writer = new StringWriter(); - ConsoleWrapper.SetOut(writer); + ConsoleWrapper.SetOut(_writer); // Act consoleWrapper.WriteLine("test message"); // Assert - Assert.Equal($"test message{Environment.NewLine}", writer.ToString()); + Assert.Equal($"test message{Environment.NewLine}", _writer.ToString()); } [Fact] @@ -26,16 +35,15 @@ public void Error_Should_Write_To_Error_Console() { // Arrange var consoleWrapper = new ConsoleWrapper(); - var writer = new StringWriter(); - ConsoleWrapper.SetOut(writer); - Console.SetError(writer); + ConsoleWrapper.SetOut(_writer); + Console.SetError(_writer); // Act consoleWrapper.Error("error message"); - writer.Flush(); + _writer.Flush(); // Assert - Assert.Equal($"error message{Environment.NewLine}", writer.ToString()); + Assert.Equal($"error message{Environment.NewLine}", _writer.ToString()); } [Fact] @@ -43,122 +51,149 @@ public void SetOut_Should_Override_Console_Output() { // Arrange var consoleWrapper = new ConsoleWrapper(); - var writer = new StringWriter(); - ConsoleWrapper.SetOut(writer); + ConsoleWrapper.SetOut(_writer); // Act consoleWrapper.WriteLine("test message"); // Assert - Assert.Equal($"test message{Environment.NewLine}", writer.ToString()); + Assert.Equal($"test message{Environment.NewLine}", _writer.ToString()); } [Fact] public void OverrideLambdaLogger_Should_Override_Console_Out() { -// Arrange - var originalOut = Console.Out; - try - { - var consoleWrapper = new ConsoleWrapper(); - - // Act - create a custom StringWriter and set it after constructor - // but before WriteLine (which triggers OverrideLambdaLogger) - var writer = new StringWriter(); - ConsoleWrapper.SetOut(writer); - - consoleWrapper.WriteLine("test message"); - - // Assert - Assert.Equal($"test message{Environment.NewLine}", writer.ToString()); - } - finally - { - // Restore original console out - ConsoleWrapper.ResetForTest(); - } + // Arrange + var consoleWrapper = new ConsoleWrapper(); + ConsoleWrapper.SetOut(_writer); + + // Act + consoleWrapper.WriteLine("test message"); + + // Assert + Assert.Equal($"test message{Environment.NewLine}", _writer.ToString()); + } + + [Fact] + public void WriteLine_WritesMessageToConsole() + { + // Arrange + var consoleWrapper = new ConsoleWrapper(); + ConsoleWrapper.SetOut(_writer); + + // Act + consoleWrapper.WriteLine("Test message"); + + // Assert + var output = _writer.ToString(); + Assert.Contains("Test message", output); + } + + [Fact] + public void SetOut_OverridesConsoleOutput() + { + // Act + ConsoleWrapper.SetOut(_writer); + Console.WriteLine("Test override"); + + // Assert + var output = _writer.ToString(); + Assert.Contains("Test override", output); } + + [Fact] + public void StaticWriteLine_FormatsLogMessageCorrectly() + { + // Arrange + ConsoleWrapper.SetOut(_writer); + + // Act - Using reflection to call internal static method + typeof(ConsoleWrapper) + .GetMethod("WriteLine", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static, + null, new[] { typeof(string), typeof(string) }, null) + ?.Invoke(null, new object[] { "INFO", "Test log message" }); + + // Assert + var output = _writer.ToString(); + Assert.Matches(@"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\tINFO\tTest log message", output); + } + + [Fact] + public void ClearOutputResetFlag_ResetsFlag() + { + // Arrange + var consoleWrapper = new ConsoleWrapper(); + ConsoleWrapper.SetOut(_writer); + + // Act + consoleWrapper.WriteLine("First message"); // Should set the reset flag + ConsoleWrapper.ClearOutputResetFlag(); + consoleWrapper.WriteLine("Second message"); // Should set it again + + // Assert + Assert.Equal($"First message{Environment.NewLine}Second message{Environment.NewLine}", _writer.ToString()); + } + + [Fact] + public void Debug_InTestMode_WritesToTestOutputStream() + { + // Arrange + var consoleWrapper = new ConsoleWrapper(); + ConsoleWrapper.SetOut(_writer); + + // Act + consoleWrapper.Debug("debug message"); + + // Assert + Assert.Equal($"debug message{Environment.NewLine}", _writer.ToString()); + } + + [Fact] + public void Debug_NotInTestMode_WritesToDebugConsole() + { + // Since capturing Debug output is difficult in a unit test + // We'll use a mock or just verify the path doesn't throw + // Arrange + var consoleWrapper = new ConsoleWrapper(); + ConsoleWrapper.ResetForTest(); // Ensure we're not in test mode + + // Act & Assert - Just verify it doesn't throw + var exception = Record.Exception(() => consoleWrapper.Debug("debug message")); + Assert.Null(exception); + } + + [Fact] + public void Error_DoesNotThrowWhenNotOverridden() + { + // Arrange + var consoleWrapper = new ConsoleWrapper(); + ConsoleWrapper.ResetForTest(); // Reset to ensure _override is false + + // Act & Assert - Just verify it doesn't throw + var exception = Record.Exception(() => consoleWrapper.Error("error without override")); + Assert.Null(exception); + } + [Fact] - public void WriteLine_WritesMessageToConsole() - { - // Arrange - var consoleWrapper = new ConsoleWrapper(); - var originalOutput = Console.Out; - using var stringWriter = new StringWriter(); - ConsoleWrapper.SetOut(stringWriter); - - try - { - // Act - consoleWrapper.WriteLine("Test message"); - - // Assert - var output = stringWriter.ToString(); - Assert.Contains("Test message", output); - } - finally - { - // Restore original output - ConsoleWrapper.ResetForTest(); - } - } - - [Fact] - public void SetOut_OverridesConsoleOutput() - { - // Arrange - var originalOutput = Console.Out; - using var stringWriter = new StringWriter(); - - try - { - // Act - typeof(ConsoleWrapper) - .GetMethod("SetOut", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static) - ?.Invoke(null, new object[] { stringWriter }); - - Console.WriteLine("Test override"); - - // Assert - var output = stringWriter.ToString(); - Assert.Contains("Test override", output); - } - finally - { - // Restore original output - ConsoleWrapper.ResetForTest(); - } - } - - [Fact] - public void StaticWriteLine_FormatsLogMessageCorrectly() - { - // Arrange - var originalOutput = Console.Out; - using var stringWriter = new StringWriter(); - ConsoleWrapper.SetOut(stringWriter); - - try - { - // Act - typeof(ConsoleWrapper) - .GetMethod("WriteLine", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static, null, new[] { typeof(string), typeof(string) }, null) - ?.Invoke(null, new object[] { "INFO", "Test log message" }); - - // Assert - var output = stringWriter.ToString(); - Assert.Matches(@"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\tINFO\tTest log message", output); - } - finally - { - // Restore original output - ConsoleWrapper.ResetForTest(); - } - } - - public void Dispose() - { - ConsoleWrapper.ResetForTest(); - } + public void Error_UsesTestOutputStreamWhenInTestMode() + { + // Arrange + var consoleWrapper = new ConsoleWrapper(); + + // Set test mode + ConsoleWrapper.SetOut(_writer); + + // Act + consoleWrapper.Error("error in test mode"); + + // Assert + Assert.Contains("error in test mode", _writer.ToString()); + } + + public void Dispose() + { + ConsoleWrapper.ResetForTest(); + _writer?.Dispose(); + } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotentAspectTests.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotentAspectTests.cs index be80a4c45..8cc7e7f99 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotentAspectTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotentAspectTests.cs @@ -272,7 +272,7 @@ public void Idempotency_Set_Execution_Environment_Context() var xRayRecorder = new Idempotency(conf); // Assert - Assert.Equal($"{Constants.FeatureContextIdentifier}/Idempotency/1.0.0", + Assert.Equal($"{Constants.FeatureContextIdentifier}/Idempotency/{env.GetAssemblyVersion(this)}", env.GetEnvironmentVariable("AWS_EXECUTION_ENV")); Assert.NotNull(xRayRecorder); diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs index f4ba2a434..099e8a2a2 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs @@ -1369,7 +1369,7 @@ public void Log_Set_Execution_Environment_Context() logger.LogInformation("Test"); // Assert - Assert.Equal($"{Constants.FeatureContextIdentifier}/Logging/1.0.0", + Assert.Equal($"{Constants.FeatureContextIdentifier}/Logging/{env.GetAssemblyVersion(this)}", env.GetEnvironmentVariable("AWS_EXECUTION_ENV")); } diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/MetricsTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/MetricsTests.cs index adcee3727..a1c1ee515 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/MetricsTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/MetricsTests.cs @@ -24,7 +24,7 @@ public void Metrics_Set_Execution_Environment_Context() _ = new Metrics(conf); // Assert - Assert.Equal($"{Constants.FeatureContextIdentifier}/Metrics/1.0.0", + Assert.Equal($"{Constants.FeatureContextIdentifier}/Metrics/{env.GetAssemblyVersion(this)}", env.GetEnvironmentVariable("AWS_EXECUTION_ENV")); } diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/XRayRecorderTests.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/XRayRecorderTests.cs index 5318b7b35..a4e9e515c 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/XRayRecorderTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/XRayRecorderTests.cs @@ -40,7 +40,7 @@ public void Tracing_Set_Execution_Environment_Context() var xRayRecorder = new XRayRecorder(awsXray, conf); // Assert - Assert.Equal($"{Constants.FeatureContextIdentifier}/Tracing/1.0.0", + Assert.Equal($"{Constants.FeatureContextIdentifier}/Tracing/{env.GetAssemblyVersion(this)}", env.GetEnvironmentVariable("AWS_EXECUTION_ENV")); Assert.NotNull(xRayRecorder);