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);
}
}