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:
Joseph Doherty
2026-01-23 14:44:04 -05:00
parent ba54a87be5
commit bfc1c8064a
16 changed files with 462 additions and 279 deletions
@@ -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