using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using AgileConfig.Server.Application; using AgileConfig.Server.Application.Roles; using AgileConfig.Server.Common; using AgileConfig.Server.Data.Entity; using AgileConfig.Server.IService; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; namespace AgileConfig.Server.ApplicationTests; [TestClass] public sealed class RoleManagementServiceTests { [TestMethod] public async Task GetAllAsync_FiltersSuperAdminSortsAndLoadsFunctions() { var roleService = new Mock(); var superAdmin = new Role { Id = SystemRoleConstants.SuperAdminId, Name = "Super Administrator", IsSystem = true }; var operatorRole = new Role { Id = SystemRoleConstants.OperatorId, Name = "Operator", IsSystem = true }; var adminRole = new Role { Id = SystemRoleConstants.AdminId, Name = "Administrator", IsSystem = true }; var zetaRole = new Role { Id = "zeta", Name = "Zeta", IsSystem = false }; var alphaRole = new Role { Id = "alpha", Name = "Alpha", IsSystem = false }; roleService.Setup(x => x.GetAllAsync()) .ReturnsAsync(new List { zetaRole, superAdmin, operatorRole, alphaRole, adminRole }); roleService.Setup(x => x.GetFunctionsAsync(adminRole.Id)) .ReturnsAsync(new List { Functions.Role_Read }); roleService.Setup(x => x.GetFunctionsAsync(operatorRole.Id)) .ReturnsAsync(new List { Functions.App_Read, Functions.Config_Read }); roleService.Setup(x => x.GetFunctionsAsync(alphaRole.Id)) .ReturnsAsync(new List()); roleService.Setup(x => x.GetFunctionsAsync(zetaRole.Id)) .ReturnsAsync(new List { Functions.Log_Read }); var result = await CreateService(roleService).GetAllAsync(); Assert.AreEqual(4, result.Count); Assert.AreSame(adminRole, result[0].Role); Assert.AreSame(operatorRole, result[1].Role); Assert.AreSame(alphaRole, result[2].Role); Assert.AreSame(zetaRole, result[3].Role); CollectionAssert.AreEqual(new[] { Functions.Role_Read }, result[0].Functions.ToArray()); CollectionAssert.AreEqual( new[] { Functions.App_Read, Functions.Config_Read }, result[1].Functions.ToArray()); Assert.IsFalse(result.Any(x => x.Role.Id == SystemRoleConstants.SuperAdminId)); roleService.Verify(x => x.GetFunctionsAsync(SystemRoleConstants.SuperAdminId), Times.Never); roleService.Verify(x => x.GetFunctionsAsync(It.IsAny()), Times.Exactly(4)); } [TestMethod] public async Task CreateAsync_MapsRoleAndNormalizesFunctions() { var roleService = new Mock(); Role createdRole = null; List createdFunctions = null; roleService.Setup(x => x.GetAsync("custom-role")) .ReturnsAsync((Role)null); roleService.Setup(x => x.CreateAsync(It.IsAny(), It.IsAny>())) .Callback>((role, functions) => { createdRole = role; createdFunctions = functions.ToList(); }) .ReturnsAsync((Role role, IEnumerable _) => role); var result = await CreateService(roleService).CreateAsync(new CreateRoleCommand( "custom-role", "Custom role", null, new[] { Functions.Role_Read, " ", Functions.Role_Read, null, Functions.App_Read })); Assert.IsTrue(result.Succeeded); Assert.AreSame(createdRole, result.Value); Assert.AreEqual("custom-role", createdRole.Id); Assert.AreEqual("Custom role", createdRole.Name); Assert.AreEqual(string.Empty, createdRole.Description); Assert.IsFalse(createdRole.IsSystem); CollectionAssert.AreEqual( new[] { Functions.Role_Read, Functions.App_Read }, createdFunctions); roleService.Verify(x => x.CreateAsync(It.IsAny(), It.IsAny>()), Times.Once); } [TestMethod] public async Task CreateAsync_WhenIdentifierExistsReturnsConflictWithoutWriting() { var roleService = new Mock(); roleService.Setup(x => x.GetAsync("existing")) .ReturnsAsync(new Role { Id = "existing" }); var result = await CreateService(roleService).CreateAsync(new CreateRoleCommand( "existing", "Duplicate", "description", Array.Empty())); Assert.IsFalse(result.Succeeded); Assert.AreEqual(ApplicationError.Conflict, result.Error); roleService.Verify(x => x.CreateAsync(It.IsAny(), It.IsAny>()), Times.Never); } [TestMethod] public async Task UpdateAsync_WhenRoleDoesNotExistReturnsNotFoundWithoutWriting() { var roleService = new Mock(); roleService.Setup(x => x.GetAsync("missing")) .ReturnsAsync((Role)null); var result = await CreateService(roleService).UpdateAsync(new UpdateRoleCommand( "missing", "Updated", null, Array.Empty())); Assert.IsFalse(result.Succeeded); Assert.AreEqual(ApplicationError.NotFound, result.Error); roleService.Verify(x => x.UpdateAsync(It.IsAny(), It.IsAny>()), Times.Never); } [TestMethod] public async Task UpdateAsync_WhenRoleIsSuperAdminReturnsForbiddenWithoutWriting() { var roleService = new Mock(); roleService.Setup(x => x.GetAsync(SystemRoleConstants.SuperAdminId)) .ReturnsAsync(new Role { Id = SystemRoleConstants.SuperAdminId, Name = "Super Administrator", IsSystem = true }); var result = await CreateService(roleService).UpdateAsync(new UpdateRoleCommand( SystemRoleConstants.SuperAdminId, "Changed", "Changed description", new[] { Functions.Role_Edit })); Assert.IsFalse(result.Succeeded); Assert.AreEqual(ApplicationError.Forbidden, result.Error); roleService.Verify(x => x.UpdateAsync(It.IsAny(), It.IsAny>()), Times.Never); } [TestMethod] public async Task UpdateAsync_PreservesSystemFlagAndMapsNormalizedFunctions() { var roleService = new Mock(); var existingRole = new Role { Id = "system-role", Name = "Old name", Description = "Old description", IsSystem = true }; Role updatedRole = null; List updatedFunctions = null; roleService.Setup(x => x.GetAsync(existingRole.Id)).ReturnsAsync(existingRole); roleService.Setup(x => x.UpdateAsync(It.IsAny(), It.IsAny>())) .Callback>((role, functions) => { updatedRole = role; updatedFunctions = functions.ToList(); }) .ReturnsAsync(true); var result = await CreateService(roleService).UpdateAsync(new UpdateRoleCommand( existingRole.Id, "New name", null, new[] { Functions.Config_Read, "", Functions.Config_Read })); Assert.IsTrue(result.Succeeded); Assert.AreSame(updatedRole, result.Value); Assert.AreEqual(existingRole.Id, updatedRole.Id); Assert.AreEqual("New name", updatedRole.Name); Assert.AreEqual(string.Empty, updatedRole.Description); Assert.IsTrue(updatedRole.IsSystem); CollectionAssert.AreEqual(new[] { Functions.Config_Read }, updatedFunctions); roleService.Verify(x => x.UpdateAsync(It.IsAny(), It.IsAny>()), Times.Once); } [TestMethod] public async Task UpdateAsync_WhenPersistenceFailsReturnsOperationFailed() { var roleService = new Mock(); roleService.Setup(x => x.GetAsync("role-1")) .ReturnsAsync(new Role { Id = "role-1", IsSystem = false }); roleService.Setup(x => x.UpdateAsync(It.IsAny(), It.IsAny>())) .ReturnsAsync(false); var result = await CreateService(roleService).UpdateAsync(new UpdateRoleCommand( "role-1", "Updated", "description", new[] { Functions.Role_Read })); Assert.IsFalse(result.Succeeded); Assert.AreEqual(ApplicationError.OperationFailed, result.Error); roleService.Verify(x => x.UpdateAsync(It.IsAny(), It.IsAny>()), Times.Once); } [TestMethod] public async Task DeleteAsync_WhenRoleDoesNotExistReturnsNotFoundWithoutDeleting() { var roleService = new Mock(); roleService.Setup(x => x.GetAsync("missing")) .ReturnsAsync((Role)null); var result = await CreateService(roleService).DeleteAsync("missing"); Assert.IsFalse(result.Succeeded); Assert.AreEqual(ApplicationError.NotFound, result.Error); roleService.Verify(x => x.DeleteAsync(It.IsAny()), Times.Never); } [TestMethod] public async Task DeleteAsync_WhenRoleIsSystemReturnsForbiddenWithoutDeleting() { var roleService = new Mock(); roleService.Setup(x => x.GetAsync(SystemRoleConstants.AdminId)) .ReturnsAsync(new Role { Id = SystemRoleConstants.AdminId, Name = "Administrator", IsSystem = true }); var result = await CreateService(roleService).DeleteAsync(SystemRoleConstants.AdminId); Assert.IsFalse(result.Succeeded); Assert.AreEqual(ApplicationError.Forbidden, result.Error); roleService.Verify(x => x.DeleteAsync(It.IsAny()), Times.Never); } [TestMethod] public async Task DeleteAsync_WhenRoleIsDeletedReturnsSuccess() { var roleService = new Mock(); roleService.Setup(x => x.GetAsync("role-1")) .ReturnsAsync(new Role { Id = "role-1", IsSystem = false }); roleService.Setup(x => x.DeleteAsync("role-1")) .ReturnsAsync(true); var result = await CreateService(roleService).DeleteAsync("role-1"); Assert.IsTrue(result.Succeeded); Assert.AreEqual(ApplicationError.None, result.Error); roleService.Verify(x => x.DeleteAsync("role-1"), Times.Once); } [TestMethod] public async Task DeleteAsync_WhenPersistenceFailsReturnsOperationFailed() { var roleService = new Mock(); roleService.Setup(x => x.GetAsync("role-1")) .ReturnsAsync(new Role { Id = "role-1", IsSystem = false }); roleService.Setup(x => x.DeleteAsync("role-1")) .ReturnsAsync(false); var result = await CreateService(roleService).DeleteAsync("role-1"); Assert.IsFalse(result.Succeeded); Assert.AreEqual(ApplicationError.OperationFailed, result.Error); roleService.Verify(x => x.DeleteAsync("role-1"), Times.Once); } [TestMethod] public void GetSupportedPermissions_ReturnsCorePermissionKeys() { var permissions = CreateService(new Mock()).GetSupportedPermissions(); Assert.IsTrue(permissions.Count > 0); CollectionAssert.Contains(permissions.ToList(), Functions.App_Read); CollectionAssert.Contains(permissions.ToList(), Functions.Config_Publish); CollectionAssert.Contains(permissions.ToList(), Functions.Role_Read); CollectionAssert.Contains(permissions.ToList(), Functions.Log_Read); Assert.AreEqual(permissions.Count, permissions.Distinct().Count()); } private static RoleManagementService CreateService(Mock roleService) { return new RoleManagementService(roleService.Object); } }