6642c83cdb
Enable ConfigManager to validate runtime configuration (SecureStore secrets, connection strings, LDAP) using the same validators as the Host application. Adds AddInfrastructureValidators() extension for shared validator registration.
318 lines
9.7 KiB
C#
318 lines
9.7 KiB
C#
using JdeScoping.Infrastructure.Tests.Helpers;
|
|
using JdeScoping.Infrastructure.Validation;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Shouldly;
|
|
|
|
namespace JdeScoping.Infrastructure.Tests.Validation;
|
|
|
|
public class ConnectionStringValidatorTests : IDisposable
|
|
{
|
|
private readonly InMemorySecureStore _secureStore;
|
|
|
|
public ConnectionStringValidatorTests()
|
|
{
|
|
_secureStore = new InMemorySecureStore();
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_secureStore.Dispose();
|
|
}
|
|
|
|
private ConnectionStringValidator CreateValidator(Dictionary<string, string?> configValues)
|
|
{
|
|
var configuration = new ConfigurationBuilder()
|
|
.AddInMemoryCollection(configValues)
|
|
.Build();
|
|
|
|
return new ConnectionStringValidator(
|
|
configuration,
|
|
_secureStore,
|
|
NullLogger<ConnectionStringValidator>.Instance);
|
|
}
|
|
|
|
[Fact]
|
|
public void Order_Returns150()
|
|
{
|
|
// Arrange
|
|
var validator = CreateValidator(new Dictionary<string, string?>());
|
|
|
|
// Assert
|
|
validator.Order.ShouldBe(150);
|
|
}
|
|
|
|
[Fact]
|
|
public void Name_ReturnsConnectionStrings()
|
|
{
|
|
// Arrange
|
|
var validator = CreateValidator(new Dictionary<string, string?>());
|
|
|
|
// Assert
|
|
validator.Name.ShouldBe("ConnectionStrings");
|
|
}
|
|
|
|
[Fact]
|
|
public void Validate_NoConnectionStrings_ReturnsValid()
|
|
{
|
|
// Arrange
|
|
var validator = CreateValidator(new Dictionary<string, string?>());
|
|
|
|
// Act
|
|
var result = validator.Validate();
|
|
|
|
// Assert
|
|
result.IsValid.ShouldBeTrue();
|
|
result.Errors.ShouldBeEmpty();
|
|
result.Warnings.ShouldBeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public void Validate_ConnectionStringWithNoPlaceholders_ValidatesFormat()
|
|
{
|
|
// Arrange
|
|
var validator = CreateValidator(new Dictionary<string, string?>
|
|
{
|
|
["ConnectionStrings:SqlServer"] = "Server=localhost;Database=TestDb;Trusted_Connection=true;"
|
|
});
|
|
|
|
// Act
|
|
var result = validator.Validate();
|
|
|
|
// Assert
|
|
result.IsValid.ShouldBeTrue();
|
|
result.Errors.ShouldBeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public void Validate_EmptyConnectionString_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_PlaceholderKeyNotInSecureStore_ReturnsError()
|
|
{
|
|
// Arrange
|
|
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};"
|
|
});
|
|
|
|
// Act
|
|
var result = validator.Validate();
|
|
|
|
// Assert
|
|
result.IsValid.ShouldBeFalse();
|
|
result.Errors.ShouldContain(e => e.Contains("'${DB_PASSWORD}'") && e.Contains("empty value"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Validate_AllPlaceholdersResolved_ValidatesFormat()
|
|
{
|
|
// Arrange
|
|
_secureStore.Set("DB_PASSWORD", "secretpassword");
|
|
var validator = CreateValidator(new Dictionary<string, string?>
|
|
{
|
|
["ConnectionStrings:SqlServer"] = "Server=localhost;Database=TestDb;Password=${DB_PASSWORD};"
|
|
});
|
|
|
|
// Act
|
|
var result = validator.Validate();
|
|
|
|
// Assert
|
|
result.IsValid.ShouldBeTrue();
|
|
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=
|
|
var validator = CreateValidator(new Dictionary<string, string?>
|
|
{
|
|
["ConnectionStrings:Invalid"] = "Database=TestDb;Trusted_Connection=true;"
|
|
});
|
|
|
|
// Act
|
|
var result = validator.Validate();
|
|
|
|
// Assert
|
|
result.IsValid.ShouldBeFalse();
|
|
result.Errors.ShouldContain(e => e.Contains("'Invalid'") && e.Contains("missing required"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Validate_DataSourceFormat_ValidatesSuccessfully()
|
|
{
|
|
// Arrange - Oracle-style connection string
|
|
var validator = CreateValidator(new Dictionary<string, string?>
|
|
{
|
|
["ConnectionStrings:Oracle"] = "Data Source=//localhost:1521/ORCL;User Id=test;Password=test;"
|
|
});
|
|
|
|
// Act
|
|
var result = validator.Validate();
|
|
|
|
// Assert
|
|
result.IsValid.ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void Validate_HostFormat_ValidatesSuccessfully()
|
|
{
|
|
// Arrange - PostgreSQL-style connection string
|
|
var validator = CreateValidator(new Dictionary<string, string?>
|
|
{
|
|
["ConnectionStrings:Postgres"] = "Host=localhost;Port=5432;Database=testdb;Username=test;Password=test;"
|
|
});
|
|
|
|
// Act
|
|
var result = validator.Validate();
|
|
|
|
// Assert
|
|
result.IsValid.ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void Validate_MultipleConnectionStrings_ValidatesAll()
|
|
{
|
|
// Arrange
|
|
_secureStore.Set("SQL_PASSWORD", "sqlpass");
|
|
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"] = ""
|
|
});
|
|
|
|
// Act
|
|
var result = validator.Validate();
|
|
|
|
// Assert
|
|
result.IsValid.ShouldBeFalse();
|
|
result.Errors.Count.ShouldBe(1); // Only the empty one should fail
|
|
result.Errors.ShouldContain("Connection string 'InvalidEmpty' is empty");
|
|
}
|
|
|
|
[Fact]
|
|
public void Validate_UnknownConnectionFormat_AddsWarning()
|
|
{
|
|
// Arrange - connection string with Server= but no Database=
|
|
var validator = CreateValidator(new Dictionary<string, string?>
|
|
{
|
|
["ConnectionStrings:Custom"] = "Server=localhost;CustomProperty=value;"
|
|
});
|
|
|
|
// Act
|
|
var result = validator.Validate();
|
|
|
|
// Assert
|
|
result.IsValid.ShouldBeTrue(); // Still valid, just a warning
|
|
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
|
|
var validator = CreateValidator(new Dictionary<string, string?>
|
|
{
|
|
["ConnectionStrings:SqlServer"] = "server=localhost;Database=TestDb;"
|
|
});
|
|
|
|
// Act
|
|
var result = validator.Validate();
|
|
|
|
// Assert
|
|
result.IsValid.ShouldBeTrue();
|
|
}
|
|
}
|