using GB5Shared.DTO.Framework.Login; using Moq; using SwBLL.ClientDatabase; using SwBLL.Integration; using SwBLL.Provisioning; using SwBLL.RolloutCampaign; using SwBLL.UpgradePackage; using SwDAL.CustomCode.RolloutCampaign; using SwDAL.DTO.ClientDatabase; using SwDAL.DTO.RolloutCampaign; using Xunit; namespace SwTests; /// /// Covers RolloutCampaignBLL's DeliveryMode=Offline handling (Offline Rollout Package, tracker /// §53, Phase 5) — a due target whose ClientDatabase is Offline is never pushed via /// IProvisioningBLL.ProvisionClientDatabase, and a campaign never finalizes to /// Completed/PartiallyFailed while one still sits AwaitingManualApply. AutoNumber is passed /// null! for tests that never reach the campaign-creation/auto-numbered-insert path (only /// ProcessDueTargetsAsync/MarkManuallyAppliedAsync are exercised here). /// public class RolloutCampaignBLLDeliveryModeTests { private static readonly LoginDTO Login = new() { ClientId = 1, UserId = -1 }; private static (RolloutCampaignBLL Svc, Mock Dal, Mock Provisioning, Mock ClientDb) BuildService() { var dal = new Mock(); var provisioning = new Mock(); var pkg = new Mock(); var clientDb = new Mock(); var ent = new Mock(); var svc = new RolloutCampaignBLL(dal.Object, provisioning.Object, pkg.Object, clientDb.Object, ent.Object, autoNumber: null!); return (svc, dal, provisioning, clientDb); } private static RolloutTargetDTO DueTarget(int targetId = 1, int campaignId = 1, int clientDbId = 100) => new() { RolloutTargetId = targetId, RolloutCampaignId = campaignId, ClientDbId = clientDbId, TargetStatus = 0, }; private static RolloutCampaignDTO Campaign(int campaignId = 1, int packageId = 5) => new() { RolloutCampaignId = campaignId, PackageId = packageId, }; [Fact] public async Task ProcessDueTargetsAsync_OfflineDeliveryMode_NeverCallsProvisionClientDatabase() { var (svc, dal, provisioning, clientDb) = BuildService(); var target = DueTarget(); dal.Setup(d => d.GetDueTargets(It.IsAny(), Login, default)).ReturnsAsync(new[] { target }); dal.Setup(d => d.GetById(1, Login, default)).ReturnsAsync(Campaign()); clientDb.Setup(c => c.GetById(100, Login, default)).ReturnsAsync(new ClientDatabaseDTO { ClientDbId = 100, DeliveryMode = 1 }); dal.Setup(d => d.GetCampaignStatusRollup(1, Login, default)).ReturnsAsync(new RolloutCampaignStatusDTO { RolloutCampaignId = 1, TotalTargets = 1, PendingCount = 0, DeferredCount = 0, AwaitingManualApplyCount = 1, }); int processed = await svc.ProcessDueTargetsAsync(Login, default); Assert.Equal(1, processed); provisioning.Verify(p => p.ProvisionClientDatabase(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); dal.Verify(d => d.UpdateTargetStatus( It.Is(t => t.TargetStatus == 6), Login, default), Times.Once); // AwaitingManualApply } [Fact] public async Task ProcessDueTargetsAsync_OfflineDeliveryMode_DoesNotFinalizeCampaign() { var (svc, dal, _, clientDb) = BuildService(); var target = DueTarget(); dal.Setup(d => d.GetDueTargets(It.IsAny(), Login, default)).ReturnsAsync(new[] { target }); dal.Setup(d => d.GetById(1, Login, default)).ReturnsAsync(Campaign()); clientDb.Setup(c => c.GetById(100, Login, default)).ReturnsAsync(new ClientDatabaseDTO { ClientDbId = 100, DeliveryMode = 1 }); dal.Setup(d => d.GetCampaignStatusRollup(1, Login, default)).ReturnsAsync(new RolloutCampaignStatusDTO { RolloutCampaignId = 1, TotalTargets = 1, PendingCount = 0, DeferredCount = 0, AwaitingManualApplyCount = 1, }); await svc.ProcessDueTargetsAsync(Login, default); // Campaign is set InProgress at the top of the loop; must never advance to // Completed(3)/PartiallyFailed(4) while AwaitingManualApplyCount > 0. dal.Verify(d => d.UpdateCampaignStatus(1, 3, Login, default), Times.Never); dal.Verify(d => d.UpdateCampaignStatus(1, 4, Login, default), Times.Never); } [Fact] public async Task ProcessDueTargetsAsync_PushDeliveryMode_StillCallsProvisionClientDatabase() { var (svc, dal, provisioning, clientDb) = BuildService(); var target = DueTarget(); dal.Setup(d => d.GetDueTargets(It.IsAny(), Login, default)).ReturnsAsync(new[] { target }); dal.Setup(d => d.GetById(1, Login, default)).ReturnsAsync(Campaign()); clientDb.Setup(c => c.GetById(100, Login, default)).ReturnsAsync(new ClientDatabaseDTO { ClientDbId = 100, DeliveryMode = 0 }); dal.Setup(d => d.IsPackageAlreadyApplied(100, 5, Login, default)).ReturnsAsync(false); dal.Setup(d => d.GetCampaignStatusRollup(1, Login, default)).ReturnsAsync(new RolloutCampaignStatusDTO { RolloutCampaignId = 1, TotalTargets = 1, AppliedCount = 1, }); await svc.ProcessDueTargetsAsync(Login, default); provisioning.Verify(p => p.ProvisionClientDatabase(100, 5, Login, default), Times.Once); dal.Verify(d => d.UpdateTargetStatus(It.Is(t => t.TargetStatus == 3), Login, default), Times.Once); } [Fact] public async Task MarkManuallyAppliedAsync_TargetNotFound_Throws() { var (svc, dal, _, _) = BuildService(); dal.Setup(d => d.GetTargetById(1, Login, default)).ReturnsAsync((RolloutTargetDTO?)null); await Assert.ThrowsAsync(() => svc.MarkManuallyAppliedAsync(1, Login, default)); } [Fact] public async Task MarkManuallyAppliedAsync_NotAwaitingManualApply_Throws() { var (svc, dal, _, _) = BuildService(); dal.Setup(d => d.GetTargetById(1, Login, default)).ReturnsAsync(new RolloutTargetDTO { RolloutTargetId = 1, TargetStatus = 0 }); await Assert.ThrowsAsync(() => svc.MarkManuallyAppliedAsync(1, Login, default)); } [Fact] public async Task MarkManuallyAppliedAsync_ValidTarget_MovesToAppliedAndFinalizesCampaign() { var (svc, dal, _, _) = BuildService(); dal.Setup(d => d.GetTargetById(1, Login, default)).ReturnsAsync( new RolloutTargetDTO { RolloutTargetId = 1, RolloutCampaignId = 1, TargetStatus = 6 }); dal.Setup(d => d.GetCampaignStatusRollup(1, Login, default)).ReturnsAsync(new RolloutCampaignStatusDTO { RolloutCampaignId = 1, PendingCount = 0, DeferredCount = 0, AwaitingManualApplyCount = 0, FailedCount = 0, }); await svc.MarkManuallyAppliedAsync(1, Login, default); dal.Verify(d => d.UpdateTargetStatus(It.Is(t => t.TargetStatus == 3), Login, default), Times.Once); dal.Verify(d => d.UpdateCampaignStatus(1, 3, Login, default), Times.Once); // Completed } }