feat(configmanager): integrate SecureStore for credential management
Add SecureStore integration to ConfigManager for secure handling of connection strings and sensitive configuration values. Includes store/secret management UI, encrypted .store file support, and comprehensive test coverage.
This commit is contained in:
+327
@@ -0,0 +1,327 @@
|
||||
using JdeScoping.ConfigManager.ViewModels.Forms;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Tests.ViewModels.Forms;
|
||||
|
||||
public class SecureStoreUnlockedFormViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_SetsPropertiesCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new SecureStoreUnlockedFormViewModel(
|
||||
"test.secrets",
|
||||
"/path/to/test.secrets",
|
||||
5,
|
||||
false,
|
||||
() => { },
|
||||
() => { },
|
||||
() => { });
|
||||
|
||||
// Assert
|
||||
sut.StoreName.ShouldBe("test.secrets");
|
||||
sut.StorePath.ShouldBe("/path/to/test.secrets");
|
||||
sut.SecretCount.ShouldBe(5);
|
||||
sut.HasUnsavedChanges.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithUnsavedChanges_SetsHasUnsavedChanges()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new SecureStoreUnlockedFormViewModel(
|
||||
"test.secrets",
|
||||
"/path/to/test.secrets",
|
||||
3,
|
||||
true,
|
||||
() => { },
|
||||
() => { },
|
||||
() => { });
|
||||
|
||||
// Assert
|
||||
sut.HasUnsavedChanges.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HasUnsavedChanges_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecureStoreUnlockedFormViewModel(
|
||||
"test.secrets",
|
||||
"/path/to/test.secrets",
|
||||
0,
|
||||
false,
|
||||
() => { },
|
||||
() => { },
|
||||
() => { });
|
||||
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(SecureStoreUnlockedFormViewModel.HasUnsavedChanges))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.HasUnsavedChanges = true;
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HasUnsavedChanges_DoesNotRaisePropertyChanged_WhenValueUnchanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecureStoreUnlockedFormViewModel(
|
||||
"test.secrets",
|
||||
"/path/to/test.secrets",
|
||||
0,
|
||||
false,
|
||||
() => { },
|
||||
() => { },
|
||||
() => { });
|
||||
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(SecureStoreUnlockedFormViewModel.HasUnsavedChanges))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.HasUnsavedChanges = false; // Same as initial value
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LockCommand_InvokesCallback()
|
||||
{
|
||||
// Arrange
|
||||
var lockCalled = false;
|
||||
var sut = new SecureStoreUnlockedFormViewModel(
|
||||
"test.secrets",
|
||||
"/path/to/test.secrets",
|
||||
0,
|
||||
false,
|
||||
() => lockCalled = true,
|
||||
() => { },
|
||||
() => { });
|
||||
|
||||
// Act
|
||||
sut.LockCommand.Execute(null);
|
||||
|
||||
// Assert
|
||||
lockCalled.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddSecretCommand_InvokesCallback()
|
||||
{
|
||||
// Arrange
|
||||
var addSecretCalled = false;
|
||||
var sut = new SecureStoreUnlockedFormViewModel(
|
||||
"test.secrets",
|
||||
"/path/to/test.secrets",
|
||||
0,
|
||||
false,
|
||||
() => { },
|
||||
() => addSecretCalled = true,
|
||||
() => { });
|
||||
|
||||
// Act
|
||||
sut.AddSecretCommand.Execute(null);
|
||||
|
||||
// Assert
|
||||
addSecretCalled.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SaveCommand_InvokesCallback()
|
||||
{
|
||||
// Arrange
|
||||
var saveCalled = false;
|
||||
var sut = new SecureStoreUnlockedFormViewModel(
|
||||
"test.secrets",
|
||||
"/path/to/test.secrets",
|
||||
0,
|
||||
true, // Must have unsaved changes for save to be enabled
|
||||
() => { },
|
||||
() => { },
|
||||
() => saveCalled = true);
|
||||
|
||||
// Act
|
||||
sut.SaveCommand.Execute(null);
|
||||
|
||||
// Assert
|
||||
saveCalled.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SaveCommand_CanExecute_ReturnsFalse_WhenNoUnsavedChanges()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecureStoreUnlockedFormViewModel(
|
||||
"test.secrets",
|
||||
"/path/to/test.secrets",
|
||||
0,
|
||||
false,
|
||||
() => { },
|
||||
() => { },
|
||||
() => { });
|
||||
|
||||
// Act & Assert
|
||||
sut.SaveCommand.CanExecute(null).ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SaveCommand_CanExecute_ReturnsTrue_WhenHasUnsavedChanges()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecureStoreUnlockedFormViewModel(
|
||||
"test.secrets",
|
||||
"/path/to/test.secrets",
|
||||
0,
|
||||
true,
|
||||
() => { },
|
||||
() => { },
|
||||
() => { });
|
||||
|
||||
// Act & Assert
|
||||
sut.SaveCommand.CanExecute(null).ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SaveCommand_CanExecute_Updates_WhenHasUnsavedChangesChanges()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecureStoreUnlockedFormViewModel(
|
||||
"test.secrets",
|
||||
"/path/to/test.secrets",
|
||||
0,
|
||||
false,
|
||||
() => { },
|
||||
() => { },
|
||||
() => { });
|
||||
|
||||
// Initial state - can't save
|
||||
sut.SaveCommand.CanExecute(null).ShouldBeFalse();
|
||||
|
||||
// Act
|
||||
sut.HasUnsavedChanges = true;
|
||||
|
||||
// Assert
|
||||
sut.SaveCommand.CanExecute(null).ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SaveCommand_RaisesCanExecuteChanged_WhenHasUnsavedChangesChanges()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecureStoreUnlockedFormViewModel(
|
||||
"test.secrets",
|
||||
"/path/to/test.secrets",
|
||||
0,
|
||||
false,
|
||||
() => { },
|
||||
() => { },
|
||||
() => { });
|
||||
|
||||
var canExecuteChangedRaised = false;
|
||||
sut.SaveCommand.CanExecuteChanged += (s, e) => canExecuteChangedRaised = true;
|
||||
|
||||
// Act
|
||||
sut.HasUnsavedChanges = true;
|
||||
|
||||
// Assert
|
||||
canExecuteChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LockCommand_CanExecute_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecureStoreUnlockedFormViewModel(
|
||||
"test.secrets",
|
||||
"/path/to/test.secrets",
|
||||
0,
|
||||
false,
|
||||
() => { },
|
||||
() => { },
|
||||
() => { });
|
||||
|
||||
// Act & Assert
|
||||
sut.LockCommand.CanExecute(null).ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddSecretCommand_CanExecute_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecureStoreUnlockedFormViewModel(
|
||||
"test.secrets",
|
||||
"/path/to/test.secrets",
|
||||
0,
|
||||
false,
|
||||
() => { },
|
||||
() => { },
|
||||
() => { });
|
||||
|
||||
// Act & Assert
|
||||
sut.AddSecretCommand.CanExecute(null).ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullStoreName()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new SecureStoreUnlockedFormViewModel(null!, "/path", 0, false, () => { }, () => { }, () => { }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullStorePath()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new SecureStoreUnlockedFormViewModel("test", null!, 0, false, () => { }, () => { }, () => { }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullLockCallback()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new SecureStoreUnlockedFormViewModel("test", "/path", 0, false, null!, () => { }, () => { }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullAddSecretCallback()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new SecureStoreUnlockedFormViewModel("test", "/path", 0, false, () => { }, null!, () => { }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullSaveCallback()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new SecureStoreUnlockedFormViewModel("test", "/path", 0, false, () => { }, () => { }, null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadOnlyProperties_CannotBeModified()
|
||||
{
|
||||
// Assert - Verify StoreName, StorePath, and SecretCount are get-only
|
||||
var storeNameProperty = typeof(SecureStoreUnlockedFormViewModel).GetProperty(nameof(SecureStoreUnlockedFormViewModel.StoreName));
|
||||
var storePathProperty = typeof(SecureStoreUnlockedFormViewModel).GetProperty(nameof(SecureStoreUnlockedFormViewModel.StorePath));
|
||||
var secretCountProperty = typeof(SecureStoreUnlockedFormViewModel).GetProperty(nameof(SecureStoreUnlockedFormViewModel.SecretCount));
|
||||
|
||||
storeNameProperty!.CanWrite.ShouldBeFalse();
|
||||
storePathProperty!.CanWrite.ShouldBeFalse();
|
||||
secretCountProperty!.CanWrite.ShouldBeFalse();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user