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:
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user