94d5a864e0
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.
328 lines
9.0 KiB
C#
328 lines
9.0 KiB
C#
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();
|
|
}
|
|
}
|