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