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:
+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