diff --git a/src/Application/Rooms/Commands/UpdateRoom.cs b/src/Application/Rooms/Commands/UpdateRoom.cs index 3dc20417..ade660fe 100644 --- a/src/Application/Rooms/Commands/UpdateRoom.cs +++ b/src/Application/Rooms/Commands/UpdateRoom.cs @@ -1,15 +1,97 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities.Physical; +using FluentValidation; using MediatR; +using Microsoft.EntityFrameworkCore; namespace Application.Rooms.Commands; public class UpdateRoom { + public class Validator : AbstractValidator + { + public Validator() + { + RuleLevelCascadeMode = CascadeMode.Stop; + + RuleFor(x => x.Name) + .NotEmpty().WithMessage("Name can not be empty.") + .MaximumLength(64).WithMessage("Name can not exceed 64 characters."); + + RuleFor(x => x.Capacity) + .NotEmpty().WithMessage("Capacity can not be empty."); + + RuleFor(x => x.Description) + .MaximumLength(256).WithMessage("Description can not exceed 256 characters."); + } + } public record Command : IRequest { public Guid RoomId { get; init; } - public string Name { get; set; } = null!; + public string Name { get; init; } = null!; 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 room = await _context.Rooms + .FirstOrDefaultAsync(x => x.Id.Equals(request.RoomId), cancellationToken: cancellationToken); + + if (room is null) + { + throw new KeyNotFoundException("Room does not exist."); + } + + var nameExisted = await _context.Rooms.AnyAsync(x => x.Name + .ToLower().Equals(request.Name.ToLower()) + && x.Id != room.Id + , cancellationToken: cancellationToken); + + if (nameExisted) + { + throw new ConflictException("Name has already exists."); + } + + if (request.Capacity < room.NumberOfLockers) + { + throw new ConflictException("New capacity cannot be less than current number of lockers."); + } + + var updatedRoom = new Room + { + Id = room.Id, + Name = request.Name, + Description = request.Description, + Staff = room.Staff, + Department = room.Department, + DepartmentId = room.DepartmentId, + Capacity = request.Capacity, + NumberOfLockers = room.NumberOfLockers, + IsAvailable = room.IsAvailable, + Lockers = room.Lockers + }; + + _context.Rooms.Entry(room).State = EntityState.Detached; + _context.Rooms.Entry(updatedRoom).State = EntityState.Modified; + + await _context.SaveChangesAsync(cancellationToken); + + return _mapper.Map(updatedRoom); + } + } } \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Rooms/Commands/UpdateRoomTests.cs b/tests/Application.Tests.Integration/Rooms/Commands/UpdateRoomTests.cs new file mode 100644 index 00000000..25f0b5b1 --- /dev/null +++ b/tests/Application.Tests.Integration/Rooms/Commands/UpdateRoomTests.cs @@ -0,0 +1,128 @@ +using Application.Common.Exceptions; +using Application.Rooms.Commands; +using Domain.Entities; +using FluentAssertions; +using Xunit; + +namespace Application.Tests.Integration.Rooms.Commands; + +public class UpdateRoomTests : BaseClassFixture +{ + public UpdateRoomTests(CustomApiFactory apiFactory) : base(apiFactory) + { + } + + [Fact] + public async Task ShouldUpdateRoom_WhenUpdateDetailsAreValid() + { + // Act + var department = CreateDepartment(); + var room = CreateRoom(department); + await AddAsync(room); + + var command = new UpdateRoom.Command() + { + RoomId = room.Id, + Name = "Something else", + Description = "Description else", + Capacity = 6, + }; + + // Act + var result = await SendAsync(command); + + // Assert + result.Name.Should().Be(command.Name); + result.Description.Should().Be(command.Description); + result.Capacity.Should().Be(command.Capacity); + + // Cleanup + Remove(room); + Remove(await FindAsync(department.Id)); + } + + [Fact] + public async Task ShouldThrowKeyNotFoundException_WhenRoomDoesNotExist() + { + // Act + var command = new UpdateRoom.Command() + { + RoomId = Guid.NewGuid(), + Name = "Something else", + Description = "Description else", + Capacity = 6, + }; + + // Act + var action = async () => await SendAsync(command); + + // Assert + await action.Should().ThrowAsync() + .WithMessage("Room does not exist."); + } + + [Fact] + public async Task ShouldThrowConflictException_WhenNewCapacityIsLowerThanNumberOfCurrentLockers() + { + // Act + var department = CreateDepartment(); + var locker1 = CreateLocker(); + var locker2 = CreateLocker(); + var room = CreateRoom(department, locker1, locker2); + await AddAsync(room); + + var command = new UpdateRoom.Command() + { + RoomId = room.Id, + Name = "Something else", + Description = "Description else", + Capacity = 1, + }; + + // Act + var action = async () => await SendAsync(command); + + // Assert + await action.Should().ThrowAsync() + .WithMessage("New capacity cannot be less than current number of lockers."); + + // Cleanup + Remove(locker1); + Remove(locker2); + Remove(room); + Remove(await FindAsync(department.Id)); + } + + [Fact] + public async Task ShouldThrowConflictException_WhenNewNameHasAlreadyExisted() + { + // Act + var department1 = CreateDepartment(); + var department2 = CreateDepartment(); + var existedNameRoom = CreateRoom(department1); + var room = CreateRoom(department2); + await AddAsync(existedNameRoom); + await AddAsync(room); + + var command = new UpdateRoom.Command() + { + RoomId = room.Id, + Name = existedNameRoom.Name, + Description = "Description else", + Capacity = 1, + }; + + // Act + var action = async () => await SendAsync(command); + + // Assert + await action.Should().ThrowAsync() + .WithMessage("Name has already exists."); + + // Cleanup + Remove(existedNameRoom); + Remove(room); + Remove(await FindAsync(department1.Id)); + Remove(await FindAsync(department2.Id)); + } +} \ No newline at end of file