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:
+412
@@ -0,0 +1,412 @@
|
||||
using JdeScoping.ConfigManager.Services;
|
||||
using JdeScoping.ConfigManager.ViewModels.Forms;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Tests.ViewModels.Forms;
|
||||
|
||||
public class SecretFormViewModelTests
|
||||
{
|
||||
private readonly IClipboardService _clipboardService;
|
||||
|
||||
public SecretFormViewModelTests()
|
||||
{
|
||||
_clipboardService = Substitute.For<IClipboardService>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_SetsPropertiesCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"secret-value-123",
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => { });
|
||||
|
||||
// Assert
|
||||
sut.Key.ShouldBe("API_KEY");
|
||||
sut.Value.ShouldBe("secret-value-123");
|
||||
sut.IsValueVisible.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithNullValue_SetsEmptyString()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
null!,
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => { });
|
||||
|
||||
// Assert
|
||||
sut.Value.ShouldBe(string.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Value_Setter_InvokesCallback()
|
||||
{
|
||||
// Arrange
|
||||
string? changedValue = null;
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"initial",
|
||||
_clipboardService,
|
||||
v => changedValue = v,
|
||||
() => { });
|
||||
|
||||
// Act
|
||||
sut.Value = "new-value";
|
||||
|
||||
// Assert
|
||||
changedValue.ShouldBe("new-value");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Value_Setter_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"initial",
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => { });
|
||||
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(SecretFormViewModel.Value))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.Value = "new-value";
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Value_Setter_RaisesDisplayValuePropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"initial",
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => { });
|
||||
|
||||
var displayValueChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(SecretFormViewModel.DisplayValue))
|
||||
displayValueChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.Value = "new-value";
|
||||
|
||||
// Assert
|
||||
displayValueChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValueVisible_Toggle_Works()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"secret",
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => { });
|
||||
|
||||
// Act & Assert - Initial state
|
||||
sut.IsValueVisible.ShouldBeFalse();
|
||||
|
||||
// Act - Toggle on
|
||||
sut.IsValueVisible = true;
|
||||
sut.IsValueVisible.ShouldBeTrue();
|
||||
|
||||
// Act - Toggle off
|
||||
sut.IsValueVisible = false;
|
||||
sut.IsValueVisible.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValueVisible_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"secret",
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => { });
|
||||
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(SecretFormViewModel.IsValueVisible))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.IsValueVisible = true;
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValueVisible_RaisesDisplayValueAndVisibilityButtonTextPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"secret",
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => { });
|
||||
|
||||
var displayValueChangedRaised = false;
|
||||
var visibilityButtonTextChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(SecretFormViewModel.DisplayValue))
|
||||
displayValueChangedRaised = true;
|
||||
if (e.PropertyName == nameof(SecretFormViewModel.VisibilityButtonText))
|
||||
visibilityButtonTextChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.IsValueVisible = true;
|
||||
|
||||
// Assert
|
||||
displayValueChangedRaised.ShouldBeTrue();
|
||||
visibilityButtonTextChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisplayValue_ShowsMaskedValue_WhenNotVisible()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"secret123",
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => { });
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValueVisible.ShouldBeFalse();
|
||||
sut.DisplayValue.ShouldBe("\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"); // 9 bullet points
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisplayValue_ShowsActualValue_WhenVisible()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"secret123",
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => { });
|
||||
|
||||
// Act
|
||||
sut.IsValueVisible = true;
|
||||
|
||||
// Assert
|
||||
sut.DisplayValue.ShouldBe("secret123");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisplayValue_LimitsMaskedLength_ToTwentyCharacters()
|
||||
{
|
||||
// Arrange
|
||||
var longValue = new string('x', 50);
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
longValue,
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => { });
|
||||
|
||||
// Act & Assert
|
||||
sut.DisplayValue.Length.ShouldBe(20);
|
||||
sut.DisplayValue.ShouldBe(new string('\u2022', 20));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisplayValue_HandlesEmptyValue()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"",
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => { });
|
||||
|
||||
// Act & Assert
|
||||
sut.DisplayValue.ShouldBe("");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VisibilityButtonText_ShowsShow_WhenHidden()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"secret",
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => { });
|
||||
|
||||
// Act & Assert
|
||||
sut.VisibilityButtonText.ShouldBe("Show");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VisibilityButtonText_ShowsHide_WhenVisible()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"secret",
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => { });
|
||||
|
||||
// Act
|
||||
sut.IsValueVisible = true;
|
||||
|
||||
// Assert
|
||||
sut.VisibilityButtonText.ShouldBe("Hide");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToggleVisibilityCommand_TogglesVisibility()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"secret",
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => { });
|
||||
|
||||
// Act & Assert - Toggle on
|
||||
sut.ToggleVisibilityCommand.Execute(null);
|
||||
sut.IsValueVisible.ShouldBeTrue();
|
||||
|
||||
// Act & Assert - Toggle off
|
||||
sut.ToggleVisibilityCommand.Execute(null);
|
||||
sut.IsValueVisible.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CopyToClipboardCommand_CopiesValueToClipboard()
|
||||
{
|
||||
// Arrange
|
||||
_clipboardService.SetTextAsync(Arg.Any<string>()).Returns(Task.CompletedTask);
|
||||
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"secret-to-copy",
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => { });
|
||||
|
||||
// Act
|
||||
sut.CopyToClipboardCommand.Execute(null);
|
||||
|
||||
// Small delay to allow async command to complete
|
||||
await Task.Delay(50);
|
||||
|
||||
// Assert
|
||||
await _clipboardService.Received(1).SetTextAsync("secret-to-copy");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeleteCommand_InvokesCallback()
|
||||
{
|
||||
// Arrange
|
||||
var deleteCalled = false;
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"secret",
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => deleteCalled = true);
|
||||
|
||||
// Act
|
||||
sut.DeleteCommand.Execute(null);
|
||||
|
||||
// Assert
|
||||
deleteCalled.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullKey()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new SecretFormViewModel(null!, "value", _clipboardService, _ => { }, () => { }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullClipboardService()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new SecretFormViewModel("key", "value", null!, _ => { }, () => { }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullValueChangedCallback()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new SecretFormViewModel("key", "value", _clipboardService, null!, () => { }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullDeleteCallback()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new SecretFormViewModel("key", "value", _clipboardService, _ => { }, null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Key_IsReadOnly()
|
||||
{
|
||||
// Assert - Verify Key property is get-only
|
||||
var keyProperty = typeof(SecretFormViewModel).GetProperty(nameof(SecretFormViewModel.Key));
|
||||
keyProperty!.CanWrite.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Value_DoesNotInvokeCallback_WhenValueUnchanged()
|
||||
{
|
||||
// Arrange
|
||||
var callbackCount = 0;
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"same-value",
|
||||
_clipboardService,
|
||||
_ => callbackCount++,
|
||||
() => { });
|
||||
|
||||
// Act
|
||||
sut.Value = "same-value"; // Same as initial value
|
||||
|
||||
// Assert
|
||||
callbackCount.ShouldBe(0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user