using Microsoft.Extensions.Configuration; using Serilog.Events; namespace ScadaLink.Host.Tests; /// /// Host-011: ScadaLink:Logging:MinimumLevel must actually drive the Serilog /// minimum level. Previously the value was bound into /// but never read, so editing it had no effect. /// public class LoggerConfigurationTests { private static IConfiguration BuildConfig(string? minimumLevel) { var values = new Dictionary(); if (minimumLevel != null) values["ScadaLink:Logging:MinimumLevel"] = minimumLevel; return new ConfigurationBuilder().AddInMemoryCollection(values).Build(); } [Fact] public void MinimumLevel_Warning_SuppressesInformationLogs() { var sink = new InMemorySink(); var logger = LoggerConfigurationFactory .Build(BuildConfig("Warning"), "Central", "central", "node1") .WriteTo.Sink(sink) .CreateLogger(); logger.Information("info message"); logger.Warning("warning message"); Assert.Single(sink.LogEvents); Assert.Equal(LogEventLevel.Warning, sink.LogEvents[0].Level); } [Fact] public void MinimumLevel_Debug_AllowsDebugLogs() { var sink = new InMemorySink(); var logger = LoggerConfigurationFactory .Build(BuildConfig("Debug"), "Site", "site-a", "node1") .WriteTo.Sink(sink) .CreateLogger(); logger.Debug("debug message"); Assert.Single(sink.LogEvents); Assert.Equal(LogEventLevel.Debug, sink.LogEvents[0].Level); } [Fact] public void MinimumLevel_Absent_DefaultsToInformation() { var sink = new InMemorySink(); var logger = LoggerConfigurationFactory .Build(BuildConfig(null), "Central", "central", "node1") .WriteTo.Sink(sink) .CreateLogger(); logger.Debug("debug message"); logger.Information("info message"); Assert.Single(sink.LogEvents); Assert.Equal(LogEventLevel.Information, sink.LogEvents[0].Level); } /// /// Host-022: an unrecognised ScadaLink:Logging:MinimumLevel (e.g. a typo /// like "Informaiton") must NOT abort startup but MUST emit a one-shot warning /// naming the offending value and the fallback so the silent coercion is /// visible. Null/blank is treated as "unset" and silently defaults. /// [Fact] public void ParseLevel_UnrecognisedValue_FallsBackAndWarns() { var writer = new StringWriter(); var result = LoggerConfigurationFactory.ParseLevel("Informaiton", writer); Assert.Equal(LogEventLevel.Information, result); var warning = writer.ToString(); Assert.Contains("warning", warning, StringComparison.OrdinalIgnoreCase); Assert.Contains("Informaiton", warning); Assert.Contains("Information", warning); } [Fact] public void ParseLevel_NullOrBlank_FallsBackSilently() { var writer = new StringWriter(); var nullResult = LoggerConfigurationFactory.ParseLevel(null, writer); var blankResult = LoggerConfigurationFactory.ParseLevel(" ", writer); Assert.Equal(LogEventLevel.Information, nullResult); Assert.Equal(LogEventLevel.Information, blankResult); Assert.Empty(writer.ToString()); } [Fact] public void ParseLevel_RecognisedValue_NoWarning() { var writer = new StringWriter(); var result = LoggerConfigurationFactory.ParseLevel("Warning", writer); Assert.Equal(LogEventLevel.Warning, result); Assert.Empty(writer.ToString()); } /// /// Host-020: ScadaLink:Logging:MinimumLevel is the documented source /// of truth for the Serilog floor, and the explicit MinimumLevel.Is /// call deliberately runs after ReadFrom.Configuration(...) so a /// Serilog:MinimumLevel entry is overridden. To make that precedence /// visible — instead of silently swallowed — /// writes a one-shot warning when both keys are present. The warning must /// name both values and point the operator at the documented key. When the /// Serilog key is absent the warning is silent. /// [Fact] public void Build_BothMinimumLevelKeysSet_WarnsAboutOverride() { var writer = new StringWriter(); var configuration = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary { ["ScadaLink:Logging:MinimumLevel"] = "Warning", ["Serilog:MinimumLevel"] = "Debug", }) .Build(); LoggerConfigurationFactory.Build(configuration, "Central", "central", "node1", writer); var warning = writer.ToString(); Assert.Contains("warning", warning, StringComparison.OrdinalIgnoreCase); Assert.Contains("Serilog:MinimumLevel", warning); Assert.Contains("ScadaLink:Logging:MinimumLevel", warning); Assert.Contains("Debug", warning); Assert.Contains("Warning", warning); } [Fact] public void Build_OnlyScadaLinkMinimumLevelSet_NoOverrideWarning() { var writer = new StringWriter(); var configuration = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary { ["ScadaLink:Logging:MinimumLevel"] = "Warning", }) .Build(); LoggerConfigurationFactory.Build(configuration, "Central", "central", "node1", writer); // No Serilog override -> no override-warning. (The ScadaLink value is // a recognised level, so ParseLevel is silent too.) Assert.Empty(writer.ToString()); } }