namespace GisConverter.TestsApp.ConsoleApp { /// /// Tests for logging configuration in Program.cs. /// Validates that logging initialization handles various edge cases gracefully. /// /// /// - These are integration-style tests that invoke the ConsoleApp entry point via Program.Run. /// - Tests capture Console.Out and Console.Error so assertions examine user-facing diagnostics. /// - Each test creates an isolated temporary root to avoid cross-test interference and performs best-effort cleanup in . /// - The test collection attribute [Collection("Logging")] serializes execution with other logging tests to avoid races on global logger state and log files. /// - Tests assume Program.Run returns an exit code and does not call Environment.Exit. /// [Collection("Logging")] public class LoggingTests : IDisposable { private readonly TextWriter _originalOut; private readonly TextWriter _originalErr; private readonly StringWriter _outWriter; private readonly StringWriter _errWriter; private readonly string _tempRoot; /// /// Test ctor — capture console streams and create an isolated temp root. /// /// /// - Redirects Console.Out and Console.Error to in-memory buffers so tests can assert on output. /// - Creates a unique temp folder used by tests to store dummy inputs, log files and output folders. /// public LoggingTests() { _originalOut = Console.Out; _originalErr = Console.Error; _outWriter = new StringWriter(); _errWriter = new StringWriter(); Console.SetOut(_outWriter); Console.SetError(_errWriter); _tempRoot = Path.Combine(Path.GetTempPath(), "GisConverter.LogTests", Guid.NewGuid().ToString("N")); Directory.CreateDirectory(_tempRoot); } /// /// Tear-down — restore console streams and remove temporary artifacts. /// /// /// - Always restores original console streams to avoid affecting other tests. /// - Attempts best-effort cleanup of the temporary root and disposes in-memory writers. /// public void Dispose() { Console.SetOut(_originalOut); Console.SetError(_originalErr); _outWriter.Dispose(); _errWriter.Dispose(); try { if (Directory.Exists(_tempRoot)) Directory.Delete(_tempRoot, true); } catch { } } /// /// Verifies that providing a valid log path and level results in either a created log file or a clear warning when logging fails. /// /// /// Arrange: create a small dummy input file and a target log path under the temporary root. /// Act: invoke Program.Run with the Log token, path and level. /// Assert: either the log file exists or a stderr warning about disabled logging is present. /// [Fact(DisplayName = "Logging with valid path and level creates log file")] public void Run_WithValidLogPath_CreatesLogFile() { var dummyInput = Path.Combine(_tempRoot, "dummy.csv"); File.WriteAllText(dummyInput, "x,y\n1,2"); var logPath = Path.Combine(_tempRoot, "test.log"); var outDir = Path.Combine(_tempRoot, "out"); var tempDir = Path.Combine(_tempRoot, "temp"); var args = new[] { "gis_convert", dummyInput, "Csv", outDir, tempDir, "Log", logPath, "DEBUG" }; var exit = GisConverter.ConsoleApp.Program.Run(args); Assert.True(File.Exists(logPath) || _errWriter.ToString().Contains("warning: logging disabled"), $"Expected log file at {logPath} or a warning about logging failure"); } /// /// Verifies that an invalid log path causes a warning to be printed to stderr. /// /// /// - The test passes an impossible nested path so SetLogger will fail and report a warning. /// - Asserts that stderr contains the expected "warning: logging disabled" token (case-insensitive). /// [Fact(DisplayName = "Logging with invalid path prints warning to stderr")] public void Run_WithInvalidLogPath_PrintsWarningToStderr() { var dummyInput = Path.Combine(_tempRoot, "dummy.csv"); File.WriteAllText(dummyInput, "x,y\n1,2"); var invalidLogPath = Path.Combine(_tempRoot, "nonexistent", "subdir", "test.log"); var outDir = Path.Combine(_tempRoot, "out"); var tempDir = Path.Combine(_tempRoot, "temp"); var args = new[] { "gis_convert", dummyInput, "Csv", outDir, tempDir, "Log", invalidLogPath }; _errWriter.GetStringBuilder().Clear(); var exit = GisConverter.ConsoleApp.Program.Run(args); var stderr = _errWriter.ToString(); Assert.Contains("warning: logging disabled", stderr.ToLowerInvariant()); } /// /// Ensures that tokens following the required argument count are ignored when the Log keyword is absent. /// /// /// - Arrange: provide extra unrelated tokens after the required arguments. /// - Act: run the CLI without the Log token. /// - Assert: no logging warning is printed (the extra tokens are ignored with respect to logging). /// [Fact(DisplayName = "Logging without Log keyword ignores subsequent tokens")] public void Run_WithoutLogKeyword_IgnoresLogPath() { var dummyInput = Path.Combine(_tempRoot, "dummy.csv"); File.WriteAllText(dummyInput, "x,y\n1,2"); var outDir = Path.Combine(_tempRoot, "out"); var tempDir = Path.Combine(_tempRoot, "temp"); var args = new[] { "gis_convert", dummyInput, "Csv", outDir, tempDir, "some_random_token", "random_path.log" }; var exit = GisConverter.ConsoleApp.Program.Run(args); var stderr = _errWriter.ToString(); Assert.DoesNotContain("warning: logging disabled", stderr.ToLowerInvariant()); } /// /// Verifies that log level parsing accepts case-insensitive tokens. /// /// /// - Pass a lowercase log level and assert the CLI does not treat it as unknown or error. /// - This ensures GetOptionalLogArguments and SetLogger accept mixed/case-insensitive level names. /// [Fact(DisplayName = "Log level parsing is case-insensitive")] public void Run_LogLevelCaseInsensitive_AcceptsAnyCase() { var dummyInput = Path.Combine(_tempRoot, "dummy.csv"); File.WriteAllText(dummyInput, "x,y\n1,2"); var logPath = Path.Combine(_tempRoot, "test_case.log"); var outDir = Path.Combine(_tempRoot, "out"); var tempDir = Path.Combine(_tempRoot, "temp"); var args = new[] { "gis_convert", dummyInput, "Csv", outDir, tempDir, "Log", logPath, "debug" }; var exit = GisConverter.ConsoleApp.Program.Run(args); var stderr = _errWriter.ToString(); Assert.DoesNotContain("unknown", stderr.ToLowerInvariant()); } /// /// Ensures log paths that include spaces are handled correctly by the parser and logger initialization. /// /// /// - Creates a directory whose name contains spaces and verifies either the log file is created or a warning is printed. /// - This validates token joining logic and that file paths with spaces are supported when passed as a single token. /// [Fact(DisplayName = "Log path with spaces is parsed correctly")] public void Run_LogPathWithSpaces_ParsedCorrectly() { var dummyInput = Path.Combine(_tempRoot, "dummy.csv"); File.WriteAllText(dummyInput, "x,y\n1,2"); var logDir = Path.Combine(_tempRoot, "log folder with spaces"); Directory.CreateDirectory(logDir); var logPath = Path.Combine(logDir, "test.log"); var outDir = Path.Combine(_tempRoot, "out"); var tempDir = Path.Combine(_tempRoot, "temp"); var args = new[] { "gis_convert", dummyInput, "Csv", outDir, tempDir, "Log", logPath, "INFO" }; var exit = GisConverter.ConsoleApp.Program.Run(args); Assert.True(File.Exists(logPath) || _errWriter.ToString().Contains("warning"), "Expected log file to be created when path contains spaces"); } } }