using GB5Shared.DTO.Framework.Login; using Moq; using SwBLL.ClientDatabaseKey; using SwDAL.CustomCode.ClientDatabaseKey; using SwDAL.DTO.ClientDatabaseKey; using Xunit; namespace SwTests; /// /// Covers ClientDatabaseKeyBLL (Offline Rollout Package, tracker §53) — the registry for an /// on-prem/BYOC client database's own public key, rotatable (Supersede-then-insert) rather than /// overwritable. AutoNumber is passed null! for guard tests that never reach the auto-numbered /// insert path, same convention already established in UpgradePackageBLLTests/RefDataSourceBLLTests. /// public class ClientDatabaseKeyBLLTests { private static readonly LoginDTO Login = new() { ClientId = -1, UserId = -1 }; private static (ClientDatabaseKeyBLL Svc, Mock Dal) BuildService() { var dal = new Mock(); var svc = new ClientDatabaseKeyBLL(dal.Object, autoNumber: null!); return (svc, dal); } [Fact] public async Task RegisterKeyAsync_MissingClientDbId_Throws() { var (svc, dal) = BuildService(); await Assert.ThrowsAsync(() => svc.RegisterKeyAsync(0, "PEM", Login, default)); dal.Verify(d => d.SupersedeActive(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } [Theory] [InlineData(null)] [InlineData("")] [InlineData(" ")] public async Task RegisterKeyAsync_MissingPublicKeyPem_Throws(string? pem) { var (svc, dal) = BuildService(); await Assert.ThrowsAsync(() => svc.RegisterKeyAsync(1, pem!, Login, default)); dal.Verify(d => d.SupersedeActive(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } [Fact] public async Task RegisterKeyAsync_ValidInput_SupersedesActiveBeforeAllocatingNewId() { var (svc, dal) = BuildService(); dal.Setup(d => d.SupersedeActive(7, Login, default)).ReturnsAsync(1); // Guards pass, SupersedeActive is called first — null AutoNumber then throws // NullReferenceException on the next line, proving the ordering without needing a real // AutoNumber/QueryExecutor round trip. await Assert.ThrowsAsync(() => svc.RegisterKeyAsync(7, "-----BEGIN PUBLIC KEY-----", Login, default)); dal.Verify(d => d.SupersedeActive(7, Login, default), Times.Once); dal.Verify(d => d.Insert(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } [Fact] public async Task RevokeAsync_MissingId_Throws() { var (svc, dal) = BuildService(); await Assert.ThrowsAsync(() => svc.RevokeAsync(0, Login, default)); dal.Verify(d => d.Revoke(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } [Fact] public async Task RevokeAsync_ValidId_CallsDal() { var (svc, dal) = BuildService(); dal.Setup(d => d.Revoke(9, Login, default)).ReturnsAsync(1); var result = await svc.RevokeAsync(9, Login, default); Assert.Contains("updated", result, StringComparison.OrdinalIgnoreCase); dal.Verify(d => d.Revoke(9, Login, default), Times.Once); } [Fact] public async Task GetActiveByClientDb_DelegatesToDal() { var (svc, dal) = BuildService(); var expected = new ClientDatabaseKeyDTO { ClientDatabaseKeyId = 1, ClientDbId = 7, KeyStatus = 0 }; dal.Setup(d => d.GetActiveByClientDb(7, Login, default)).ReturnsAsync(expected); var result = await svc.GetActiveByClientDb(7, Login, default); Assert.Same(expected, result); } }