using EntitlementBLL.Common;
using EntitlementBLL.Exceptions;
using EntitlementBLL.Implementations;
using EntitlementBLL.Interfaces;
using EntitlementDAL.DTOs;
using EntitlementDAL.Interfaces;
using GB5Shared.DTO.Framework.Login;
using Moq;
using Xunit;
namespace EntitlementTests;
///
/// Covers ClientEntitlementSnapshotBLL — the combined subscription/support/deployment-type/
/// enabled-feature read that SqlWorkbench's rollout/provisioning Gate A/Gate B (tracker §40)
/// calls over HTTP via SwBLL.Integration.EntitlementSubscriptionClient.
///
public class ClientEntitlementSnapshotBLLTests
{
private static (ClientEntitlementSnapshotBLL Svc, Mock Sub, Mock Plan, Mock ClientDal)
BuildService()
{
var sub = new Mock();
var plan = new Mock();
var clientDal = new Mock();
var loginFactory = new Mock();
loginFactory.Setup(f => f.Create(It.IsAny(), It.IsAny()))
.Returns((int clientId, int userId) => new LoginDTO { ClientId = clientId, UserId = userId });
var svc = new ClientEntitlementSnapshotBLL(sub.Object, plan.Object, clientDal.Object, loginFactory.Object);
return (svc, sub, plan, clientDal);
}
[Fact]
public async Task GetSnapshotAsync_ZeroClientId_Throws()
{
var (svc, _, _, _) = BuildService();
await Assert.ThrowsAsync(() => svc.GetSnapshotAsync(0, default));
}
[Fact]
public async Task GetSnapshotAsync_RealNegativeClientId_DoesNotThrow()
{
// Real GB5 ClientIds are large negative AutoNumber-allocated values, never small
// positive ones — live-verified 2026-09-08 that a "clientId <= 0" guard silently
// rejected every genuine client, aborting Gate A/B rollout-campaign resolution entirely.
var (svc, sub, plan, clientDal) = BuildService();
sub.Setup(s => s.GetAsync(-999999001, default)).ThrowsAsync(new EntitlementNotFoundException("No subscription found."));
clientDal.Setup(d => d.GetClientDeploymentTypeAsync(-999999001, It.IsAny(), default)).ReturnsAsync((byte?)null);
var result = await svc.GetSnapshotAsync(-999999001, default);
Assert.Equal(-999999001, result.ClientId);
Assert.Equal((byte)0, result.SubscriptionStatus);
}
[Fact]
public async Task GetSnapshotAsync_NoSubscription_ReturnsPendingStatusWithNoFeatures()
{
var (svc, sub, plan, clientDal) = BuildService();
sub.Setup(s => s.GetAsync(7, default)).ThrowsAsync(new EntitlementNotFoundException("No subscription found for ClientId 7."));
clientDal.Setup(d => d.GetClientDeploymentTypeAsync(7, It.IsAny(), default)).ReturnsAsync((byte?)null);
var result = await svc.GetSnapshotAsync(7, default);
Assert.Equal(7, result.ClientId);
Assert.Equal((byte)0, result.SubscriptionStatus);
Assert.Empty(result.EnabledFeatureCodes);
plan.Verify(p => p.GetPlanFeaturesAsync(It.IsAny(), It.IsAny(), default), Times.Never);
}
[Fact]
public async Task GetSnapshotAsync_ActiveSubscription_ReturnsEnabledFeatureCodesAndDeploymentType()
{
var (svc, sub, plan, clientDal) = BuildService();
sub.Setup(s => s.GetAsync(7, default)).ReturnsAsync(new SubscriptionDto
{
ClientId = 7, PlanId = 3, SubscriptionStatus = 1,
SupportValidTill = DateTime.UtcNow.AddDays(10), GracePeriodDays = 5
});
plan.Setup(p => p.GetPlanFeaturesAsync(3, It.IsAny(), default)).ReturnsAsync(new List
{
new() { FeatureCode = "MOD.PRODUCTION", IsEnabled = 1 },
new() { FeatureCode = "MOD.DISABLED", IsEnabled = 0 }
});
clientDal.Setup(d => d.GetClientDeploymentTypeAsync(7, It.IsAny(), default)).ReturnsAsync((byte?)1);
var result = await svc.GetSnapshotAsync(7, default);
Assert.Equal((byte)1, result.SubscriptionStatus);
Assert.Equal((byte)1, result.DeploymentType);
Assert.Single(result.EnabledFeatureCodes);
Assert.Contains("MOD.PRODUCTION", result.EnabledFeatureCodes);
}
}