diff --git a/src/ScadaLink.CLI/Commands/AuditLogCommands.cs b/src/ScadaLink.CLI/Commands/AuditLogCommands.cs
index 00fc084..eee6143 100644
--- a/src/ScadaLink.CLI/Commands/AuditLogCommands.cs
+++ b/src/ScadaLink.CLI/Commands/AuditLogCommands.cs
@@ -4,11 +4,50 @@ using ScadaLink.Commons.Messages.Management;
namespace ScadaLink.CLI.Commands;
+///
+/// The scadalink audit-config command group: views the configuration-change
+/// audit log (the IAuditService trail of admin edits — distinct from the
+/// centralized append-only Audit Log served by ).
+///
+///
+/// Renamed from audit-log in #23 M8-T7 to avoid confusion with the new
+/// scadalink audit group. The old audit-log name is retained as a
+/// deprecated alias; still resolves the full subcommand
+/// tree, and Program.cs prints a deprecation warning when it is used.
+///
public static class AuditLogCommands
{
+ /// The deprecated alias kept for backward compatibility with the old command name.
+ public const string DeprecatedAlias = "audit-log";
+
+ /// The deprecation warning emitted when the old audit-log name is used.
+ public const string DeprecationWarning =
+ "Warning: 'audit-log' is deprecated and will be removed in a future release. "
+ + "Use 'audit-config' instead.";
+
+ ///
+ /// Writes the to when the
+ /// CLI was invoked via the deprecated audit-log command name (i.e. the first
+ /// argument is ). The command itself still works — it is
+ /// an alias of audit-config — so this only adds the migration warning.
+ /// Factored out of Program.cs so it is unit-testable without spawning a process.
+ ///
+ public static void WriteDeprecationWarningIfNeeded(string[] args, TextWriter stderr)
+ {
+ if (args.Length > 0
+ && string.Equals(args[0], DeprecatedAlias, StringComparison.Ordinal))
+ {
+ stderr.WriteLine(DeprecationWarning);
+ }
+ }
+
public static Command Build(Option urlOption, Option formatOption, Option usernameOption, Option passwordOption)
{
- var command = new Command("audit-log") { Description = "Query audit logs" };
+ var command = new Command("audit-config") { Description = "Query the configuration-change audit log" };
+ // Backward-compatible alias for the pre-M8 `audit-log` name. The alias keeps
+ // full subcommand parity automatically; the deprecation warning is emitted by
+ // the args[0] check in Program.cs.
+ command.Aliases.Add(DeprecatedAlias);
command.Add(BuildQuery(urlOption, formatOption, usernameOption, passwordOption));
diff --git a/src/ScadaLink.CLI/Program.cs b/src/ScadaLink.CLI/Program.cs
index a79f678..240b410 100644
--- a/src/ScadaLink.CLI/Program.cs
+++ b/src/ScadaLink.CLI/Program.cs
@@ -39,5 +39,10 @@ rootCommand.SetAction(_ =>
Console.WriteLine("Use --help to see available commands.");
});
+// Deprecation notice for the pre-M8 `audit-log` command name. The command itself
+// still works (it is an alias of `audit-config`), but using the old name emits a
+// warning to stderr so scripts can be migrated.
+AuditLogCommands.WriteDeprecationWarningIfNeeded(args, Console.Error);
+
var parseResult = CommandLineParser.Parse(rootCommand, args);
return await parseResult.InvokeAsync();
diff --git a/tests/ScadaLink.CLI.Tests/Commands/AuditConfigDeprecationTests.cs b/tests/ScadaLink.CLI.Tests/Commands/AuditConfigDeprecationTests.cs
new file mode 100644
index 0000000..3cc4d20
--- /dev/null
+++ b/tests/ScadaLink.CLI.Tests/Commands/AuditConfigDeprecationTests.cs
@@ -0,0 +1,100 @@
+using System.CommandLine;
+using ScadaLink.CLI.Commands;
+
+namespace ScadaLink.CLI.Tests.Commands;
+
+///
+/// Tests for the audit-log → audit-config rename (Audit Log #23 M8-T7):
+/// the new name parses, the deprecated audit-log alias still resolves the full
+/// subcommand tree and emits a stderr deprecation warning, and the renamed group does
+/// not collide with the distinct audit group from Bundle A.
+///
+public class AuditConfigDeprecationTests
+{
+ private static readonly Option Url = new("--url") { Recursive = true };
+ private static readonly Option Username = new("--username") { Recursive = true };
+ private static readonly Option Password = new("--password") { Recursive = true };
+ private static readonly Option Format = CliOptions.CreateFormatOption();
+
+ private static RootCommand BuildRoot()
+ {
+ var root = new RootCommand();
+ root.Add(Url);
+ root.Add(Username);
+ root.Add(Password);
+ root.Add(Format);
+ root.Add(AuditCommands.Build(Url, Format, Username, Password));
+ root.Add(AuditLogCommands.Build(Url, Format, Username, Password));
+ return root;
+ }
+
+ [Fact]
+ public void AuditConfig_Query_Works()
+ {
+ // The new `audit-config query` name parses cleanly with no errors.
+ var root = BuildRoot();
+ var parse = root.Parse(new[] { "audit-config", "query", "--user", "alice" });
+ Assert.Empty(parse.Errors);
+ }
+
+ [Fact]
+ public void AuditLog_Query_StillWorks_ButEmitsDeprecationWarning_ToStderr()
+ {
+ // The deprecated `audit-log` alias still resolves the full subcommand tree...
+ var root = BuildRoot();
+ var parse = root.Parse(new[] { "audit-log", "query", "--user", "alice" });
+ Assert.Empty(parse.Errors);
+
+ // ...and invoking via the old name emits the deprecation warning to stderr.
+ var stderr = new StringWriter();
+ AuditLogCommands.WriteDeprecationWarningIfNeeded(
+ new[] { "audit-log", "query" }, stderr);
+ var warning = stderr.ToString();
+ Assert.Contains("deprecated", warning);
+ Assert.Contains("audit-config", warning);
+ }
+
+ [Fact]
+ public void DeprecationWarning_NotEmitted_ForNewName()
+ {
+ // The new `audit-config` name must not trigger the deprecation warning.
+ var stderr = new StringWriter();
+ AuditLogCommands.WriteDeprecationWarningIfNeeded(
+ new[] { "audit-config", "query" }, stderr);
+ Assert.Equal("", stderr.ToString());
+ }
+
+ [Fact]
+ public void DeprecationWarning_NotEmitted_ForUnrelatedCommand()
+ {
+ var stderr = new StringWriter();
+ AuditLogCommands.WriteDeprecationWarningIfNeeded(
+ new[] { "audit", "query" }, stderr);
+ Assert.Equal("", stderr.ToString());
+ }
+
+ [Fact]
+ public void Audit_And_AuditConfig_AreDistinctCommands_NoConflict()
+ {
+ var root = BuildRoot();
+
+ var auditNames = new[] { "audit", "audit-config" };
+ foreach (var name in auditNames)
+ {
+ var match = root.Subcommands.SingleOrDefault(c => c.Name == name);
+ Assert.NotNull(match);
+ }
+
+ // The two groups are distinct objects with distinct subcommand sets:
+ // `audit` has query/export/verify-chain; `audit-config` has only query.
+ var audit = root.Subcommands.Single(c => c.Name == "audit");
+ var auditConfig = root.Subcommands.Single(c => c.Name == "audit-config");
+ Assert.NotSame(audit, auditConfig);
+ Assert.Contains(audit.Subcommands, c => c.Name == "verify-chain");
+ Assert.DoesNotContain(auditConfig.Subcommands, c => c.Name == "verify-chain");
+
+ // `audit-config` carries the deprecated `audit-log` alias; `audit` does not.
+ Assert.Contains(AuditLogCommands.DeprecatedAlias, auditConfig.Aliases);
+ Assert.DoesNotContain(AuditLogCommands.DeprecatedAlias, audit.Aliases);
+ }
+}