Skip to content

Commit 8724278

Browse files
authored
Add tests for logger to increase coverage (#648)
* Add test case to increase logger coverage * Add basic coverage for Callback * Address review comments * Dispose the provider * Fix tests review comment * Address comments * Fix the test cases * Remove sleep * Fix test case
1 parent 53172dc commit 8724278

File tree

2 files changed

+180
-1
lines changed

2 files changed

+180
-1
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#nullable enable
2+
using System;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using Cognite.Extractor.Utils;
6+
using Microsoft.Extensions.Logging;
7+
using Microsoft.Extensions.Logging.Abstractions;
8+
using Moq;
9+
using Xunit;
10+
11+
namespace ExtractorUtils.Test.Unit
12+
{
13+
/// <summary>
14+
/// Test double for CogniteDestination that doesn't require CogniteSdk.Client
15+
/// </summary>
16+
internal class TestCogniteDestination : CogniteDestination
17+
{
18+
public TestCogniteDestination()
19+
: base(null!, new NullLogger<CogniteDestination>(), new CogniteConfig { Project = "test-project" })
20+
{
21+
}
22+
}
23+
24+
public class CallbackUtilTest
25+
{
26+
private static CogniteDestination GetCogniteDestination()
27+
{
28+
return new TestCogniteDestination();
29+
}
30+
31+
[Fact]
32+
public async Task TryCallWithMissingConfiguration_ReturnsFalseAndLogsWarning()
33+
{
34+
// Arrange
35+
var destination = GetCogniteDestination();
36+
var mockLogger = new Mock<ILogger>();
37+
var config = new FunctionCallConfig(); // No ExternalId or Id
38+
var wrapper = new FunctionCallWrapper<string>(destination, config, mockLogger.Object);
39+
40+
// Act
41+
var result = await wrapper.TryCall("test-argument", CancellationToken.None);
42+
43+
// Assert
44+
Assert.False(result);
45+
mockLogger.Verify(
46+
x => x.Log(
47+
LogLevel.Warning,
48+
It.IsAny<EventId>(),
49+
It.Is<It.IsAnyType>((v, t) => v.ToString()!.Contains("Missing function configuration")),
50+
It.IsAny<Exception>(),
51+
It.IsAny<Func<It.IsAnyType, Exception?, string>>()),
52+
Times.Once);
53+
}
54+
55+
[Theory]
56+
[InlineData("test-function-id", null)]
57+
[InlineData(null, 12345L)]
58+
[InlineData("test-function-id", 12345L)]
59+
public async Task TryCallWithValidConfiguration_ReturnsTrue(string? externalId, long? id)
60+
{
61+
// Arrange
62+
var destination = GetCogniteDestination();
63+
var config = new FunctionCallConfig { ExternalId = externalId, Id = id };
64+
var wrapper = new FunctionCallWrapper<string>(destination, config, null);
65+
66+
// Act
67+
// Note: The stub destination always returns true for function calls.
68+
// If the underlying function's behavior changes, this test will need to be updated.
69+
var result = await wrapper.TryCall("test-argument", CancellationToken.None);
70+
71+
// Assert
72+
Assert.True(result);
73+
}
74+
}
75+
}

ExtractorUtils.Test/unit/LoggingTest.cs

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
using Xunit;
77
using Cognite.Extractor.Logging;
88
using Cognite.Extractor.Utils;
9-
using Serilog;
109

1110
namespace ExtractorUtils.Test.Unit
1211
{
@@ -129,5 +128,110 @@ public static void TestLogLevel()
129128
logger.LogTrace("This is a trace message with {SideEffect}", sideEffect);
130129
Assert.Equal(4, sideEffect.logCount); // should not be evaluated
131130
}
131+
132+
private static ServiceCollection ConfigureLoggerService(string logType, string level = null)
133+
{
134+
var services = new ServiceCollection();
135+
var loggerConfig = new LoggerConfig();
136+
137+
switch (logType)
138+
{
139+
case "console":
140+
loggerConfig.Console = new ConsoleConfig { Level = level ?? "information" };
141+
break;
142+
case "file":
143+
loggerConfig.File = new FileConfig { Level = level ?? "warning", Path = "test.log" };
144+
break;
145+
case "trace-listener":
146+
loggerConfig.TraceListener = new TraceListenerConfig { Level = level ?? "error" };
147+
break;
148+
}
149+
150+
services.AddSingleton(loggerConfig);
151+
services.AddLogger();
152+
return services;
153+
}
154+
155+
[Theory]
156+
[InlineData("console")]
157+
[InlineData("file")]
158+
[InlineData("trace-listener")]
159+
public void TestLogger_WithDifferentLogTypes(string logType)
160+
{
161+
var services = ConfigureLoggerService(logType);
162+
163+
using (var provider = services.BuildServiceProvider())
164+
{
165+
var logger = provider.GetRequiredService<ILogger<LoggingTest>>();
166+
167+
Assert.NotNull(logger);
168+
logger.LogWarning("Test log message");
169+
} // provider is disposed here, which flushes async logs
170+
171+
// Assert and cleanup test.log file if created by file logger
172+
if (logType == "file")
173+
{
174+
var logFiles = Directory.GetFiles(".", "test*.log");
175+
Assert.NotEmpty(logFiles);
176+
foreach (var logFile in logFiles)
177+
{
178+
File.Delete(logFile);
179+
}
180+
}
181+
}
182+
183+
[Theory]
184+
[InlineData("console", "debug")]
185+
[InlineData("console", "information")]
186+
[InlineData("file", "warning")]
187+
[InlineData("trace-listener", "error")]
188+
public void TestLogger_WithTypeAndLevel(string configType, string level)
189+
{
190+
var services = ConfigureLoggerService(configType, level);
191+
192+
using (var provider = services.BuildServiceProvider())
193+
{
194+
var logger = provider.GetRequiredService<ILogger<LoggingTest>>();
195+
Assert.NotNull(logger);
196+
197+
// Verify the logger respects the configured level using side effects
198+
// Note: trace-listener alone falls back to default logger, so we only verify console and file
199+
if (configType != "trace-listener")
200+
{
201+
var sideEffect = new LogSideEffect();
202+
logger.LogDebug("Debug: {SideEffect}", sideEffect);
203+
logger.LogInformation("Info: {SideEffect}", sideEffect);
204+
logger.LogWarning("Warning: {SideEffect}", sideEffect);
205+
logger.LogError("Error: {SideEffect}", sideEffect);
206+
207+
// Verify only messages at or above the configured level were evaluated
208+
int expectedCount = level switch
209+
{
210+
"debug" => 4, // debug, info, warning, error
211+
"information" => 3, // info, warning, error
212+
"warning" => 2, // warning, error
213+
"error" => 1, // error only
214+
_ => 0
215+
};
216+
Assert.Equal(expectedCount, sideEffect.logCount);
217+
}
218+
else
219+
{
220+
// For trace-listener, just verify it doesn't throw
221+
logger.LogError("Test log message");
222+
}
223+
} // provider is disposed here
224+
225+
// Cleanup file if created by file logger
226+
if (configType == "file")
227+
{
228+
var logFiles = Directory.GetFiles(".", "test*.log");
229+
Assert.NotEmpty(logFiles);
230+
foreach (var logFile in logFiles)
231+
{
232+
File.Delete(logFile);
233+
}
234+
}
235+
}
132236
}
133237
}

0 commit comments

Comments
 (0)