using GB5Shared.DTO.Framework.AutoNumber; using GB5Shared.DTO.Framework.CommonConfig; using GB5Shared.DTO.Framework.Login; using GB5Shared.Enums.Client; using GB5Shared.GenerateAutoNumber; using GB5Shared.QueryExecutor; using Microsoft.Extensions.Options; 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 SwDAL.DTO.UpgradePackage; using SwDAL.Enums; using Xunit; namespace SwTests; /// /// Covers Entitlement-aware gating (tracker §40) inside RolloutCampaignBLL.CreateCampaignAsync — /// Gate A (subscription/support/deployment-type eligibility) and Gate B (feature/module /// licensing for MetadataOrStandardData packages). All dependencies are mocked; no real /// Gb5system/EntitlementDb database or live Entitlement host is touched. /// public class RolloutCampaignBLLTests { private static readonly LoginDTO Login = new() { ClientId = 1, UserId = -1 }; private static (RolloutCampaignBLL Svc, Mock Dal, Mock Pkg, Mock ClientDb, Mock Ent) BuildService() { var dal = new Mock(); var pkg = new Mock(); var clientDb = new Mock(); var ent = new Mock(); var provisioning = new Mock(); var queryExecutor = new Mock(); queryExecutor .Setup(q => q.QueryAsync(It.IsAny(), It.IsAny(), It.IsAny(), null, false, It.IsAny())) .ReturnsAsync(new List { 1 }); var dbConfig = new Mock>(); dbConfig.Setup(o => o.Value).Returns(new Gb5SystemDTO { DataBaseType = 0 }); var autoNumber = new AutoNumber(queryExecutor.Object, dbConfig.Object); var svc = new RolloutCampaignBLL(dal.Object, provisioning.Object, pkg.Object, clientDb.Object, ent.Object, autoNumber); return (svc, dal, pkg, clientDb, ent); } private static UpgradePackageDTO SchemaPackage(int id = 1) => new() { PackageId = id, PackageContentType = (byte)PackageContentType.Schema, RequiredFeatureCode = null }; private static UpgradePackageDTO MetadataPackage(int id, string? requiredFeatureCode) => new() { PackageId = id, PackageContentType = (byte)PackageContentType.MetadataOrStandardData, RequiredFeatureCode = requiredFeatureCode }; private static ClientDatabaseDTO ClientDb(int clientDbId, int tenantId) => new() { ClientDbId = clientDbId, TenantId = tenantId }; private static EntitlementSnapshotDTO Snapshot( int clientId, bool active = true, bool inSupport = true, byte deploymentType = (byte)ClientDeploymentType.SaaS, params string[] enabledFeatureCodes) => new() { ClientId = clientId, SubscriptionStatus = active ? (byte)1 : (byte)2, // 1=Active 2=Expired (EntitlementDAL.Enums.SubscriptionStatusEnum) SupportValidTill = inSupport ? DateTime.UtcNow.AddDays(30) : DateTime.UtcNow.AddDays(-100), GracePeriodDays = 0, DeploymentType = deploymentType, EnabledFeatureCodes = enabledFeatureCodes.ToList() }; private void SetupResolvedTarget(Mock dal, int clientDbId) => dal.Setup(d => d.ResolveTargetsAllForDbModel(9, Login, default)).ReturnsAsync(new[] { clientDbId }); [Fact] public async Task CreateCampaignAsync_SchemaPackage_ActiveInSupportSaaSClient_IsIncluded() { var (svc, dal, pkg, clientDb, ent) = BuildService(); pkg.Setup(p => p.GetById(1, Login, default)).ReturnsAsync(SchemaPackage()); SetupResolvedTarget(dal, 100); clientDb.Setup(c => c.GetById(100, Login, default)).ReturnsAsync(ClientDb(100, tenantId: 55)); ent.Setup(e => e.GetSnapshotAsync(55, Login, default)).ReturnsAsync(Snapshot(55)); var dto = new RolloutCampaignDTO { PackageId = 1, DbModelId = 9, TargetScope = 0 }; var id = await svc.CreateCampaignAsync(dto, Login, default); // AutoNumber's mocked query result makes the allocated id itself 0 in this test setup // (same "AUTOID seeding" formula quirk documented elsewhere in this engagement) — the // real assertion is that campaign creation succeeded at all, not the specific id value. dal.Verify(d => d.InsertCampaign(It.IsAny(), Login, default), Times.Once); _ = id; dal.Verify(d => d.InsertTarget(It.Is(t => t.ClientDbId == 100), Login, default), Times.Once); } [Fact] public async Task CreateCampaignAsync_ExpiredSubscription_ExcludedFromEvenAutoScope() { var (svc, dal, pkg, clientDb, ent) = BuildService(); pkg.Setup(p => p.GetById(1, Login, default)).ReturnsAsync(SchemaPackage()); SetupResolvedTarget(dal, 100); clientDb.Setup(c => c.GetById(100, Login, default)).ReturnsAsync(ClientDb(100, tenantId: 55)); ent.Setup(e => e.GetSnapshotAsync(55, Login, default)).ReturnsAsync(Snapshot(55, active: false)); var dto = new RolloutCampaignDTO { PackageId = 1, DbModelId = 9, TargetScope = 0 }; await Assert.ThrowsAsync(() => svc.CreateCampaignAsync(dto, Login, default)); dal.Verify(d => d.InsertTarget(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } [Fact] public async Task CreateCampaignAsync_SupportLapsedPastGrace_Excluded() { var (svc, dal, pkg, clientDb, ent) = BuildService(); pkg.Setup(p => p.GetById(1, Login, default)).ReturnsAsync(SchemaPackage()); SetupResolvedTarget(dal, 100); clientDb.Setup(c => c.GetById(100, Login, default)).ReturnsAsync(ClientDb(100, tenantId: 55)); ent.Setup(e => e.GetSnapshotAsync(55, Login, default)).ReturnsAsync(Snapshot(55, inSupport: false)); var dto = new RolloutCampaignDTO { PackageId = 1, DbModelId = 9, TargetScope = 0 }; await Assert.ThrowsAsync(() => svc.CreateCampaignAsync(dto, Login, default)); } [Fact] public async Task CreateCampaignAsync_OnPremClient_ExcludedFromAllForDbModel_ButIncludedViaSelectedList() { var (svc, dal, pkg, clientDb, ent) = BuildService(); pkg.Setup(p => p.GetById(1, Login, default)).ReturnsAsync(SchemaPackage()); clientDb.Setup(c => c.GetById(100, Login, default)).ReturnsAsync(ClientDb(100, tenantId: 55)); ent.Setup(e => e.GetSnapshotAsync(55, Login, default)).ReturnsAsync(Snapshot(55, deploymentType: (byte)ClientDeploymentType.OnPrem)); SetupResolvedTarget(dal, 100); var autoScopeDto = new RolloutCampaignDTO { PackageId = 1, DbModelId = 9, TargetScope = 0 }; await Assert.ThrowsAsync(() => svc.CreateCampaignAsync(autoScopeDto, Login, default)); var selectedListDto = new RolloutCampaignDTO { PackageId = 1, DbModelId = 9, TargetScope = 2, SelectedClientDbIds = new List { 100 } }; var id = await svc.CreateCampaignAsync(selectedListDto, Login, default); // AutoNumber's mocked query result makes the allocated id itself 0 in this test setup // (same "AUTOID seeding" formula quirk documented elsewhere in this engagement) — the // real assertion is that campaign creation succeeded at all, not the specific id value. dal.Verify(d => d.InsertCampaign(It.IsAny(), Login, default), Times.Once); _ = id; dal.Verify(d => d.InsertTarget(It.Is(t => t.ClientDbId == 100), Login, default), Times.Once); } [Fact] public async Task CreateCampaignAsync_MetadataPackage_ClientMissingRequiredFeature_Excluded() { var (svc, dal, pkg, clientDb, ent) = BuildService(); pkg.Setup(p => p.GetById(2, Login, default)).ReturnsAsync(MetadataPackage(2, "MOD.PRODUCTION")); SetupResolvedTarget(dal, 100); clientDb.Setup(c => c.GetById(100, Login, default)).ReturnsAsync(ClientDb(100, tenantId: 55)); ent.Setup(e => e.GetSnapshotAsync(55, Login, default)).ReturnsAsync(Snapshot(55, enabledFeatureCodes: "MOD.INVENTORY")); var dto = new RolloutCampaignDTO { PackageId = 2, DbModelId = 9, TargetScope = 0 }; await Assert.ThrowsAsync(() => svc.CreateCampaignAsync(dto, Login, default)); } [Fact] public async Task CreateCampaignAsync_MetadataPackage_ClientHasRequiredFeature_Included() { var (svc, dal, pkg, clientDb, ent) = BuildService(); pkg.Setup(p => p.GetById(2, Login, default)).ReturnsAsync(MetadataPackage(2, "MOD.PRODUCTION")); SetupResolvedTarget(dal, 100); clientDb.Setup(c => c.GetById(100, Login, default)).ReturnsAsync(ClientDb(100, tenantId: 55)); ent.Setup(e => e.GetSnapshotAsync(55, Login, default)).ReturnsAsync(Snapshot(55, enabledFeatureCodes: "MOD.PRODUCTION")); var dto = new RolloutCampaignDTO { PackageId = 2, DbModelId = 9, TargetScope = 0 }; var id = await svc.CreateCampaignAsync(dto, Login, default); // AutoNumber's mocked query result makes the allocated id itself 0 in this test setup // (same "AUTOID seeding" formula quirk documented elsewhere in this engagement) — the // real assertion is that campaign creation succeeded at all, not the specific id value. dal.Verify(d => d.InsertCampaign(It.IsAny(), Login, default), Times.Once); _ = id; } [Fact] public async Task CreateCampaignAsync_MetadataPackage_NoRequiredFeatureCode_SkipsGateBRegardlessOfFeatures() { var (svc, dal, pkg, clientDb, ent) = BuildService(); pkg.Setup(p => p.GetById(2, Login, default)).ReturnsAsync(MetadataPackage(2, requiredFeatureCode: null)); SetupResolvedTarget(dal, 100); clientDb.Setup(c => c.GetById(100, Login, default)).ReturnsAsync(ClientDb(100, tenantId: 55)); ent.Setup(e => e.GetSnapshotAsync(55, Login, default)).ReturnsAsync(Snapshot(55)); // no enabled features at all var dto = new RolloutCampaignDTO { PackageId = 2, DbModelId = 9, TargetScope = 0 }; var id = await svc.CreateCampaignAsync(dto, Login, default); // AutoNumber's mocked query result makes the allocated id itself 0 in this test setup // (same "AUTOID seeding" formula quirk documented elsewhere in this engagement) — the // real assertion is that campaign creation succeeded at all, not the specific id value. dal.Verify(d => d.InsertCampaign(It.IsAny(), Login, default), Times.Once); _ = id; } [Fact] public async Task CreateCampaignAsync_SchemaPackage_ClientMissingAnyFeature_StillIncluded_SchemaNeverGated() { var (svc, dal, pkg, clientDb, ent) = BuildService(); pkg.Setup(p => p.GetById(1, Login, default)).ReturnsAsync(SchemaPackage()); SetupResolvedTarget(dal, 100); clientDb.Setup(c => c.GetById(100, Login, default)).ReturnsAsync(ClientDb(100, tenantId: 55)); ent.Setup(e => e.GetSnapshotAsync(55, Login, default)).ReturnsAsync(Snapshot(55)); // zero enabled features var dto = new RolloutCampaignDTO { PackageId = 1, DbModelId = 9, TargetScope = 0 }; var id = await svc.CreateCampaignAsync(dto, Login, default); // AutoNumber's mocked query result makes the allocated id itself 0 in this test setup // (same "AUTOID seeding" formula quirk documented elsewhere in this engagement) — the // real assertion is that campaign creation succeeded at all, not the specific id value. dal.Verify(d => d.InsertCampaign(It.IsAny(), Login, default), Times.Once); _ = id; } [Fact] public async Task CreateCampaignAsync_NoEntitlementSnapshot_ExcludedNotIncludedByDefault() { var (svc, dal, pkg, clientDb, ent) = BuildService(); pkg.Setup(p => p.GetById(1, Login, default)).ReturnsAsync(SchemaPackage()); SetupResolvedTarget(dal, 100); clientDb.Setup(c => c.GetById(100, Login, default)).ReturnsAsync(ClientDb(100, tenantId: 55)); ent.Setup(e => e.GetSnapshotAsync(55, Login, default)).ReturnsAsync((EntitlementSnapshotDTO?)null); var dto = new RolloutCampaignDTO { PackageId = 1, DbModelId = 9, TargetScope = 0 }; await Assert.ThrowsAsync(() => svc.CreateCampaignAsync(dto, Login, default)); } }