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