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