using GB5Shared.DTO.Framework.Login; using Moq; using SwBLL.ClientDatabase; using SwBLL.ClientDatabaseKey; using SwBLL.DdlScript; using SwBLL.OfflineRollout; using SwBLL.Provisioning; using SwBLL.UpgradePackage; using SwDAL.DTO.ClientDatabase; using SwDAL.DTO.ClientDatabaseKey; using SwDAL.DTO.DdlScript; using SwDAL.DTO.Provisioning; using SwDAL.DTO.UpgradePackage; using Xunit; namespace SwTests; /// /// Covers OfflineRolloutExportBLL (Offline Rollout Package, tracker §53) — builds the plaintext /// bundle from an already-Released package's linked scripts and hands it to IRolloutPackageCrypto /// encrypted to the client database's registered key. Crypto itself is mocked here (covered for /// real in RolloutPackageCryptoTests) so this file focuses purely on the guard/orchestration /// logic: package-not-found, not-Released, no-registered-key, and the happy path's call shape. /// Also covers CheckForUpdatesAsync (§40 Decision 3's pull-model check). /// public class OfflineRolloutExportBLLTests { private static readonly LoginDTO Login = new() { ClientId = -1, UserId = -1 }; private static (OfflineRolloutExportBLL Svc, Mock Pkg, Mock Script, Mock Key, Mock Crypto, Mock ClientDb, Mock Provisioning) BuildService() { var pkg = new Mock(); var script = new Mock(); var key = new Mock(); var crypto = new Mock(); var clientDb = new Mock(); var provisioning = new Mock(); var svc = new OfflineRolloutExportBLL(pkg.Object, script.Object, key.Object, crypto.Object, clientDb.Object, provisioning.Object); return (svc, pkg, script, key, crypto, clientDb, provisioning); } [Fact] public async Task ExportOfflinePackageAsync_MissingClientDbId_Throws() { var (svc, _, _, _, _, _, _) = BuildService(); await Assert.ThrowsAsync(() => svc.ExportOfflinePackageAsync(0, 1, Login, default)); } [Fact] public async Task ExportOfflinePackageAsync_MissingPackageId_Throws() { var (svc, _, _, _, _, _, _) = BuildService(); await Assert.ThrowsAsync(() => svc.ExportOfflinePackageAsync(1, 0, Login, default)); } [Fact] public async Task ExportOfflinePackageAsync_PackageNotFound_Throws() { var (svc, pkg, _, _, _, _, _) = BuildService(); pkg.Setup(p => p.GetById(5, Login, default)).ReturnsAsync((UpgradePackageDTO?)null); await Assert.ThrowsAsync(() => svc.ExportOfflinePackageAsync(1, 5, Login, default)); } [Fact] public async Task ExportOfflinePackageAsync_PackageNotReleased_Throws() { var (svc, pkg, _, _, _, _, _) = BuildService(); pkg.Setup(p => p.GetById(5, Login, default)).ReturnsAsync(new UpgradePackageDTO { PackageId = 5, PkgStatus = 0 }); await Assert.ThrowsAsync(() => svc.ExportOfflinePackageAsync(1, 5, Login, default)); } [Fact] public async Task ExportOfflinePackageAsync_NoActiveKeyRegistered_Throws() { var (svc, pkg, _, key, _, _, _) = BuildService(); pkg.Setup(p => p.GetById(5, Login, default)).ReturnsAsync(new UpgradePackageDTO { PackageId = 5, PkgStatus = 2 }); key.Setup(k => k.GetActiveByClientDb(1, Login, default)).ReturnsAsync((ClientDatabaseKeyDTO?)null); await Assert.ThrowsAsync(() => svc.ExportOfflinePackageAsync(1, 5, Login, default)); } [Fact] public async Task ExportOfflinePackageAsync_HappyPath_EncryptsBundleToRegisteredKey() { var (svc, pkg, script, key, crypto, _, _) = BuildService(); pkg.Setup(p => p.GetById(5, Login, default)).ReturnsAsync(new UpgradePackageDTO { PackageId = 5, PkgStatus = 2, PackageCode = "TEST_PKG", DbModelId = 1, PackageContentType = 0, ReleaseVersion = "1.0", }); pkg.Setup(p => p.GetDdlLines(5, Login, default)).ReturnsAsync(new List { new() { PackageDdlId = 1, PackageId = 5, DdlScriptId = 101, ObjectName = "MFOO", Sequence = 1 }, }); script.Setup(s => s.GetById(101, Login, default)).ReturnsAsync(new DdlScriptDTO { DdlScriptId = 101, ObjectName = "MFOO", SqlScript = "SELECT 1", Checksum = "abc123", }); key.Setup(k => k.GetActiveByClientDb(1, Login, default)).ReturnsAsync( new ClientDatabaseKeyDTO { ClientDatabaseKeyId = 1, ClientDbId = 1, PublicKeyPem = "PEM", KeyStatus = 0 }); crypto.Setup(c => c.EncryptPackageAsync(It.IsAny(), "PEM", default)) .ReturnsAsync(new byte[] { 9, 9, 9 }); var result = await svc.ExportOfflinePackageAsync(1, 5, Login, default); Assert.Equal(new byte[] { 9, 9, 9 }, result); crypto.Verify(c => c.EncryptPackageAsync(It.IsAny(), "PEM", default), Times.Once); } [Fact] public async Task CheckForUpdatesAsync_MissingClientDbId_Throws() { var (svc, _, _, _, _, _, _) = BuildService(); await Assert.ThrowsAsync(() => svc.CheckForUpdatesAsync(0, Login, default)); } [Fact] public async Task CheckForUpdatesAsync_ClientDbNotFound_Throws() { var (svc, _, _, _, _, clientDb, _) = BuildService(); clientDb.Setup(c => c.GetById(1, Login, default)).ReturnsAsync((ClientDatabaseDTO?)null); await Assert.ThrowsAsync(() => svc.CheckForUpdatesAsync(1, Login, default)); } [Fact] public async Task CheckForUpdatesAsync_LatestMatchesApplied_NoUpdateAvailable() { var (svc, pkg, _, _, _, clientDb, provisioning) = BuildService(); clientDb.Setup(c => c.GetById(1, Login, default)).ReturnsAsync(new ClientDatabaseDTO { ClientDbId = 1, DbModelId = 10 }); provisioning.Setup(p => p.GetCurrentVersion(1, Login, default)).ReturnsAsync( new ClientVersionRollupDTO { ClientDbId = 1, MostRecentlyAppliedPackageId = 5, CurrentReleaseVersion = "1.0" }); pkg.Setup(p => p.GetProvisioningChain(10, null, Login, default)).ReturnsAsync(new List { new() { PackageId = 5, PackageCode = "PKG5", ScopeType = 0 }, }); var result = await svc.CheckForUpdatesAsync(1, Login, default); Assert.False(result.UpdateAvailable); Assert.Equal(5, result.LatestPackageId); } [Fact] public async Task CheckForUpdatesAsync_NewerPackageExists_UpdateAvailable() { var (svc, pkg, _, _, _, clientDb, provisioning) = BuildService(); clientDb.Setup(c => c.GetById(1, Login, default)).ReturnsAsync(new ClientDatabaseDTO { ClientDbId = 1, DbModelId = 10 }); provisioning.Setup(p => p.GetCurrentVersion(1, Login, default)).ReturnsAsync( new ClientVersionRollupDTO { ClientDbId = 1, MostRecentlyAppliedPackageId = 5, CurrentReleaseVersion = "1.0" }); pkg.Setup(p => p.GetProvisioningChain(10, null, Login, default)).ReturnsAsync(new List { new() { PackageId = 7, PackageCode = "PKG7", ScopeType = 0 }, }); var result = await svc.CheckForUpdatesAsync(1, Login, default); Assert.True(result.UpdateAvailable); Assert.Equal(7, result.LatestPackageId); Assert.Equal("PKG7", result.LatestPackageCode); } }