using GB5Shared.DTO.Framework.Login; using GB5Shared.EventLogPublish; using Microsoft.Extensions.Logging.Abstractions; using Moq; using SwBLL.ChangeRequest; using SwBLL.ClientDatabase; using SwBLL.DbServer; using SwBLL.DdlScript; using SwBLL.Provisioning; using SwBLL.UserResourceRole; using SwDAL.CustomCode.ChangeRequest; using SwDAL.DTO.ChangeRequest; using SwDAL.DTO.ClientDatabase; using SwDAL.DTO.DbServer; using SwDAL.DTO.DdlScript; using SwDAL.Enums; using Xunit; namespace SwTests; /// /// Covers ChangeRequestBLL.Execute()'s new ClientProvisioning pre-step — the branch that creates /// the physical database (via IClientDatabaseProvisioner) before falling through to the existing, /// unmodified DDL-execution path. Every case here mocks IClientDatabaseProvisioner directly, so /// none of these tests touch a real database or Vault — they only prove Execute() picks the right /// provisioner method for the CR's ProvisioningMode and validates the required fields first. /// public class ChangeRequestBLLProvisioningTests { private static (ChangeRequestBLL Svc, Mock Dal, Mock ClientDatabaseBLL, Mock Provisioner) BuildService() { var dal = new Mock(); var clientDatabaseBLL = new Mock(); var dbServerBLL = new Mock(); var targetDbExecutor = new Mock(); var ddlScriptBLL = new Mock(); var provisioner = new Mock(); dal.Setup(d => d.GetLinkedApprovedDdlScripts(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(Enumerable.Empty()); dal.Setup(d => d.UpdateStatus(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(Task.CompletedTask); dal.Setup(d => d.AppendTimeline(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(Task.CompletedTask); clientDatabaseBLL.Setup(c => c.GetById(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(new ClientDatabaseDTO { ClientDbId = 5, ClientDbCode = "acme01", DatabaseName = "acme01", DbServerId = 1, DatabaseRole = 0 }); clientDatabaseBLL.Setup(c => c.UpdateStatusAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync("updated"); dbServerBLL.Setup(d => d.GetByIdWithCredentials(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(new DbServerDTO { DbServerId = 1, DbType = 0, HostName = "sql01" }); targetDbExecutor.Setup(t => t.BuildConnectionString(It.IsAny(), It.IsAny())) .Returns("conn-str"); provisioner.Setup(p => p.CreateFromSnapshotAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(Task.CompletedTask); provisioner.Setup(p => p.CreateFromScriptsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(Task.CompletedTask); // EventLogPublish.PublishEventLogAsync catches and logs every exception internally // (never throws) — a null DaprClient is a safe test double here, it just makes the // publish attempt fail silently (logged as a warning), exactly like a real Dapr sidecar // being unreachable would in production. None of these tests assert on the publish itself. var eventLogPublish = new EventLogPublish(null!, NullLogger.Instance); var svc = new ChangeRequestBLL( dal.Object, clientDatabaseBLL.Object, dbServerBLL.Object, targetDbExecutor.Object, ddlScriptBLL.Object, provisioner.Object, new Mock().Object, eventLogPublish, NullLogger.Instance); return (svc, dal, clientDatabaseBLL, provisioner); } private static ChangeRequestDTO ApprovedProvisioningCr( ProvisioningMode? mode = null, string? templateBackupRef = null, int? baselinePackageId = null) => new() { ChangeRequestId = 100, ClientDatabaseId = 5, CrStatus = ChangeRequestStatus.Approved, Category = ChangeRequestCategory.ClientProvisioning, QueryType = ChangeRequestQueryType.Other, ProvisioningMode = mode, TemplateBackupRef = templateBackupRef, BaselineUpgradePackageId = baselinePackageId }; private static LoginDTO TestLogin() => new() { ClientId = 42, UserId = -1 }; [Fact] public async Task Test_Execute_ClientProvisioning_MissingProvisioningMode_Throws() { var (svc, dal, _, provisioner) = BuildService(); var cr = ApprovedProvisioningCr(mode: null); dal.Setup(d => d.GetById(cr.ChangeRequestId, It.IsAny(), It.IsAny())).ReturnsAsync(cr); await Assert.ThrowsAsync(() => svc.Execute(cr.ChangeRequestId, TestLogin(), CancellationToken.None)); provisioner.Verify(p => p.CreateFromSnapshotAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); provisioner.Verify(p => p.CreateFromScriptsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } [Fact] public async Task Test_Execute_ClientProvisioning_FromSnapshot_MissingTemplateBackupRef_Throws() { var (svc, dal, _, provisioner) = BuildService(); var cr = ApprovedProvisioningCr(mode: ProvisioningMode.FromSnapshot, templateBackupRef: null); dal.Setup(d => d.GetById(cr.ChangeRequestId, It.IsAny(), It.IsAny())).ReturnsAsync(cr); await Assert.ThrowsAsync(() => svc.Execute(cr.ChangeRequestId, TestLogin(), CancellationToken.None)); provisioner.Verify(p => p.CreateFromSnapshotAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } [Fact] public async Task Test_Execute_ClientProvisioning_FromSnapshot_CallsProvisionerAndActivatesClientDb() { var (svc, dal, clientDatabaseBLL, provisioner) = BuildService(); var cr = ApprovedProvisioningCr(mode: ProvisioningMode.FromSnapshot, templateBackupRef: "acme-template.bak"); dal.Setup(d => d.GetById(cr.ChangeRequestId, It.IsAny(), It.IsAny())).ReturnsAsync(cr); await svc.Execute(cr.ChangeRequestId, TestLogin(), CancellationToken.None); provisioner.Verify(p => p.CreateFromSnapshotAsync( It.Is(c => c.ClientDbId == 5), It.IsAny(), "acme-template.bak", It.IsAny(), It.IsAny()), Times.Once); provisioner.Verify(p => p.CreateFromScriptsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); clientDatabaseBLL.Verify(c => c.UpdateStatusAsync(5, (byte)ClientDbStatus.Active, It.IsAny(), It.IsAny()), Times.Once); dal.Verify(d => d.UpdateStatus(cr.ChangeRequestId, (byte)ChangeRequestStatus.Executed, (byte)ChangeRequestStatus.Approved, It.IsAny(), null, It.IsAny(), It.IsAny()), Times.Once); } [Fact] public async Task Test_Execute_ClientProvisioning_FromScripts_MissingBaselinePackageId_Throws() { var (svc, dal, _, provisioner) = BuildService(); var cr = ApprovedProvisioningCr(mode: ProvisioningMode.FromScripts, baselinePackageId: null); dal.Setup(d => d.GetById(cr.ChangeRequestId, It.IsAny(), It.IsAny())).ReturnsAsync(cr); await Assert.ThrowsAsync(() => svc.Execute(cr.ChangeRequestId, TestLogin(), CancellationToken.None)); provisioner.Verify(p => p.CreateFromScriptsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } [Fact] public async Task Test_Execute_ClientProvisioning_FromScripts_CallsProvisionerWithBaselinePackage() { var (svc, dal, clientDatabaseBLL, provisioner) = BuildService(); var cr = ApprovedProvisioningCr(mode: ProvisioningMode.FromScripts, baselinePackageId: 77); dal.Setup(d => d.GetById(cr.ChangeRequestId, It.IsAny(), It.IsAny())).ReturnsAsync(cr); await svc.Execute(cr.ChangeRequestId, TestLogin(), CancellationToken.None); provisioner.Verify(p => p.CreateFromScriptsAsync( It.Is(c => c.ClientDbId == 5), It.IsAny(), 77, It.IsAny(), It.IsAny()), Times.Once); clientDatabaseBLL.Verify(c => c.UpdateStatusAsync(5, (byte)ClientDbStatus.Active, It.IsAny(), It.IsAny()), Times.Once); } [Fact] public async Task Test_Execute_NonProvisioningCategory_NeverCallsProvisioner() { var (svc, dal, _, provisioner) = BuildService(); var cr = new ChangeRequestDTO { ChangeRequestId = 200, ClientDatabaseId = 5, CrStatus = ChangeRequestStatus.Approved, Category = ChangeRequestCategory.SchemaChange, QueryType = ChangeRequestQueryType.DDL }; dal.Setup(d => d.GetById(cr.ChangeRequestId, It.IsAny(), It.IsAny())).ReturnsAsync(cr); dal.Setup(d => d.GetLinkedApprovedDdlScripts(cr.ChangeRequestId, It.IsAny(), It.IsAny())) .ReturnsAsync(new List { new() { DdlScriptId = 1, SqlScript = "SELECT 1" } }); await svc.Execute(cr.ChangeRequestId, TestLogin(), CancellationToken.None); provisioner.Verify(p => p.CreateFromSnapshotAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); provisioner.Verify(p => p.CreateFromScriptsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } }