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:
+347
@@ -0,0 +1,347 @@
|
||||
using JdeScoping.ConfigManager.Constants;
|
||||
using JdeScoping.ConfigManager.ViewModels.Dialogs;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Tests.ViewModels.Dialogs;
|
||||
|
||||
public class UnlockStoreDialogViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_SetsStorePathFromParameter()
|
||||
{
|
||||
// Arrange
|
||||
var storePath = "/path/to/store.secure";
|
||||
|
||||
// Act
|
||||
var sut = new UnlockStoreDialogViewModel(storePath);
|
||||
|
||||
// Assert
|
||||
sut.StorePath.ShouldBe(storePath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullStorePath()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() => new UnlockStoreDialogViewModel(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_DefaultsToUseKeyFile()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure");
|
||||
|
||||
// Assert
|
||||
sut.UseKeyFile.ShouldBeTrue();
|
||||
sut.UsePassword.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StorePath_IsReadOnly()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure");
|
||||
|
||||
// Assert - StorePath property has no setter, verified by checking it returns what was passed
|
||||
sut.StorePath.ShouldBe("/path/to/store.secure");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyFilePath_WhenRequiredAndEmpty_IsValidReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure")
|
||||
{
|
||||
UseKeyFile = true,
|
||||
KeyFilePath = ""
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyFilePath_WhenRequiredAndEmpty_ValidationErrorReturnsKeyFilePathRequired()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure")
|
||||
{
|
||||
UseKeyFile = true,
|
||||
KeyFilePath = ""
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.ValidationError.ShouldBe(SecureStoreStrings.KeyFilePathRequired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyFilePath_WhenRequiredAndWhitespace_IsValidReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure")
|
||||
{
|
||||
UseKeyFile = true,
|
||||
KeyFilePath = " "
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
sut.ValidationError.ShouldBe(SecureStoreStrings.KeyFilePathRequired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyFilePath_WhenRequiredAndFileDoesNotExist_IsValidReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure")
|
||||
{
|
||||
UseKeyFile = true,
|
||||
KeyFilePath = "/nonexistent/path/to/key.key"
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyFilePath_WhenRequiredAndFileDoesNotExist_ValidationErrorReturnsKeyFileNotFound()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure")
|
||||
{
|
||||
UseKeyFile = true,
|
||||
KeyFilePath = "/nonexistent/path/to/key.key"
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.ValidationError.ShouldBe(SecureStoreStrings.KeyFileNotFound);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Password_WhenRequiredAndEmpty_IsValidReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure")
|
||||
{
|
||||
UsePassword = true,
|
||||
Password = ""
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Password_WhenRequiredAndEmpty_ValidationErrorReturnsPasswordRequired()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure")
|
||||
{
|
||||
UsePassword = true,
|
||||
Password = ""
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.ValidationError.ShouldBe(SecureStoreStrings.PasswordRequired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Password_WhenRequiredAndWhitespace_IsValidReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure")
|
||||
{
|
||||
UsePassword = true,
|
||||
Password = " "
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
sut.ValidationError.ShouldBe(SecureStoreStrings.PasswordRequired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Password_WhenRequiredAndProvided_IsValidReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure")
|
||||
{
|
||||
UsePassword = true,
|
||||
Password = "password123"
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeTrue();
|
||||
sut.ValidationError.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseKeyFile_WhenSetToTrue_SetsUsePasswordToFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure")
|
||||
{
|
||||
UsePassword = true
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.UseKeyFile = true;
|
||||
|
||||
// Assert
|
||||
sut.UseKeyFile.ShouldBeTrue();
|
||||
sut.UsePassword.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UsePassword_WhenSetToTrue_SetsUseKeyFileToFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure")
|
||||
{
|
||||
UseKeyFile = true
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.UsePassword = true;
|
||||
|
||||
// Assert
|
||||
sut.UsePassword.ShouldBeTrue();
|
||||
sut.UseKeyFile.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValid_WhenNeitherKeyFileNorPasswordSelected_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure");
|
||||
// Manually set both to false (shouldn't happen in UI, but test edge case)
|
||||
sut.UseKeyFile = false;
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyFilePath_WhenChanged_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure");
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(UnlockStoreDialogViewModel.KeyFilePath))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.KeyFilePath = "/new/key/path";
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyFilePath_WhenChanged_RaisesIsValidPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure");
|
||||
var isValidPropertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(UnlockStoreDialogViewModel.IsValid))
|
||||
isValidPropertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.KeyFilePath = "/new/key/path";
|
||||
|
||||
// Assert
|
||||
isValidPropertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyFilePath_WhenChanged_RaisesValidationErrorPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure");
|
||||
var validationErrorPropertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(UnlockStoreDialogViewModel.ValidationError))
|
||||
validationErrorPropertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.KeyFilePath = "/new/key/path";
|
||||
|
||||
// Assert
|
||||
validationErrorPropertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Password_WhenChanged_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure");
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(UnlockStoreDialogViewModel.Password))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.Password = "newpassword";
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseKeyFile_WhenChanged_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure") { UseKeyFile = false };
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(UnlockStoreDialogViewModel.UseKeyFile))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.UseKeyFile = true;
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UsePassword_WhenChanged_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure");
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(UnlockStoreDialogViewModel.UsePassword))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.UsePassword = true;
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BrowseKeyFilePathCommand_IsInitialized()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure");
|
||||
|
||||
// Assert
|
||||
sut.BrowseKeyFilePathCommand.ShouldNotBeNull();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user