Skip to content

Commit 49b66e8

Browse files
committed
refactor: enhance console handling in Lambda by allowing dependency injection for output and logger overrides
1 parent 2a55568 commit 49b66e8

File tree

2 files changed

+42
-59
lines changed

2 files changed

+42
-59
lines changed

libraries/src/AWS.Lambda.Powertools.Common/Core/ConsoleWrapper.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,17 @@ private static bool ShouldOverrideConsole()
8989
return isLambda && (!_override || HasLambdaReInterceptedConsole());
9090
}
9191

92-
private static bool HasLambdaReInterceptedConsole()
92+
internal static bool HasLambdaReInterceptedConsole()
93+
{
94+
return HasLambdaReInterceptedConsole(() => Console.Out);
95+
}
96+
97+
internal static bool HasLambdaReInterceptedConsole(Func<TextWriter> consoleOutAccessor)
9398
{
9499
// Lambda might re-intercept console between init and handler execution
95100
try
96101
{
97-
var currentOut = Console.Out;
102+
var currentOut = consoleOutAccessor();
98103
// Check if current output stream looks like it might be Lambda's wrapper
99104
var typeName = currentOut.GetType().FullName ?? "";
100105
return typeName.Contains("Lambda") || typeName == "System.IO.TextWriter+SyncTextWriter";
@@ -105,12 +110,17 @@ private static bool HasLambdaReInterceptedConsole()
105110
}
106111
}
107112

108-
private static void OverrideLambdaLogger()
113+
internal static void OverrideLambdaLogger()
114+
{
115+
OverrideLambdaLogger(() => Console.OpenStandardOutput());
116+
}
117+
118+
internal static void OverrideLambdaLogger(Func<Stream> standardOutputOpener)
109119
{
110120
try
111121
{
112122
// Force override of LambdaLogger
113-
var standardOutput = new StreamWriter(Console.OpenStandardOutput())
123+
var standardOutput = new StreamWriter(standardOutputOpener())
114124
{
115125
AutoFlush = true
116126
};

libraries/tests/AWS.Lambda.Powertools.Common.Tests/ConsoleWrapperTests.cs

Lines changed: 28 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.IO;
3+
using NSubstitute;
34
using Xunit;
45

56
namespace AWS.Lambda.Powertools.Common.Tests;
@@ -15,13 +16,13 @@ public ConsoleWrapperTests()
1516
// Store original console outputs
1617
_originalOut = Console.Out;
1718
_originalError = Console.Error;
18-
19+
1920
// Setup test writer
2021
_testWriter = new StringWriter();
21-
22+
2223
// Reset ConsoleWrapper state before each test
2324
ConsoleWrapper.ResetForTest();
24-
25+
2526
// Clear any Lambda environment variables
2627
Environment.SetEnvironmentVariable("AWS_LAMBDA_FUNCTION_NAME", null);
2728
}
@@ -31,13 +32,13 @@ public void Dispose()
3132
// Restore original console outputs
3233
Console.SetOut(_originalOut);
3334
Console.SetError(_originalError);
34-
35+
3536
// Reset ConsoleWrapper state after each test
3637
ConsoleWrapper.ResetForTest();
37-
38+
3839
// Clear any test environment variables
3940
Environment.SetEnvironmentVariable("AWS_LAMBDA_FUNCTION_NAME", null);
40-
41+
4142
_testWriter?.Dispose();
4243
}
4344

@@ -240,7 +241,8 @@ public void WriteLineStatic_GivenLogLevelAndMessage_WhenCalled_ThenFormatsWithTi
240241
{
241242
// When - Using reflection to call internal static method
242243
var method = typeof(ConsoleWrapper)
243-
.GetMethod("WriteLine", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
244+
.GetMethod("WriteLine",
245+
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
244246

245247
if (method == null)
246248
{
@@ -299,64 +301,35 @@ public void ClearOutputResetFlag_GivenMultipleCalls_WhenCalled_ThenAllowsRepeate
299301
// Then
300302
Assert.Equal($"First message{Environment.NewLine}Second message{Environment.NewLine}", _testWriter.ToString());
301303
}
304+
305+
// from here
302306

303307
[Fact]
304-
public void HasLambdaReInterceptedConsole_GivenConsoleOutThrowsException_WhenCalled_ThenReturnsTrue()
308+
public void HasLambdaReInterceptedConsole_WhenConsoleOutAccessThrows_ThenReturnsTrueFromCatchBlock()
305309
{
306-
// Given
307-
Environment.SetEnvironmentVariable("AWS_LAMBDA_FUNCTION_NAME", "test-function");
308-
309-
// Create a mock TextWriter that throws when accessing its type
310-
var mockWriter = new ThrowingTextWriter();
311-
Console.SetOut(mockWriter);
312-
313-
var wrapper = new ConsoleWrapper();
314-
315-
// When - This will trigger HasLambdaReInterceptedConsole which should catch the exception
316-
var exception = Record.Exception(() => wrapper.WriteLine("test message"));
317-
318-
// Then - Should not throw, the catch block should handle it
319-
Assert.Null(exception);
310+
// Given - A function that throws when called (simulating Console.Out access failure)
311+
Func<TextWriter> throwingAccessor = () => throw new InvalidOperationException("Console.Out access failed");
312+
313+
// When - Call the internal method with the throwing accessor
314+
var result = ConsoleWrapper.HasLambdaReInterceptedConsole(throwingAccessor);
315+
316+
// Then - Should return true from the catch block (lines 102-105)
317+
Assert.True(result);
320318
}
321319

322320
[Fact]
323-
public void OverrideLambdaLogger_GivenConsoleOpenStandardOutputThrows_WhenCalled_ThenDoesNotThrow()
321+
public void OverrideLambdaLogger_WhenOpenStandardOutputThrows_ThenSetsOverrideToFalse()
324322
{
325323
// Given
326-
Environment.SetEnvironmentVariable("AWS_LAMBDA_FUNCTION_NAME", "test-function");
327-
328-
// Create a scenario where Console.OpenStandardOutput might fail
329-
// We'll simulate this by creating conditions that trigger the catch block
330-
var wrapper = new ConsoleWrapper();
331-
332-
// When - Multiple calls to trigger the override logic and potential failures
333-
var exception = Record.Exception(() =>
334-
{
335-
for (int i = 0; i < 5; i++)
336-
{
337-
wrapper.WriteLine($"test message {i}");
338-
}
339-
});
324+
ConsoleWrapper.ResetForTest();
340325

341-
// Then - Should not throw even if StreamWriter creation fails
342-
Assert.Null(exception);
343-
}
326+
// A function that throws when called (simulating Console.OpenStandardOutput failure)
327+
Func<Stream> throwingOpener = () => throw new UnauthorizedAccessException("Cannot open standard output");
344328

345-
// Helper class to simulate exceptions in Console.Out.GetType()
346-
private class ThrowingTextWriter : TextWriter
347-
{
348-
public override System.Text.Encoding Encoding => System.Text.Encoding.UTF8;
329+
// When - Call the internal method with the throwing opener
330+
var exception = Record.Exception(() => ConsoleWrapper.OverrideLambdaLogger(throwingOpener));
349331

350-
public override void Write(char value)
351-
{
352-
// Simulate the scenario where accessing type information fails
353-
// by throwing when any operation is performed
354-
throw new InvalidOperationException("Simulated exception during console operation");
355-
}
356-
357-
public override void WriteLine(string value)
358-
{
359-
throw new InvalidOperationException("Simulated exception during console operation");
360-
}
332+
// Then - Should not throw (catch block handles it on lines 120-123)
333+
Assert.Null(exception);
361334
}
362335
}

0 commit comments

Comments
 (0)