using GB5Shared.EncryptionHelper; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging.Abstractions; using Moq; using SwBLL.Provisioning; using SwBLL.Vault; using SwDAL.DTO.ClientDatabase; using SwDAL.DTO.DbServer; using SwDAL.Enums; using Xunit; namespace SwTests; /// /// Covers TargetDbExecutor.BuildConnectionStringForRoleAsync — the least-privilege contained-user /// (dba/app/readonly) connection-string resolution — and the DbServer-admin-credential /// decryption fix in BuildConnectionString's no-override fallback path. /// public class TargetDbExecutorTests { private const string EncryptionKey = "unit-test-encryption-key"; private static IConfiguration BuildConfiguration() => new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary { ["Encryption:DbPasswordKey"] = EncryptionKey }) .Build(); private static DbServerDTO AdminServer() => new() { DbServerId = 1, HostName = "sql01", Port = 1433, DbUsername = "sa", DbPassword = PasswordEncryption.Encrypt("admin-secret", EncryptionKey) }; private static ClientDatabaseDTO ClientDb( string? dbaPath = null, string? appPath = null, string? readOnlyPath = null) => new() { ClientDbId = 5, ClientDbCode = "acme01", DatabaseName = "acme01", DbServerId = 1, DbaVaultPath = dbaPath, AppVaultPath = appPath, ReadOnlyVaultPath = readOnlyPath }; [Theory] [InlineData(ClientDbLoginRole.Dba, "dba")] [InlineData(ClientDbLoginRole.App, "app")] [InlineData(ClientDbLoginRole.ReadOnly, "readonly")] public async Task BuildConnectionStringForRoleAsync_VaultPathPresent_UsesContainedUserCredential( ClientDbLoginRole role, string expectedSuffix) { var vault = new Mock(); vault.Setup(v => v.GetSecretAsync(It.IsAny(), It.IsAny())) .ReturnsAsync("contained-secret"); var executor = new TargetDbExecutor(BuildConfiguration(), vault.Object, NullLogger.Instance); var server = AdminServer(); var clientDb = role switch { ClientDbLoginRole.Dba => ClientDb(dbaPath: "sqlworkbench/clientdb/5/dba-password"), ClientDbLoginRole.App => ClientDb(appPath: "sqlworkbench/clientdb/5/app-password"), ClientDbLoginRole.ReadOnly => ClientDb(readOnlyPath: "sqlworkbench/clientdb/5/readonly-password"), _ => throw new ArgumentOutOfRangeException(nameof(role)) }; string connStr = await executor.BuildConnectionStringForRoleAsync(server, clientDb, role, CancellationToken.None); var csb = new SqlConnectionStringBuilder(connStr); Assert.Equal($"acme01_{expectedSuffix}", csb.UserID); Assert.Equal("contained-secret", csb.Password); Assert.Equal("acme01", csb.InitialCatalog); } [Fact] public async Task BuildConnectionStringForRoleAsync_NoVaultPath_FallsBackToDecryptedAdminCredential() { var vault = new Mock(); var executor = new TargetDbExecutor(BuildConfiguration(), vault.Object, NullLogger.Instance); var server = AdminServer(); var clientDb = ClientDb(); // no vault paths at all — never provisioned through SqlWorkbench string connStr = await executor.BuildConnectionStringForRoleAsync( server, clientDb, ClientDbLoginRole.ReadOnly, CancellationToken.None); var csb = new SqlConnectionStringBuilder(connStr); Assert.Equal("sa", csb.UserID); Assert.Equal("admin-secret", csb.Password); vault.Verify(v => v.GetSecretAsync(It.IsAny(), It.IsAny()), Times.Never); } [Fact] public void BuildConnectionString_NoOverride_DecryptsServerPassword() { var vault = new Mock(); var executor = new TargetDbExecutor(BuildConfiguration(), vault.Object, NullLogger.Instance); var server = AdminServer(); string connStr = executor.BuildConnectionString(server, "master"); var csb = new SqlConnectionStringBuilder(connStr); Assert.Equal("sa", csb.UserID); Assert.Equal("admin-secret", csb.Password); } [Fact] public void BuildConnectionString_ExplicitOverride_SkipsDecryption() { var vault = new Mock(); var executor = new TargetDbExecutor(BuildConfiguration(), vault.Object, NullLogger.Instance); var server = AdminServer(); // An explicit override is already plaintext (e.g. a Vault-sourced contained-user // password) — it must be used as-is, never run through Decrypt. string connStr = executor.BuildConnectionString(server, "acme01", "acme01_app", "plain-password"); var csb = new SqlConnectionStringBuilder(connStr); Assert.Equal("acme01_app", csb.UserID); Assert.Equal("plain-password", csb.Password); } }