diff --git a/tests/ScadaLink.CLI.Tests/Commands/AuditCommandTestHarness.cs b/tests/ScadaLink.CLI.Tests/Commands/AuditCommandTestHarness.cs index 56ad835..4050ea7 100644 --- a/tests/ScadaLink.CLI.Tests/Commands/AuditCommandTestHarness.cs +++ b/tests/ScadaLink.CLI.Tests/Commands/AuditCommandTestHarness.cs @@ -25,15 +25,38 @@ internal static class AuditCommandTestHarness return root; } + /// + /// Parses and invokes the command tree, capturing output from both channels the CLI + /// uses: System.CommandLine's parser diagnostics flow through the + /// writers, while command actions write through + /// (consistent with the rest of the CLI). Both are merged into + /// the returned Out/Err strings. Callers must be in the Console + /// xUnit collection so the global redirect is not racy. + /// public static (int Exit, string Out, string Err) Invoke(RootCommand root, params string[] args) { var output = new StringWriter(); var error = new StringWriter(); - var exit = root.Parse(args).Invoke(new InvocationConfiguration + + var originalOut = Console.Out; + var originalErr = Console.Error; + Console.SetOut(output); + Console.SetError(error); + int exit; + try { - Output = output, - Error = error, - }); + exit = root.Parse(args).Invoke(new InvocationConfiguration + { + Output = output, + Error = error, + }); + } + finally + { + Console.SetOut(originalOut); + Console.SetError(originalErr); + } + return (exit, output.ToString(), error.ToString()); } } diff --git a/tests/ScadaLink.CLI.Tests/Commands/AuditExportCommandTests.cs b/tests/ScadaLink.CLI.Tests/Commands/AuditExportCommandTests.cs index e802eb6..cd3349a 100644 --- a/tests/ScadaLink.CLI.Tests/Commands/AuditExportCommandTests.cs +++ b/tests/ScadaLink.CLI.Tests/Commands/AuditExportCommandTests.cs @@ -11,6 +11,7 @@ namespace ScadaLink.CLI.Tests.Commands; /// required-flag enforcement, query-string construction, streaming the response body /// to the output file, and the parquet-not-implemented (501) path. /// +[Collection("Console")] public class AuditExportCommandTests { // ---- CLI parsing: required flags -------------------------------------- diff --git a/tests/ScadaLink.CLI.Tests/Commands/AuditQueryCommandTests.cs b/tests/ScadaLink.CLI.Tests/Commands/AuditQueryCommandTests.cs index 7773a90..130c8c7 100644 --- a/tests/ScadaLink.CLI.Tests/Commands/AuditQueryCommandTests.cs +++ b/tests/ScadaLink.CLI.Tests/Commands/AuditQueryCommandTests.cs @@ -12,6 +12,7 @@ namespace ScadaLink.CLI.Tests.Commands; /// time-spec resolution, query-string construction, formatter selection, error /// handling, and keyset-cursor paging via --all. /// +[Collection("Console")] public class AuditQueryCommandTests { // ---- Time-spec parsing ------------------------------------------------- diff --git a/tests/ScadaLink.CLI.Tests/Commands/AuditVerifyChainCommandTests.cs b/tests/ScadaLink.CLI.Tests/Commands/AuditVerifyChainCommandTests.cs new file mode 100644 index 0000000..c399b92 --- /dev/null +++ b/tests/ScadaLink.CLI.Tests/Commands/AuditVerifyChainCommandTests.cs @@ -0,0 +1,58 @@ +using ScadaLink.CLI.Commands; + +namespace ScadaLink.CLI.Tests.Commands; + +/// +/// Tests for the scadalink audit verify-chain subcommand (Audit Log #23 M8-T4). +/// v1 is a no-op stub: a valid --month prints the documented "not enabled" +/// message and exits 0; a malformed month or a missing --month exits non-zero. +/// +[Collection("Console")] +public class AuditVerifyChainCommandTests +{ + [Fact] + public void VerifyChain_ValidMonth_ExitsZeroWithDocumentedMessage() + { + var root = AuditCommandTestHarness.BuildRoot(); + var (exit, output, _) = AuditCommandTestHarness.Invoke( + root, "audit", "verify-chain", "--month", "2026-05"); + + Assert.Equal(0, exit); + Assert.Contains("Hash-chain tamper-evidence is not enabled", output); + Assert.Contains("Component-AuditLog.md", output); + } + + [Fact] + public void VerifyChain_MalformedMonth_ExitsNonZero() + { + var root = AuditCommandTestHarness.BuildRoot(); + var (exit, _, _) = AuditCommandTestHarness.Invoke( + root, "audit", "verify-chain", "--month", "2026-13"); + + Assert.NotEqual(0, exit); + } + + [Fact] + public void VerifyChain_MissingMonth_ProducesRequiredFlagError() + { + var root = AuditCommandTestHarness.BuildRoot(); + var (exit, _, err) = AuditCommandTestHarness.Invoke(root, "audit", "verify-chain"); + + Assert.NotEqual(0, exit); + Assert.Contains("--month", err); + } + + [Theory] + [InlineData("2026-05", true)] + [InlineData("2026-01", true)] + [InlineData("2026-12", true)] + [InlineData("2026-13", false)] + [InlineData("2026-00", false)] + [InlineData("2026-5", false)] + [InlineData("not-a-month", false)] + [InlineData("", false)] + public void IsValidMonth_ValidatesYyyyMm(string month, bool expected) + { + Assert.Equal(expected, AuditVerifyChainHelpers.IsValidMonth(month)); + } +}