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