diff --git a/src/Application/Departments/Queries/GetAllDepartments.cs b/src/Application/Departments/Queries/GetAllDepartments.cs index a4e5d711..1a2f85c1 100644 --- a/src/Application/Departments/Queries/GetAllDepartments.cs +++ b/src/Application/Departments/Queries/GetAllDepartments.cs @@ -24,9 +24,10 @@ public QueryHandler(IApplicationDbContext context, IMapper mapper) public async Task> Handle(Query request, CancellationToken cancellationToken) { - var departments = await _context.Departments.ToListAsync(cancellationToken); + var departments = await _context.Departments + .ToListAsync(cancellationToken); var result = new ReadOnlyCollection(_mapper.Map>(departments)); return result; } } -} \ No newline at end of file +} diff --git a/src/Application/Lockers/Commands/UpdateLocker.cs b/src/Application/Lockers/Commands/UpdateLocker.cs index 580c1e6f..f389c468 100644 --- a/src/Application/Lockers/Commands/UpdateLocker.cs +++ b/src/Application/Lockers/Commands/UpdateLocker.cs @@ -1,10 +1,34 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities.Physical; +using Domain.Exceptions; +using FluentValidation; using MediatR; +using Microsoft.EntityFrameworkCore; namespace Application.Lockers.Commands; public class UpdateLocker { + public class Validator : AbstractValidator + { + public Validator() + { + RuleLevelCascadeMode = CascadeMode.Stop; + + RuleFor(x => x.Name) + .NotEmpty() + .MaximumLength(64).WithMessage("Locker's name cannot exceed 64 characters."); + + RuleFor(x => x.Description) + .MaximumLength(256).WithMessage("Locker's description cannot exceed 256 characters."); + + RuleFor(x => x.Capacity) + .GreaterThan(0).WithMessage("Locker's capacity cannot be less than 1"); + } + } public record Command : IRequest { public Guid LockerId { get; init; } @@ -12,4 +36,48 @@ public record Command : IRequest public string? Description { get; init; } public int Capacity { get; init; } } + + public class CommandHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + + public CommandHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task Handle(Command request, CancellationToken cancellationToken) + { + var locker = await _context.Lockers.FirstOrDefaultAsync( + x => x.Id.Equals(request.LockerId), cancellationToken); + + if (locker is null) + { + throw new KeyNotFoundException("Locker does not exist."); + } + + var duplicateLocker = await _context.Lockers.FirstOrDefaultAsync( + x => x.Name.Equals(request.Name), cancellationToken); + + if (duplicateLocker is not null && !duplicateLocker.Equals(locker)) + { + throw new ConflictException("New locker name already exists."); + } + + if (locker.NumberOfFolders > request.Capacity) + { + throw new ConflictException("New capacity cannot be less than current number of folders."); + } + + locker.Name = request.Name; + locker.Description = request.Description; + locker.Capacity = request.Capacity; + + var result = _context.Lockers.Update(locker); + await _context.SaveChangesAsync(cancellationToken); + return _mapper.Map(result.Entity); + } + } } \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Lockers/Commands/UpdateLockerTests.cs b/tests/Application.Tests.Integration/Lockers/Commands/UpdateLockerTests.cs new file mode 100644 index 00000000..67031617 --- /dev/null +++ b/tests/Application.Tests.Integration/Lockers/Commands/UpdateLockerTests.cs @@ -0,0 +1,132 @@ +using Application.Common.Exceptions; +using Application.Lockers.Commands; +using Domain.Entities; +using FluentAssertions; +using Xunit; + +namespace Application.Tests.Integration.Lockers.Commands; + +public class UpdateLockerTests : BaseClassFixture +{ + public UpdateLockerTests(CustomApiFactory apiFactory) : base(apiFactory) + { + } + + [Fact] + public async Task ShouldUpdateLocker_WhenUpdateDetailsAreValid() + { + // Arrange + var department = CreateDepartment(); + var locker = CreateLocker(); + var room = CreateRoom(department, locker); + await AddAsync(room); + + var command = new UpdateLocker.Command() + { + LockerId = locker.Id, + Name = "Something else", + Capacity = 6, + Description = "ehehe", + }; + + // Act + var result = await SendAsync(command); + + // Assert + result.Id.Should().Be(locker.Id); + result.Name.Should().Be(command.Name); + result.Capacity.Should().Be(command.Capacity); + result.Description.Should().Be(command.Description); + + // Cleanup + Remove(locker); + Remove(room); + Remove(await FindAsync(department.Id)); + } + + [Fact] + public async Task ShouldThrowKeyNotFoundException_WhenThatLockerDoesNotExist() + { + // Arrange + var command = new UpdateLocker.Command() + { + LockerId = Guid.NewGuid(), + Name = "Something else", + Capacity = 6, + Description = "ehehe", + }; + + // Act + var result = async () => await SendAsync(command); + + // Assert + await result.Should().ThrowAsync() + .WithMessage("Locker does not exist."); + } + + [Fact] + public async Task ShouldThrowConflictException_WhenNewLockerNameHasAlreadyExistedInThatRoom() + { + // Arrange + var department = CreateDepartment(); + var duplicateNameLocker = CreateLocker(); + var locker = CreateLocker(); + var room = CreateRoom(department, duplicateNameLocker, locker); + await AddAsync(room); + + var command = new UpdateLocker.Command() + { + LockerId = locker.Id, + Name = duplicateNameLocker.Name, + Capacity = 6, + Description = "ehehe", + }; + + // Act + var result = async () => await SendAsync(command); + + // Assert + await result.Should().ThrowAsync() + .WithMessage("New locker name already exists."); + + // Cleanup + Remove(locker); + Remove(room); + Remove(await FindAsync(department.Id)); + } + + [Fact] + public async Task ShouldThrowConflictException_WhenNewCapacityIsLessThanCurrentNumberOfFolders() + { + // Arrange + var department = CreateDepartment(); + var folder1 = CreateFolder(); + var folder2 = CreateFolder(); + var locker = CreateLocker(folder1, folder2); + locker.Capacity = 3; + var room = CreateRoom(department, locker); + await AddAsync(room); + + var command = new UpdateLocker.Command() + { + LockerId = locker.Id, + Name = "Something else", + Capacity = 1, + Description = "ehehe", + }; + + // Act + var result = async () => await SendAsync(command); + + // Assert + await result.Should().ThrowAsync() + .WithMessage("New capacity cannot be less than current number of folders."); + + // Cleanup + Remove(folder1); + Remove(folder2); + Remove(locker); + Remove(room); + Remove(await FindAsync(department.Id)); + } +} \ No newline at end of file