using GB5Shared.DTO.Framework.Login;
using GB5Shared.GenerateAutoNumber;
using Moq;
using SwBLL.ClientDatabase;
using SwBLL.DbServer;
using SwBLL.UserResourceRole;
using SwDAL.CustomCode.UserResourceRole;
using SwDAL.DTO.ClientDatabase;
using SwDAL.DTO.DbServer;
using SwDAL.DTO.UserResourceRole;
using SwDAL.Enums;
using Xunit;
namespace SwTests;
///
/// Covers UserResourceRoleBLL — the per-user, per-resource-instance (DbServer/ClientDatabase)
/// role-grant mechanism, distinct from module-wide MenuRights (which has no per-resource-
/// instance dimension). GetEffectiveRole is the one authorization primitive every enforcement
/// call site (ChangeRequestBLL/SwQueryBLL/ProvisioningBLL) uses — these tests prove its
/// most-specific-wins precedence and its "no grant configured" contract (returns null, never
/// assumes a default — callers decide fail-open vs fail-closed for themselves).
///
/// AutoNumber is passed `null!` for the read-only/guard-clause cases below — safe because
/// every one of those throws before AssignUserResourceRole would ever reach it.
///
public class UserResourceRoleBLLTests
{
private static (UserResourceRoleBLL Svc, Mock Dal,
Mock DbServerBLL, Mock ClientDatabaseBLL) BuildService(AutoNumber? autoNumber = null)
{
var dal = new Mock();
var dbServerBLL = new Mock();
var clientDatabaseBLL = new Mock();
var svc = new UserResourceRoleBLL(dal.Object, dbServerBLL.Object, clientDatabaseBLL.Object, autoNumber!);
return (svc, dal, dbServerBLL, clientDatabaseBLL);
}
private static readonly LoginDTO Login = new() { ClientId = 42, UserId = 1 };
// ── GetEffectiveRole ─────────────────────────────────────────────────
[Fact]
public async Task GetEffectiveRole_ZeroUserId_ThrowsWithoutTouchingDal()
{
var (svc, dal, _, _) = BuildService();
await Assert.ThrowsAsync(() => svc.GetEffectiveRole(0, 1, null, Login, default));
dal.Verify(d => d.GetEffectiveRole(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never);
}
[Fact]
public async Task GetEffectiveRole_NeitherTargetSet_ThrowsWithoutTouchingDal()
{
var (svc, dal, _, _) = BuildService();
await Assert.ThrowsAsync(() => svc.GetEffectiveRole(5, null, null, Login, default));
dal.Verify(d => d.GetEffectiveRole(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never);
}
[Fact]
public async Task GetEffectiveRole_NoGrantConfigured_ReturnsNull()
{
var (svc, dal, _, _) = BuildService();
dal.Setup(d => d.GetEffectiveRole(5, 1, null, Login, default)).ReturnsAsync((byte?)null);
var result = await svc.GetEffectiveRole(5, 1, null, Login, default);
Assert.Null(result);
}
[Fact]
public async Task GetEffectiveRole_ClientDbGrant_ReturnsClientDbLoginRole()
{
var (svc, dal, _, _) = BuildService();
dal.Setup(d => d.GetEffectiveRole(5, null, 10, Login, default)).ReturnsAsync((byte?)(byte)ClientDbLoginRole.Dba);
var result = await svc.GetEffectiveRole(5, null, 10, Login, default);
Assert.Equal(ClientDbLoginRole.Dba, result);
}
[Fact]
public async Task GetEffectiveRole_BothTargetsPassed_DelegatesPrecedenceToDal()
{
// Most-specific-wins is resolved by UserResourceRoleQB.GET_EFFECTIVE_ROLE itself (DB-
// level ORDER BY) — the BLL just passes both targets through and trusts the DAL's
// single row back. This proves the BLL doesn't second-guess or reorder the DAL's answer.
var (svc, dal, _, _) = BuildService();
dal.Setup(d => d.GetEffectiveRole(5, 1, 10, Login, default)).ReturnsAsync((byte?)(byte)ClientDbLoginRole.ReadOnly);
var result = await svc.GetEffectiveRole(5, 1, 10, Login, default);
Assert.Equal(ClientDbLoginRole.ReadOnly, result);
}
// ── AssignUserResourceRole — guard clauses ──────────────────────────
[Fact]
public async Task AssignUserResourceRole_ZeroUserId_ThrowsWithoutTouchingDal()
{
var (svc, dal, _, _) = BuildService();
var dto = new AssignUserResourceRoleDTO { UserId = 0, DbServerId = 1, Role = (byte)ClientDbLoginRole.Dba };
await Assert.ThrowsAsync(() => svc.AssignUserResourceRole(dto, Login, default));
dal.Verify(d => d.UpsertGrant(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never);
}
[Fact]
public async Task AssignUserResourceRole_BothTargetsSet_ThrowsWithoutTouchingDal()
{
var (svc, dal, _, _) = BuildService();
var dto = new AssignUserResourceRoleDTO { UserId = 5, DbServerId = 1, ClientDbId = 10, Role = (byte)ClientDbLoginRole.Dba };
await Assert.ThrowsAsync(() => svc.AssignUserResourceRole(dto, Login, default));
dal.Verify(d => d.UpsertGrant(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never);
}
[Fact]
public async Task AssignUserResourceRole_NeitherTargetSet_ThrowsWithoutTouchingDal()
{
var (svc, dal, _, _) = BuildService();
var dto = new AssignUserResourceRoleDTO { UserId = 5, Role = (byte)ClientDbLoginRole.Dba };
await Assert.ThrowsAsync(() => svc.AssignUserResourceRole(dto, Login, default));
dal.Verify(d => d.UpsertGrant(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never);
}
[Fact]
public async Task AssignUserResourceRole_InvalidRole_ThrowsWithoutTouchingDal()
{
var (svc, dal, _, _) = BuildService();
var dto = new AssignUserResourceRoleDTO { UserId = 5, DbServerId = 1, Role = 99 };
await Assert.ThrowsAsync(() => svc.AssignUserResourceRole(dto, Login, default));
dal.Verify(d => d.UpsertGrant(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never);
}
[Fact]
public async Task AssignUserResourceRole_DbServerNotFound_Throws()
{
var (svc, dal, dbServerBLL, _) = BuildService();
dbServerBLL.Setup(d => d.GetById(1, Login, default)).ReturnsAsync((DbServerDTO?)null);
var dto = new AssignUserResourceRoleDTO { UserId = 5, DbServerId = 1, Role = (byte)ClientDbLoginRole.Dba };
await Assert.ThrowsAsync(() => svc.AssignUserResourceRole(dto, Login, default));
dal.Verify(d => d.UpsertGrant(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never);
}
[Fact]
public async Task AssignUserResourceRole_ClientDbNotFound_Throws()
{
var (svc, dal, _, clientDatabaseBLL) = BuildService();
clientDatabaseBLL.Setup(c => c.GetById(10, Login, default)).ReturnsAsync((ClientDatabaseDTO?)null);
var dto = new AssignUserResourceRoleDTO { UserId = 5, ClientDbId = 10, Role = (byte)ClientDbLoginRole.ReadOnly };
await Assert.ThrowsAsync(() => svc.AssignUserResourceRole(dto, Login, default));
dal.Verify(d => d.UpsertGrant(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never);
}
// ── RevokeUserResourceRole — guard clauses ──────────────────────────
[Fact]
public async Task RevokeUserResourceRole_ZeroUserId_ThrowsWithoutTouchingDal()
{
var (svc, dal, _, _) = BuildService();
var dto = new RevokeUserResourceRoleDTO { UserId = 0, DbServerId = 1 };
await Assert.ThrowsAsync(() => svc.RevokeUserResourceRole(dto, Login, default));
dal.Verify(d => d.RevokeGrant(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never);
}
[Fact]
public async Task RevokeUserResourceRole_BothTargetsSet_ThrowsWithoutTouchingDal()
{
var (svc, dal, _, _) = BuildService();
var dto = new RevokeUserResourceRoleDTO { UserId = 5, DbServerId = 1, ClientDbId = 10 };
await Assert.ThrowsAsync(() => svc.RevokeUserResourceRole(dto, Login, default));
dal.Verify(d => d.RevokeGrant(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never);
}
[Fact]
public async Task RevokeUserResourceRole_ValidRequest_CallsDal()
{
var (svc, dal, _, _) = BuildService();
var dto = new RevokeUserResourceRoleDTO { UserId = 5, DbServerId = 1 };
var result = await svc.RevokeUserResourceRole(dto, Login, default);
Assert.False(string.IsNullOrEmpty(result));
dal.Verify(d => d.RevokeGrant(dto, Login, default), Times.Once);
}
// ── Read guard clauses ───────────────────────────────────────────────
[Fact]
public async Task GetGrantsForUser_ZeroUserId_ThrowsWithoutTouchingDal()
{
var (svc, dal, _, _) = BuildService();
await Assert.ThrowsAsync(() => svc.GetGrantsForUser(0, Login, default));
dal.Verify(d => d.GetGrantsForUser(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never);
}
[Fact]
public async Task GetGranteesForServer_ZeroDbServerId_ThrowsWithoutTouchingDal()
{
var (svc, dal, _, _) = BuildService();
await Assert.ThrowsAsync(() => svc.GetGranteesForServer(0, Login, default));
dal.Verify(d => d.GetGranteesForServer(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never);
}
[Fact]
public async Task GetGranteesForClientDb_ZeroClientDbId_ThrowsWithoutTouchingDal()
{
var (svc, dal, _, _) = BuildService();
await Assert.ThrowsAsync(() => svc.GetGranteesForClientDb(0, Login, default));
dal.Verify(d => d.GetGranteesForClientDb(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never);
}
}