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);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using JdeScoping.Core.Interfaces;
|
||||
using JdeScoping.DataAccess.Exceptions;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using Shouldly;
|
||||
@@ -9,15 +9,16 @@ namespace JdeScoping.DataAccess.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for DbConnectionFactory GIW connection support.
|
||||
/// Connection strings are retrieved from ISecureStoreService.
|
||||
/// </summary>
|
||||
public class DbConnectionFactoryGiwTests
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ISecureStoreService _secureStore;
|
||||
private readonly ILogger<DbConnectionFactory> _logger;
|
||||
|
||||
public DbConnectionFactoryGiwTests()
|
||||
{
|
||||
_configuration = Substitute.For<IConfiguration>();
|
||||
_secureStore = Substitute.For<ISecureStoreService>();
|
||||
_logger = Substitute.For<ILogger<DbConnectionFactory>>();
|
||||
}
|
||||
|
||||
@@ -25,8 +26,8 @@ public class DbConnectionFactoryGiwTests
|
||||
public async Task CreateGiwConnectionAsync_MissingConnectionString_ThrowsConnectionException()
|
||||
{
|
||||
// Arrange
|
||||
_configuration.GetConnectionString("GIW").Returns((string?)null);
|
||||
var factory = new DbConnectionFactory(_configuration, _logger);
|
||||
_secureStore.Get("GIW").Returns((string?)null);
|
||||
var factory = new DbConnectionFactory(_secureStore, _logger);
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Should.ThrowAsync<ConnectionException>(
|
||||
@@ -40,8 +41,8 @@ public class DbConnectionFactoryGiwTests
|
||||
public async Task CreateGiwConnectionAsync_EmptyConnectionString_ThrowsConnectionException()
|
||||
{
|
||||
// Arrange
|
||||
_configuration.GetConnectionString("GIW").Returns(string.Empty);
|
||||
var factory = new DbConnectionFactory(_configuration, _logger);
|
||||
_secureStore.Get("GIW").Returns(string.Empty);
|
||||
var factory = new DbConnectionFactory(_secureStore, _logger);
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Should.ThrowAsync<ConnectionException>(
|
||||
@@ -55,8 +56,8 @@ public class DbConnectionFactoryGiwTests
|
||||
public async Task CreateGiwConnectionAsync_InvalidConnectionString_ThrowsConnectionException()
|
||||
{
|
||||
// Arrange
|
||||
_configuration.GetConnectionString("GIW").Returns("Invalid oracle connection");
|
||||
var factory = new DbConnectionFactory(_configuration, _logger);
|
||||
_secureStore.Get("GIW").Returns("Invalid oracle connection");
|
||||
var factory = new DbConnectionFactory(_secureStore, _logger);
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Should.ThrowAsync<ConnectionException>(
|
||||
@@ -71,8 +72,8 @@ public class DbConnectionFactoryGiwTests
|
||||
public async Task CreateGiwConnectionAsync_CancellationRequested_ThrowsOperationCanceledException()
|
||||
{
|
||||
// Arrange
|
||||
_configuration.GetConnectionString("GIW").Returns("User Id=test;Password=test;Data Source=test");
|
||||
var factory = new DbConnectionFactory(_configuration, _logger);
|
||||
_secureStore.Get("GIW").Returns("User Id=test;Password=test;Data Source=test");
|
||||
var factory = new DbConnectionFactory(_secureStore, _logger);
|
||||
using var cts = new CancellationTokenSource();
|
||||
cts.Cancel();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using JdeScoping.Core.Interfaces;
|
||||
using JdeScoping.DataAccess.Exceptions;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using Shouldly;
|
||||
@@ -9,33 +9,34 @@ namespace JdeScoping.DataAccess.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for DbConnectionFactory.
|
||||
/// Connection strings are retrieved from ISecureStoreService.
|
||||
/// </summary>
|
||||
public class DbConnectionFactoryTests
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ISecureStoreService _secureStore;
|
||||
private readonly ILogger<DbConnectionFactory> _logger;
|
||||
|
||||
public DbConnectionFactoryTests()
|
||||
{
|
||||
_configuration = Substitute.For<IConfiguration>();
|
||||
_secureStore = Substitute.For<ISecureStoreService>();
|
||||
_logger = Substitute.For<ILogger<DbConnectionFactory>>();
|
||||
}
|
||||
|
||||
#region Constructor Tests
|
||||
|
||||
[Fact]
|
||||
public void Constructor_NullConfiguration_ThrowsArgumentNullException()
|
||||
public void Constructor_NullSecureStore_ThrowsArgumentNullException()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() => new DbConnectionFactory(null!, _logger))
|
||||
.ParamName.ShouldBe("configuration");
|
||||
.ParamName.ShouldBe("secureStore");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_NullLogger_ThrowsArgumentNullException()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() => new DbConnectionFactory(_configuration, null!))
|
||||
Should.Throw<ArgumentNullException>(() => new DbConnectionFactory(_secureStore, null!))
|
||||
.ParamName.ShouldBe("logger");
|
||||
}
|
||||
|
||||
@@ -43,7 +44,7 @@ public class DbConnectionFactoryTests
|
||||
public void Constructor_ValidParameters_CreatesInstance()
|
||||
{
|
||||
// Act
|
||||
var factory = new DbConnectionFactory(_configuration, _logger);
|
||||
var factory = new DbConnectionFactory(_secureStore, _logger);
|
||||
|
||||
// Assert
|
||||
factory.ShouldNotBeNull();
|
||||
@@ -57,14 +58,14 @@ public class DbConnectionFactoryTests
|
||||
public async Task CreateLotFinderConnectionAsync_MissingConnectionString_ThrowsConnectionException()
|
||||
{
|
||||
// Arrange
|
||||
_configuration.GetConnectionString("LotFinderDB").Returns((string?)null);
|
||||
var factory = new DbConnectionFactory(_configuration, _logger);
|
||||
_secureStore.Get("LotFinder").Returns((string?)null);
|
||||
var factory = new DbConnectionFactory(_secureStore, _logger);
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Should.ThrowAsync<ConnectionException>(
|
||||
async () => await factory.CreateLotFinderConnectionAsync());
|
||||
|
||||
ex.DataSource.ShouldBe("LotFinderDB");
|
||||
ex.DataSource.ShouldBe("LotFinder");
|
||||
ex.Message.ShouldContain("Connection string not found");
|
||||
}
|
||||
|
||||
@@ -72,14 +73,14 @@ public class DbConnectionFactoryTests
|
||||
public async Task CreateLotFinderConnectionAsync_EmptyConnectionString_ThrowsConnectionException()
|
||||
{
|
||||
// Arrange
|
||||
_configuration.GetConnectionString("LotFinderDB").Returns(string.Empty);
|
||||
var factory = new DbConnectionFactory(_configuration, _logger);
|
||||
_secureStore.Get("LotFinder").Returns(string.Empty);
|
||||
var factory = new DbConnectionFactory(_secureStore, _logger);
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Should.ThrowAsync<ConnectionException>(
|
||||
async () => await factory.CreateLotFinderConnectionAsync());
|
||||
|
||||
ex.DataSource.ShouldBe("LotFinderDB");
|
||||
ex.DataSource.ShouldBe("LotFinder");
|
||||
ex.Message.ShouldContain("Connection string not found");
|
||||
}
|
||||
|
||||
@@ -87,14 +88,14 @@ public class DbConnectionFactoryTests
|
||||
public async Task CreateLotFinderConnectionAsync_InvalidConnectionString_ThrowsConnectionException()
|
||||
{
|
||||
// Arrange
|
||||
_configuration.GetConnectionString("LotFinderDB").Returns("Invalid connection string");
|
||||
var factory = new DbConnectionFactory(_configuration, _logger);
|
||||
_secureStore.Get("LotFinder").Returns("Invalid connection string");
|
||||
var factory = new DbConnectionFactory(_secureStore, _logger);
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Should.ThrowAsync<ConnectionException>(
|
||||
async () => await factory.CreateLotFinderConnectionAsync());
|
||||
|
||||
ex.DataSource.ShouldBe("LotFinderDB");
|
||||
ex.DataSource.ShouldBe("LotFinder");
|
||||
ex.Message.ShouldContain("Failed to open connection");
|
||||
ex.InnerException.ShouldNotBeNull();
|
||||
}
|
||||
@@ -103,8 +104,8 @@ public class DbConnectionFactoryTests
|
||||
public async Task CreateLotFinderConnectionAsync_CancellationRequested_ThrowsOperationCanceledException()
|
||||
{
|
||||
// Arrange
|
||||
_configuration.GetConnectionString("LotFinderDB").Returns("Server=test;Database=test;");
|
||||
var factory = new DbConnectionFactory(_configuration, _logger);
|
||||
_secureStore.Get("LotFinder").Returns("Server=test;Database=test;");
|
||||
var factory = new DbConnectionFactory(_secureStore, _logger);
|
||||
using var cts = new CancellationTokenSource();
|
||||
cts.Cancel();
|
||||
|
||||
@@ -121,8 +122,8 @@ public class DbConnectionFactoryTests
|
||||
public async Task CreateJdeConnectionAsync_MissingConnectionString_ThrowsConnectionException()
|
||||
{
|
||||
// Arrange
|
||||
_configuration.GetConnectionString("JDE").Returns((string?)null);
|
||||
var factory = new DbConnectionFactory(_configuration, _logger);
|
||||
_secureStore.Get("JDE").Returns((string?)null);
|
||||
var factory = new DbConnectionFactory(_secureStore, _logger);
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Should.ThrowAsync<ConnectionException>(
|
||||
@@ -136,8 +137,8 @@ public class DbConnectionFactoryTests
|
||||
public async Task CreateJdeConnectionAsync_InvalidConnectionString_ThrowsConnectionException()
|
||||
{
|
||||
// Arrange
|
||||
_configuration.GetConnectionString("JDE").Returns("Invalid oracle connection");
|
||||
var factory = new DbConnectionFactory(_configuration, _logger);
|
||||
_secureStore.Get("JDE").Returns("Invalid oracle connection");
|
||||
var factory = new DbConnectionFactory(_secureStore, _logger);
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Should.ThrowAsync<ConnectionException>(
|
||||
@@ -155,8 +156,8 @@ public class DbConnectionFactoryTests
|
||||
public async Task CreateJdeStageConnectionAsync_MissingConnectionString_ThrowsConnectionException()
|
||||
{
|
||||
// Arrange
|
||||
_configuration.GetConnectionString("JDEStage").Returns((string?)null);
|
||||
var factory = new DbConnectionFactory(_configuration, _logger);
|
||||
_secureStore.Get("JDEStage").Returns((string?)null);
|
||||
var factory = new DbConnectionFactory(_secureStore, _logger);
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Should.ThrowAsync<ConnectionException>(
|
||||
@@ -174,8 +175,8 @@ public class DbConnectionFactoryTests
|
||||
public async Task CreateCmsConnectionAsync_MissingConnectionString_ThrowsConnectionException()
|
||||
{
|
||||
// Arrange
|
||||
_configuration.GetConnectionString("CMS").Returns((string?)null);
|
||||
var factory = new DbConnectionFactory(_configuration, _logger);
|
||||
_secureStore.Get("CMS").Returns((string?)null);
|
||||
var factory = new DbConnectionFactory(_secureStore, _logger);
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Should.ThrowAsync<ConnectionException>(
|
||||
@@ -189,8 +190,8 @@ public class DbConnectionFactoryTests
|
||||
public async Task CreateCmsConnectionAsync_InvalidConnectionString_ThrowsConnectionException()
|
||||
{
|
||||
// Arrange
|
||||
_configuration.GetConnectionString("CMS").Returns("Invalid oracle connection");
|
||||
var factory = new DbConnectionFactory(_configuration, _logger);
|
||||
_secureStore.Get("CMS").Returns("Invalid oracle connection");
|
||||
var factory = new DbConnectionFactory(_secureStore, _logger);
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Should.ThrowAsync<ConnectionException>(
|
||||
@@ -208,8 +209,8 @@ public class DbConnectionFactoryTests
|
||||
public async Task CreateLotFinderConnectionAsync_InvalidConnection_LogsError()
|
||||
{
|
||||
// Arrange
|
||||
_configuration.GetConnectionString("LotFinderDB").Returns("Invalid connection string");
|
||||
var factory = new DbConnectionFactory(_configuration, _logger);
|
||||
_secureStore.Get("LotFinder").Returns("Invalid connection string");
|
||||
var factory = new DbConnectionFactory(_secureStore, _logger);
|
||||
|
||||
// Act
|
||||
try
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Dapper;
|
||||
using JdeScoping.Core.Interfaces;
|
||||
using JdeScoping.DataAccess;
|
||||
using JdeScoping.DataAccess.Interfaces;
|
||||
using JdeScoping.DataSync.Dev.Configuration;
|
||||
@@ -33,7 +34,17 @@ public class DevEtlPipelineFactoryTests
|
||||
.AddEnvironmentVariables()
|
||||
.Build();
|
||||
|
||||
_connectionFactory = new DbConnectionFactory(config, NullLogger<DbConnectionFactory>.Instance);
|
||||
// Create a mock SecureStore that returns connection strings from configuration
|
||||
var secureStore = Substitute.For<ISecureStoreService>();
|
||||
|
||||
// Setup the mock to return connection strings from config
|
||||
secureStore.Get("LotFinder").Returns(config.GetConnectionString("LotFinder"));
|
||||
secureStore.Get("JDE").Returns(config.GetConnectionString("JDE"));
|
||||
secureStore.Get("JDEStage").Returns(config.GetConnectionString("JDEStage"));
|
||||
secureStore.Get("CMS").Returns(config.GetConnectionString("CMS"));
|
||||
secureStore.Get("GIW").Returns(config.GetConnectionString("GIW"));
|
||||
|
||||
_connectionFactory = new DbConnectionFactory(secureStore, NullLogger<DbConnectionFactory>.Instance);
|
||||
_logger = NullLogger<EtlPipeline>.Instance;
|
||||
_cacheDirectory = config["DevEtl:CacheDirectory"]
|
||||
?? Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "..", "CACHED_DB_FILES");
|
||||
|
||||
+61
-104
@@ -6,6 +6,10 @@ using Shouldly;
|
||||
|
||||
namespace JdeScoping.Infrastructure.Tests.Validation;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for ConnectionStringValidator which validates connection strings stored in SecureStore.
|
||||
/// Connection strings are referenced by name in configuration and retrieved from SecureStore.
|
||||
/// </summary>
|
||||
public class ConnectionStringValidatorTests : IDisposable
|
||||
{
|
||||
private readonly InMemorySecureStore _secureStore;
|
||||
@@ -55,7 +59,7 @@ public class ConnectionStringValidatorTests : IDisposable
|
||||
[Fact]
|
||||
public void Validate_NoConnectionStrings_ReturnsValid()
|
||||
{
|
||||
// Arrange
|
||||
// Arrange - no connection strings in config means nothing to validate
|
||||
var validator = CreateValidator(new Dictionary<string, string?>());
|
||||
|
||||
// Act
|
||||
@@ -68,12 +72,13 @@ public class ConnectionStringValidatorTests : IDisposable
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_ConnectionStringWithNoPlaceholders_ValidatesFormat()
|
||||
public void Validate_ConnectionStringInSecureStore_ValidatesFormat()
|
||||
{
|
||||
// Arrange
|
||||
// Arrange - connection string name in config, value in SecureStore
|
||||
_secureStore.Set("SqlServer", "Server=localhost;Database=TestDb;Trusted_Connection=true;");
|
||||
var validator = CreateValidator(new Dictionary<string, string?>
|
||||
{
|
||||
["ConnectionStrings:SqlServer"] = "Server=localhost;Database=TestDb;Trusted_Connection=true;"
|
||||
["ConnectionStrings:SqlServer"] = "SqlServer" // Name only, value in SecureStore
|
||||
});
|
||||
|
||||
// Act
|
||||
@@ -85,12 +90,31 @@ public class ConnectionStringValidatorTests : IDisposable
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_EmptyConnectionString_ReturnsError()
|
||||
public void Validate_ConnectionStringNotInSecureStore_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
// Arrange - connection string name in config but not in SecureStore
|
||||
var validator = CreateValidator(new Dictionary<string, string?>
|
||||
{
|
||||
["ConnectionStrings:SqlServer"] = ""
|
||||
["ConnectionStrings:SqlServer"] = "SqlServer"
|
||||
});
|
||||
// Note: Not adding SqlServer to SecureStore
|
||||
|
||||
// Act
|
||||
var result = validator.Validate();
|
||||
|
||||
// Assert
|
||||
result.IsValid.ShouldBeFalse();
|
||||
result.Errors.ShouldContain(e => e.Contains("'SqlServer'") && e.Contains("not found in SecureStore"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_EmptyConnectionStringInSecureStore_ReturnsError()
|
||||
{
|
||||
// Arrange - connection string exists in SecureStore but is empty
|
||||
_secureStore.Set("SqlServer", "");
|
||||
var validator = CreateValidator(new Dictionary<string, string?>
|
||||
{
|
||||
["ConnectionStrings:SqlServer"] = "SqlServer"
|
||||
});
|
||||
|
||||
// Act
|
||||
@@ -98,35 +122,17 @@ public class ConnectionStringValidatorTests : IDisposable
|
||||
|
||||
// Assert
|
||||
result.IsValid.ShouldBeFalse();
|
||||
result.Errors.ShouldContain("Connection string 'SqlServer' is empty");
|
||||
result.Errors.ShouldContain(e => e.Contains("'SqlServer'") && e.Contains("empty in SecureStore"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_PlaceholderKeyNotInSecureStore_ReturnsError()
|
||||
public void Validate_WhitespaceOnlyConnectionStringInSecureStore_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
// Arrange - connection string exists in SecureStore but is whitespace only
|
||||
_secureStore.Set("SqlServer", " ");
|
||||
var validator = CreateValidator(new Dictionary<string, string?>
|
||||
{
|
||||
["ConnectionStrings:SqlServer"] = "Server=localhost;Database=TestDb;Password=${DB_PASSWORD};"
|
||||
});
|
||||
// Note: Not adding DB_PASSWORD to SecureStore
|
||||
|
||||
// Act
|
||||
var result = validator.Validate();
|
||||
|
||||
// Assert
|
||||
result.IsValid.ShouldBeFalse();
|
||||
result.Errors.ShouldContain(e => e.Contains("'${DB_PASSWORD}'") && e.Contains("not found in SecureStore"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_PlaceholderKeyHasEmptyValue_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
_secureStore.Set("DB_PASSWORD", "");
|
||||
var validator = CreateValidator(new Dictionary<string, string?>
|
||||
{
|
||||
["ConnectionStrings:SqlServer"] = "Server=localhost;Database=TestDb;Password=${DB_PASSWORD};"
|
||||
["ConnectionStrings:SqlServer"] = "SqlServer"
|
||||
});
|
||||
|
||||
// Act
|
||||
@@ -134,17 +140,17 @@ public class ConnectionStringValidatorTests : IDisposable
|
||||
|
||||
// Assert
|
||||
result.IsValid.ShouldBeFalse();
|
||||
result.Errors.ShouldContain(e => e.Contains("'${DB_PASSWORD}'") && e.Contains("empty value"));
|
||||
result.Errors.ShouldContain(e => e.Contains("'SqlServer'") && e.Contains("empty in SecureStore"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_AllPlaceholdersResolved_ValidatesFormat()
|
||||
public void Validate_ValidSqlServerFormat_ValidatesSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
_secureStore.Set("DB_PASSWORD", "secretpassword");
|
||||
// Arrange - SQL Server connection string format
|
||||
_secureStore.Set("SqlServer", "Server=localhost;Database=TestDb;Trusted_Connection=true;");
|
||||
var validator = CreateValidator(new Dictionary<string, string?>
|
||||
{
|
||||
["ConnectionStrings:SqlServer"] = "Server=localhost;Database=TestDb;Password=${DB_PASSWORD};"
|
||||
["ConnectionStrings:SqlServer"] = "SqlServer"
|
||||
});
|
||||
|
||||
// Act
|
||||
@@ -155,53 +161,14 @@ public class ConnectionStringValidatorTests : IDisposable
|
||||
result.Errors.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_MultiplePlaceholders_ResolvesAll()
|
||||
{
|
||||
// Arrange
|
||||
_secureStore.Set("DB_SERVER", "prodserver.local");
|
||||
_secureStore.Set("DB_USER", "app_user");
|
||||
_secureStore.Set("DB_PASSWORD", "secretpassword");
|
||||
var validator = CreateValidator(new Dictionary<string, string?>
|
||||
{
|
||||
["ConnectionStrings:SqlServer"] = "Server=${DB_SERVER};Database=TestDb;User Id=${DB_USER};Password=${DB_PASSWORD};"
|
||||
});
|
||||
|
||||
// Act
|
||||
var result = validator.Validate();
|
||||
|
||||
// Assert
|
||||
result.IsValid.ShouldBeTrue();
|
||||
result.Errors.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_MultiplePlaceholders_ReportsAllMissing()
|
||||
{
|
||||
// Arrange - only set one of three required placeholders
|
||||
_secureStore.Set("DB_SERVER", "prodserver.local");
|
||||
var validator = CreateValidator(new Dictionary<string, string?>
|
||||
{
|
||||
["ConnectionStrings:SqlServer"] = "Server=${DB_SERVER};Database=TestDb;User Id=${DB_USER};Password=${DB_PASSWORD};"
|
||||
});
|
||||
|
||||
// Act
|
||||
var result = validator.Validate();
|
||||
|
||||
// Assert
|
||||
result.IsValid.ShouldBeFalse();
|
||||
result.Errors.Count.ShouldBe(2); // DB_USER and DB_PASSWORD missing
|
||||
result.Errors.ShouldContain(e => e.Contains("'${DB_USER}'"));
|
||||
result.Errors.ShouldContain(e => e.Contains("'${DB_PASSWORD}'"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_MissingServerOrDataSource_ReturnsError()
|
||||
{
|
||||
// Arrange - connection string with no Server= or Data Source=
|
||||
_secureStore.Set("Invalid", "Database=TestDb;Trusted_Connection=true;");
|
||||
var validator = CreateValidator(new Dictionary<string, string?>
|
||||
{
|
||||
["ConnectionStrings:Invalid"] = "Database=TestDb;Trusted_Connection=true;"
|
||||
["ConnectionStrings:Invalid"] = "Invalid"
|
||||
});
|
||||
|
||||
// Act
|
||||
@@ -216,9 +183,10 @@ public class ConnectionStringValidatorTests : IDisposable
|
||||
public void Validate_DataSourceFormat_ValidatesSuccessfully()
|
||||
{
|
||||
// Arrange - Oracle-style connection string
|
||||
_secureStore.Set("Oracle", "Data Source=//localhost:1521/ORCL;User Id=test;Password=test;");
|
||||
var validator = CreateValidator(new Dictionary<string, string?>
|
||||
{
|
||||
["ConnectionStrings:Oracle"] = "Data Source=//localhost:1521/ORCL;User Id=test;Password=test;"
|
||||
["ConnectionStrings:Oracle"] = "Oracle"
|
||||
});
|
||||
|
||||
// Act
|
||||
@@ -232,9 +200,10 @@ public class ConnectionStringValidatorTests : IDisposable
|
||||
public void Validate_HostFormat_ValidatesSuccessfully()
|
||||
{
|
||||
// Arrange - PostgreSQL-style connection string
|
||||
_secureStore.Set("Postgres", "Host=localhost;Port=5432;Database=testdb;Username=test;Password=test;");
|
||||
var validator = CreateValidator(new Dictionary<string, string?>
|
||||
{
|
||||
["ConnectionStrings:Postgres"] = "Host=localhost;Port=5432;Database=testdb;Username=test;Password=test;"
|
||||
["ConnectionStrings:Postgres"] = "Postgres"
|
||||
});
|
||||
|
||||
// Act
|
||||
@@ -247,13 +216,16 @@ public class ConnectionStringValidatorTests : IDisposable
|
||||
[Fact]
|
||||
public void Validate_MultipleConnectionStrings_ValidatesAll()
|
||||
{
|
||||
// Arrange
|
||||
_secureStore.Set("SQL_PASSWORD", "sqlpass");
|
||||
// Arrange - multiple connection strings, one missing from SecureStore
|
||||
_secureStore.Set("SqlServer", "Server=localhost;Database=TestDb;Trusted_Connection=true;");
|
||||
_secureStore.Set("Oracle", "Data Source=//localhost:1521/ORCL;User Id=test;Password=test;");
|
||||
// Note: Not adding "Missing" to SecureStore
|
||||
|
||||
var validator = CreateValidator(new Dictionary<string, string?>
|
||||
{
|
||||
["ConnectionStrings:SqlServer"] = "Server=localhost;Database=TestDb;Password=${SQL_PASSWORD};",
|
||||
["ConnectionStrings:Oracle"] = "Data Source=//localhost:1521/ORCL;User Id=test;Password=test;",
|
||||
["ConnectionStrings:InvalidEmpty"] = ""
|
||||
["ConnectionStrings:SqlServer"] = "SqlServer",
|
||||
["ConnectionStrings:Oracle"] = "Oracle",
|
||||
["ConnectionStrings:Missing"] = "Missing"
|
||||
});
|
||||
|
||||
// Act
|
||||
@@ -261,17 +233,18 @@ public class ConnectionStringValidatorTests : IDisposable
|
||||
|
||||
// Assert
|
||||
result.IsValid.ShouldBeFalse();
|
||||
result.Errors.Count.ShouldBe(1); // Only the empty one should fail
|
||||
result.Errors.ShouldContain("Connection string 'InvalidEmpty' is empty");
|
||||
result.Errors.Count.ShouldBe(1); // Only the missing one should fail
|
||||
result.Errors.ShouldContain(e => e.Contains("'Missing'") && e.Contains("not found in SecureStore"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_UnknownConnectionFormat_AddsWarning()
|
||||
{
|
||||
// Arrange - connection string with Server= but no Database=
|
||||
_secureStore.Set("Custom", "Server=localhost;CustomProperty=value;");
|
||||
var validator = CreateValidator(new Dictionary<string, string?>
|
||||
{
|
||||
["ConnectionStrings:Custom"] = "Server=localhost;CustomProperty=value;"
|
||||
["ConnectionStrings:Custom"] = "Custom"
|
||||
});
|
||||
|
||||
// Act
|
||||
@@ -282,30 +255,14 @@ public class ConnectionStringValidatorTests : IDisposable
|
||||
result.Warnings.ShouldContain(w => w.Contains("'Custom'") && w.Contains("unknown format"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_WhitespaceOnlyConnectionString_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
var validator = CreateValidator(new Dictionary<string, string?>
|
||||
{
|
||||
["ConnectionStrings:SqlServer"] = " "
|
||||
});
|
||||
|
||||
// Act
|
||||
var result = validator.Validate();
|
||||
|
||||
// Assert
|
||||
result.IsValid.ShouldBeFalse();
|
||||
result.Errors.ShouldContain("Connection string 'SqlServer' is empty");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_CaseInsensitiveServerCheck_ValidatesSuccessfully()
|
||||
{
|
||||
// Arrange - mixed case Server
|
||||
_secureStore.Set("SqlServer", "server=localhost;Database=TestDb;");
|
||||
var validator = CreateValidator(new Dictionary<string, string?>
|
||||
{
|
||||
["ConnectionStrings:SqlServer"] = "server=localhost;Database=TestDb;"
|
||||
["ConnectionStrings:SqlServer"] = "SqlServer"
|
||||
});
|
||||
|
||||
// Act
|
||||
|
||||
Reference in New Issue
Block a user