feat(configmanager): add config set and connection update CLI commands

Add missing CLI commands to match UI capabilities: config set commands for
all configuration sections (datasync, dataaccess, auth, ldap, search,
excelexport) and connection update command. Also adds unit tests for
SecretCommands, ValidateCommand, and TestConnectionCommand.
This commit is contained in:
Joseph Doherty
2026-01-28 15:10:22 -05:00
parent bad0102af1
commit 61694ca50b
11 changed files with 2172 additions and 2 deletions
@@ -0,0 +1,211 @@
using System.CommandLine;
using JdeScoping.ConfigManager.Cli.Commands;
using JdeScoping.ConfigManager.Core.Models;
using JdeScoping.ConfigManager.Core.Services;
using JdeScoping.ConfigManager.Core.Services.SecureStore;
using Microsoft.Extensions.DependencyInjection;
namespace JdeScoping.ConfigManager.Cli.Tests.Commands;
[Collection("Console Tests")]
public class SecretCommandsTests
{
private readonly IServiceProvider _serviceProvider;
private readonly IConfigFileService _configFileService;
private readonly IAutoDiscoveryService _autoDiscoveryService;
private readonly ISecureStoreManager _secureStoreManager;
private readonly Option<string?> _configPathOption;
private readonly Option<bool> _verboseOption;
private readonly Option<bool> _quietOption;
public SecretCommandsTests()
{
_configFileService = Substitute.For<IConfigFileService>();
_autoDiscoveryService = Substitute.For<IAutoDiscoveryService>();
_secureStoreManager = Substitute.For<ISecureStoreManager>();
var services = new ServiceCollection();
services.AddSingleton(_configFileService);
services.AddSingleton(_autoDiscoveryService);
services.AddSingleton(_secureStoreManager);
_serviceProvider = services.BuildServiceProvider();
_configPathOption = new Option<string?>(["--config-path", "-c"]);
_verboseOption = new Option<bool>(["--verbose", "-v"]);
_quietOption = new Option<bool>(["--quiet", "-q"]);
}
[Fact]
public void CreateListCommand_ReturnsCommand()
{
// Act
var command = SecretCommands.CreateListCommand(
_serviceProvider, _configPathOption, _verboseOption, _quietOption);
// Assert
command.ShouldNotBeNull();
command.Name.ShouldBe("list");
command.Description.ShouldBe("List all secret keys");
}
[Fact]
public void CreateGetCommand_ReturnsCommandWithKeyArgument()
{
// Act
var command = SecretCommands.CreateGetCommand(
_serviceProvider, _configPathOption, _verboseOption, _quietOption);
// Assert
command.ShouldNotBeNull();
command.Name.ShouldBe("get");
command.Description.ShouldBe("Get a secret value");
command.Arguments.ShouldContain(a => a.Name == "key");
}
[Fact]
public void CreateSetCommand_ReturnsCommandWithKeyAndValueArguments()
{
// Act
var command = SecretCommands.CreateSetCommand(
_serviceProvider, _configPathOption, _verboseOption, _quietOption);
// Assert
command.ShouldNotBeNull();
command.Name.ShouldBe("set");
command.Description.ShouldBe("Set or update a secret");
command.Arguments.ShouldContain(a => a.Name == "key");
command.Arguments.ShouldContain(a => a.Name == "value");
}
[Fact]
public void CreateRemoveCommand_ReturnsCommandWithKeyArgument()
{
// Act
var command = SecretCommands.CreateRemoveCommand(
_serviceProvider, _configPathOption, _verboseOption, _quietOption);
// Assert
command.ShouldNotBeNull();
command.Name.ShouldBe("remove");
command.Description.ShouldBe("Remove a secret");
command.Arguments.ShouldContain(a => a.Name == "key");
}
[Fact]
public void CreateInitCommand_ReturnsCommandWithOptions()
{
// Act
var command = SecretCommands.CreateInitCommand(
_serviceProvider, _configPathOption, _verboseOption, _quietOption);
// Assert
command.ShouldNotBeNull();
command.Name.ShouldBe("init");
command.Description.ShouldBe("Initialize a new SecureStore");
command.Options.ShouldContain(o => o.Name == "store");
command.Options.ShouldContain(o => o.Name == "key");
}
[Fact]
public async Task ListCommand_WithNoConfigFolder_ReturnsError()
{
// Arrange
_autoDiscoveryService.FindConfigFolderAsync(Arg.Any<CancellationToken>())
.Returns(Task.FromResult<string?>(null));
var command = SecretCommands.CreateListCommand(
_serviceProvider, _configPathOption, _verboseOption, _quietOption);
var rootCommand = new RootCommand { command };
rootCommand.AddGlobalOption(_configPathOption);
rootCommand.AddGlobalOption(_verboseOption);
rootCommand.AddGlobalOption(_quietOption);
// Act
var exitCode = await rootCommand.InvokeAsync(["list"]);
// Assert - command completes without throwing
command.ShouldNotBeNull();
}
[Fact]
public async Task GetCommand_WithMissingKey_ReturnsError()
{
// Arrange
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
Directory.CreateDirectory(tempDir);
var appSettingsPath = Path.Combine(tempDir, "appsettings.json");
File.WriteAllText(appSettingsPath, "{}");
var storePath = Path.Combine(tempDir, "data", "secrets.json");
var keyPath = Path.Combine(tempDir, "data", "secrets.key");
Directory.CreateDirectory(Path.Combine(tempDir, "data"));
File.WriteAllText(storePath, "{}");
File.WriteAllText(keyPath, "test-key");
try
{
_autoDiscoveryService.FindConfigFolderAsync(Arg.Any<CancellationToken>())
.Returns(Task.FromResult<string?>(tempDir));
var config = new ConfigModel
{
SecureStore = new SecureStoreSection
{
StorePath = "data/secrets.json",
KeyFilePath = "data/secrets.key"
}
};
_configFileService.LoadAppSettingsAsync(appSettingsPath, Arg.Any<CancellationToken>())
.Returns(Task.FromResult(config));
_secureStoreManager.When(m => m.GetSecret("nonexistent"))
.Throw(new KeyNotFoundException("Secret 'nonexistent' not found"));
var command = SecretCommands.CreateGetCommand(
_serviceProvider, _configPathOption, _verboseOption, _quietOption);
var rootCommand = new RootCommand { command };
rootCommand.AddGlobalOption(_configPathOption);
rootCommand.AddGlobalOption(_verboseOption);
rootCommand.AddGlobalOption(_quietOption);
// Capture console error output
var originalErr = Console.Error;
using var writer = new StringWriter();
Console.SetError(writer);
try
{
// Act
await rootCommand.InvokeAsync(["get", "nonexistent"]);
// Assert
var output = writer.ToString();
output.ShouldContain("not found");
}
finally
{
Console.SetError(originalErr);
}
}
finally
{
Directory.Delete(tempDir, true);
}
}
[Fact]
public void InitCommand_HasCorrectOptions()
{
// Arrange & Act
var command = SecretCommands.CreateInitCommand(
_serviceProvider, _configPathOption, _verboseOption, _quietOption);
// Assert
var storeOption = command.Options.FirstOrDefault(o => o.Name == "store");
var keyOption = command.Options.FirstOrDefault(o => o.Name == "key");
storeOption.ShouldNotBeNull();
keyOption.ShouldNotBeNull();
}
}