refactor(securestore): store entire connection strings in SecureStore
Eliminates placeholder substitution (${KEY}) in favor of storing complete
connection strings as single encrypted values. SecureStore now auto-creates
entries for all connection strings defined in appsettings. ConfigManager
editor reads/writes values directly to SecureStore.
This commit is contained in:
+135
@@ -294,4 +294,139 @@ public class SecureStoreManagerTests : IDisposable
|
||||
// Act & Assert
|
||||
Should.Throw<InvalidOperationException>(() => _sut.GetSecret("key"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnsureAllRequiredEntries_AddsMissingKeys()
|
||||
{
|
||||
// Arrange
|
||||
var storePath = Path.Combine(_testDirectory, "test.json");
|
||||
var keyPath = Path.Combine(_testDirectory, "test.key");
|
||||
_sut.CreateStore(storePath, keyPath);
|
||||
_sut.SetSecret("existingKey", "existingValue");
|
||||
_sut.Save();
|
||||
|
||||
// Act
|
||||
var addedKeys = _sut.EnsureAllRequiredEntries(
|
||||
new[] { "existingKey", "newKey1" },
|
||||
new[] { "LotFinder", "JDE" });
|
||||
|
||||
// Assert
|
||||
addedKeys.Count.ShouldBe(3);
|
||||
addedKeys.ShouldContain("newKey1");
|
||||
addedKeys.ShouldContain("LotFinder");
|
||||
addedKeys.ShouldContain("JDE");
|
||||
addedKeys.ShouldNotContain("existingKey");
|
||||
|
||||
// Verify all keys exist
|
||||
_sut.GetKeys().ShouldContain("existingKey");
|
||||
_sut.GetKeys().ShouldContain("newKey1");
|
||||
_sut.GetKeys().ShouldContain("LotFinder");
|
||||
_sut.GetKeys().ShouldContain("JDE");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnsureAllRequiredEntries_CreatesEmptyValues()
|
||||
{
|
||||
// Arrange
|
||||
var storePath = Path.Combine(_testDirectory, "test.json");
|
||||
var keyPath = Path.Combine(_testDirectory, "test.key");
|
||||
_sut.CreateStore(storePath, keyPath);
|
||||
|
||||
// Act
|
||||
_sut.EnsureAllRequiredEntries(
|
||||
new[] { "newKey" },
|
||||
Array.Empty<string>());
|
||||
|
||||
// Assert
|
||||
_sut.GetSecret("newKey").ShouldBe(string.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnsureAllRequiredEntries_DoesNotOverwriteExistingValues()
|
||||
{
|
||||
// Arrange
|
||||
var storePath = Path.Combine(_testDirectory, "test.json");
|
||||
var keyPath = Path.Combine(_testDirectory, "test.key");
|
||||
_sut.CreateStore(storePath, keyPath);
|
||||
_sut.SetSecret("existingKey", "originalValue");
|
||||
_sut.Save();
|
||||
|
||||
// Act
|
||||
var addedKeys = _sut.EnsureAllRequiredEntries(
|
||||
new[] { "existingKey" },
|
||||
Array.Empty<string>());
|
||||
|
||||
// Assert
|
||||
addedKeys.ShouldBeEmpty(); // Key already existed
|
||||
_sut.GetSecret("existingKey").ShouldBe("originalValue"); // Value preserved
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnsureAllRequiredEntries_AutoSavesWhenKeysAdded()
|
||||
{
|
||||
// Arrange
|
||||
var storePath = Path.Combine(_testDirectory, "test.json");
|
||||
var keyPath = Path.Combine(_testDirectory, "test.key");
|
||||
_sut.CreateStore(storePath, keyPath);
|
||||
|
||||
// Act
|
||||
_sut.EnsureAllRequiredEntries(
|
||||
new[] { "newKey" },
|
||||
Array.Empty<string>());
|
||||
|
||||
// Assert - should auto-save (HasUnsavedChanges should be false after calling EnsureAllRequiredEntries)
|
||||
_sut.HasUnsavedChanges.ShouldBeFalse();
|
||||
|
||||
// Verify persistence
|
||||
_sut.CloseStore();
|
||||
_sut.OpenStore(storePath, keyPath);
|
||||
_sut.GetKeys().ShouldContain("newKey");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnsureAllRequiredEntries_ReturnsEmptyList_WhenNoMissingKeys()
|
||||
{
|
||||
// Arrange
|
||||
var storePath = Path.Combine(_testDirectory, "test.json");
|
||||
var keyPath = Path.Combine(_testDirectory, "test.key");
|
||||
_sut.CreateStore(storePath, keyPath);
|
||||
_sut.SetSecret("key1", "value1");
|
||||
_sut.SetSecret("key2", "value2");
|
||||
_sut.Save();
|
||||
|
||||
// Act
|
||||
var addedKeys = _sut.EnsureAllRequiredEntries(
|
||||
new[] { "key1" },
|
||||
new[] { "key2" });
|
||||
|
||||
// Assert
|
||||
addedKeys.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnsureAllRequiredEntries_WhenNoStoreOpen_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<InvalidOperationException>(() => _sut.EnsureAllRequiredEntries(
|
||||
new[] { "key" },
|
||||
Array.Empty<string>()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnsureAllRequiredEntries_HandlesDuplicatesAcrossLists()
|
||||
{
|
||||
// Arrange
|
||||
var storePath = Path.Combine(_testDirectory, "test.json");
|
||||
var keyPath = Path.Combine(_testDirectory, "test.key");
|
||||
_sut.CreateStore(storePath, keyPath);
|
||||
|
||||
// Act - same key in both lists should not cause issues
|
||||
var addedKeys = _sut.EnsureAllRequiredEntries(
|
||||
new[] { "sharedKey" },
|
||||
new[] { "sharedKey" });
|
||||
|
||||
// Assert - should only add once
|
||||
addedKeys.Count.ShouldBe(1);
|
||||
addedKeys.ShouldContain("sharedKey");
|
||||
}
|
||||
}
|
||||
|
||||
+25
-8
@@ -1,18 +1,24 @@
|
||||
using JdeScoping.ConfigManager.Models;
|
||||
using JdeScoping.ConfigManager.Services;
|
||||
using JdeScoping.ConfigManager.Services.SecureStore;
|
||||
using JdeScoping.ConfigManager.ViewModels.Forms;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Tests.ViewModels.Forms;
|
||||
|
||||
public class ConnectionStringsFormViewModelTests
|
||||
{
|
||||
private readonly ISecureStoreManager _secureStoreManager;
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly IConnectionTestService _connectionTestService;
|
||||
|
||||
public ConnectionStringsFormViewModelTests()
|
||||
{
|
||||
_secureStoreManager = Substitute.For<ISecureStoreManager>();
|
||||
_dialogService = Substitute.For<IDialogService>();
|
||||
_connectionTestService = Substitute.For<IConnectionTestService>();
|
||||
|
||||
// Setup default behavior - SecureStore is not open by default in tests
|
||||
_secureStoreManager.IsStoreOpen.Returns(false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -39,7 +45,7 @@ public class ConnectionStringsFormViewModelTests
|
||||
};
|
||||
|
||||
// Act
|
||||
var sut = new ConnectionStringsFormViewModel(model, () => { }, _dialogService, _connectionTestService);
|
||||
var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService);
|
||||
|
||||
// Assert
|
||||
sut.Connections.Count.ShouldBe(2);
|
||||
@@ -56,7 +62,18 @@ public class ConnectionStringsFormViewModelTests
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new ConnectionStringsFormViewModel(null!, () => { }, _dialogService, _connectionTestService));
|
||||
new ConnectionStringsFormViewModel(null!, _secureStoreManager, () => { }, _dialogService, _connectionTestService));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullSecureStoreManager()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection();
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new ConnectionStringsFormViewModel(model, null!, () => { }, _dialogService, _connectionTestService));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -67,7 +84,7 @@ public class ConnectionStringsFormViewModelTests
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new ConnectionStringsFormViewModel(model, null!, _dialogService, _connectionTestService));
|
||||
new ConnectionStringsFormViewModel(model, _secureStoreManager, null!, _dialogService, _connectionTestService));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -78,7 +95,7 @@ public class ConnectionStringsFormViewModelTests
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new ConnectionStringsFormViewModel(model, () => { }, _dialogService, null!));
|
||||
new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -87,7 +104,7 @@ public class ConnectionStringsFormViewModelTests
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection();
|
||||
var changedInvoked = false;
|
||||
var sut = new ConnectionStringsFormViewModel(model, () => changedInvoked = true, _dialogService, _connectionTestService);
|
||||
var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => changedInvoked = true, _dialogService, _connectionTestService);
|
||||
|
||||
// Act
|
||||
sut.AddConnectionCommand.Execute(null);
|
||||
@@ -111,7 +128,7 @@ public class ConnectionStringsFormViewModelTests
|
||||
new ConnectionStringEntry { Name = "Conn1" }
|
||||
}
|
||||
};
|
||||
var sut = new ConnectionStringsFormViewModel(model, () => { }, _dialogService, _connectionTestService);
|
||||
var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService);
|
||||
|
||||
// Assert - no selection by default
|
||||
sut.SelectedConnection.ShouldBeNull();
|
||||
@@ -129,7 +146,7 @@ public class ConnectionStringsFormViewModelTests
|
||||
new ConnectionStringEntry { Name = "Conn1" }
|
||||
}
|
||||
};
|
||||
var sut = new ConnectionStringsFormViewModel(model, () => { }, _dialogService, _connectionTestService);
|
||||
var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService);
|
||||
|
||||
// Act
|
||||
sut.SelectedConnection = sut.Connections[0];
|
||||
@@ -153,7 +170,7 @@ public class ConnectionStringsFormViewModelTests
|
||||
};
|
||||
|
||||
// Act
|
||||
var sut = new ConnectionStringsFormViewModel(model, () => { }, _dialogService, _connectionTestService);
|
||||
var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService);
|
||||
|
||||
// Assert
|
||||
sut.ConnectionCount.ShouldBe(3);
|
||||
|
||||
Reference in New Issue
Block a user