diff --git a/src/Library.API/Infrastructure/Services/BookOrderService.cs b/src/Library.API/Infrastructure/Services/BookOrderService.cs index 22192e2..836a68f 100644 --- a/src/Library.API/Infrastructure/Services/BookOrderService.cs +++ b/src/Library.API/Infrastructure/Services/BookOrderService.cs @@ -81,14 +81,14 @@ public async Task AddAsync(BookOrder bookOrder) } catch (DbUpdateException ex) { - _logger.LogError(ex, "Error occurred while creating book order {BookOrderId}.", bookOrder.Id); + _logger.LogError(ex, "Error occurred while saving book order."); return ServiceResponse.Fail( "An error occurred while saving the book order", ErrorType.DatabaseError); } catch (Exception ex) { - _logger.LogError(ex, "Error occurred while creating book order {BookOrderId}.", bookOrder.Id); + _logger.LogError(ex, "Error occurred while adding book order."); return ServiceResponse.Fail( "An unexpected error occurred while processing your request", ErrorType.Unknown); diff --git a/tests/Library.API.Tests/Extensions/EnumExtensionsTests.cs b/tests/Library.API.Tests/Extensions/EnumExtensionsTests.cs new file mode 100644 index 0000000..af6bd36 --- /dev/null +++ b/tests/Library.API.Tests/Extensions/EnumExtensionsTests.cs @@ -0,0 +1,56 @@ +using System.ComponentModel; + +using FluentAssertions; + +using Library.API.Extensions; + +namespace Library.API.Tests.Extensions; + +public enum TestEnumWithDescription +{ + [Description("First Value")] + First, + [Description("Second Value")] + Second, + [Description("Third Value")] + Third +} + +public enum TestEnumWithoutDescription +{ + First, + Second, + Third +} + +public class EnumExtensionsTests +{ + [Fact] + public void ToDescription_ShouldReturnDescriptionAttribute_WhenEnumHasDescription() + { + // Arrange + var firstDescription = TestEnumWithDescription.First.ToDescription(); + var secondDescription = TestEnumWithDescription.Second.ToDescription(); + var thirdDescription = TestEnumWithDescription.Third.ToDescription(); + + // Assert + firstDescription.Should().Be("First Value"); + secondDescription.Should().Be("Second Value"); + thirdDescription.Should().Be("Third Value"); + } + + [Fact] + public void ToDescription_ShouldReturnEnumName_WhenEnumHasNoDescription() + { + // Arrange + var firstDescription = TestEnumWithoutDescription.First.ToDescription(); + var secondDescription = TestEnumWithoutDescription.Second.ToDescription(); + var thirdDescription = TestEnumWithoutDescription.Third.ToDescription(); + + // Assert + firstDescription.Should().Be("First"); + secondDescription.Should().Be("Second"); + thirdDescription.Should().Be("Third"); + } +} + diff --git a/tests/Library.API.Tests/Factories/ConsumerRegistrationBuilderTests.cs b/tests/Library.API.Tests/Factories/ConsumerRegistrationBuilderTests.cs new file mode 100644 index 0000000..8b7c5de --- /dev/null +++ b/tests/Library.API.Tests/Factories/ConsumerRegistrationBuilderTests.cs @@ -0,0 +1,39 @@ +using FluentAssertions; +using MassTransit; +using Moq; + +using Library.API.Infrastructure.Factories; + +namespace Library.API.Tests.Factories; + +public class ConsumerRegistrationBuilderTests +{ + [Fact] + public void Add_ShouldSetSuffix_WhenConsumerIsRegistered() + { + // Arrange + var expectedSuffix = "test-suffix"; + var _configuratorMock = new Mock(); + var _builder = new ConsumerRegistrationBuilder(_configuratorMock.Object, expectedSuffix); + + // Act + _builder.Add(); + + // Assert + SuffixedConsumerDefinition.CurrentSuffix.Should().Be(expectedSuffix); + } + + // Test consumer class for our tests + private class TestConsumer : IConsumer + { + public Task Consume(ConsumeContext context) + { + return Task.CompletedTask; + } + } + + // Test message class for our consumer + private class TestMessage + { + } +} \ No newline at end of file diff --git a/tests/Library.API.Tests/Helpers/TestDataHelper.cs b/tests/Library.API.Tests/Helpers/TestDataHelper.cs index 6d63db4..71a496a 100644 --- a/tests/Library.API.Tests/Helpers/TestDataHelper.cs +++ b/tests/Library.API.Tests/Helpers/TestDataHelper.cs @@ -40,5 +40,25 @@ public static class TestDataHelper AuthorId = 2, ReleaseDate = new DateTime(2010, 1, 1), } - ]; -} \ No newline at end of file + ]; + + public static List BookOrders => + [ + new BookOrder + { + Id = 1, + CheckoutDate = new DateTime(2025, 5, 1), + Status = BookOrderStatus.Placed, + Items = [ + new BookOrderItem + { + Id = 1, + BookId = 1, + Book = Books[0], + Quantity = 1, + BookOrderId = 1 + } + ] + } + ]; +} diff --git a/tests/Library.API.Tests/Mapping/BookOrderMappingTests.cs b/tests/Library.API.Tests/Mapping/BookOrderMappingTests.cs new file mode 100644 index 0000000..85cfe7d --- /dev/null +++ b/tests/Library.API.Tests/Mapping/BookOrderMappingTests.cs @@ -0,0 +1,81 @@ +using FluentAssertions; + +using Library.API.Domain.Models; +using Library.API.DTOs; +using Library.API.Extensions; + +namespace Library.API.Tests.Mapping; + +public class BookOrderMappingTests : MappingTestsBase +{ + [Fact] + public void Should_Map_BookOrder_To_BookOrderDto() + { + // Arrange + var order = new BookOrder + { + Id = 1, + Status = BookOrderStatus.Placed, + CheckoutDate = DateTime.UtcNow, + Items = new List + { + new BookOrderItem + { + Id = 1, + BookId = 1, + Book = new Book + { + Id = 1, + Title = "Book 1", + Description = "Book 1 is a book about software engineering." + }, + Quantity = 1 + } + } + }; + + // Act + var result = Mapper.Map(order); + + // Assert + result.Should().NotBeNull(); + result.Id.Should().Be(order.Id); + result.Status.Should().Be(order.Status.ToDescription()); + result.CheckoutDate.Should().Be(order.CheckoutDate); + result.Items.Should().HaveCount(order.Items.Count); + result.Items[0].BookId.Should().Be(order.Items[0].BookId); + result.Items[0].Quantity.Should().Be(order.Items[0].Quantity); + result.Items[0].Title.Should().Be(order.Items[0].Book.Title); + } + + [Fact] + public void Should_Map_SaveBookOrderDto_To_BookOrder() + { + // Arrange + var dto = new SaveBookOrderDto + { + Items = new List + { + new SaveBookOrderItemDto + { + BookId = 1, + Quantity = 1, + } + } + }; + + // Act + var result = Mapper.Map(dto); + + // Assert + result.Should().NotBeNull(); + result.Id.Should().Be(0); + result.Status.Should().Be(BookOrderStatus.Placed); + result.CheckoutDate.Should().Be(default); + result.Items.Should().HaveCount(dto.Items.Count); + result.Items[0].Book.Should().BeNull(); + result.Items[0].BookOrderId.Should().Be(0); + result.Items[0].BookId.Should().Be(dto.Items[0].BookId); + result.Items[0].Quantity.Should().Be(dto.Items[0].Quantity); + } +} diff --git a/tests/Library.API.Tests/Mapping/MappingTestsBase.cs b/tests/Library.API.Tests/Mapping/MappingTestsBase.cs index 8b974b4..53e827e 100644 --- a/tests/Library.API.Tests/Mapping/MappingTestsBase.cs +++ b/tests/Library.API.Tests/Mapping/MappingTestsBase.cs @@ -10,9 +10,11 @@ public class MappingTestsBase public MappingTestsBase() { - var configuration = new MapperConfiguration(cfg => { + var configuration = new MapperConfiguration(cfg => + { cfg.AddProfile(); cfg.AddProfile(); + cfg.AddProfile(); }); Mapper = configuration.CreateMapper(); } diff --git a/tests/Library.API.Tests/Mapping/OrderPlacedEventTests.cs b/tests/Library.API.Tests/Mapping/OrderPlacedEventTests.cs new file mode 100644 index 0000000..76fbb38 --- /dev/null +++ b/tests/Library.API.Tests/Mapping/OrderPlacedEventTests.cs @@ -0,0 +1,49 @@ +using FluentAssertions; + +using Library.API.Domain.Models; +using Library.API.Extensions; +using Library.Events.Messages; + +namespace Library.API.Tests.Mapping; + +public class OrderPlacedEventTests : MappingTestsBase +{ + [Fact] + public void Should_Map_BookOrder_To_OrderPlacedEvent() + { + // Arrange + var order = new BookOrder + { + Id = 1, + Status = BookOrderStatus.Placed, + CheckoutDate = DateTime.UtcNow, + Items = new List + { + new BookOrderItem + { + Id = 1, + Book = new Book + { + Id = 1, + Title = "Book 1", + Description = "Book 1 is a book about software engineering." + }, + BookId = 1, + Quantity = 1 + } + } + }; + + // Act + var result = Mapper.Map(order); + + // Assert + result.Should().NotBeNull(); + result.OrderId.Should().Be(order.Id); + result.Status.Should().Be(order.Status.ToDescription()); + result.CheckoutDate.Should().Be(order.CheckoutDate); + result.Items.Should().HaveCount(order.Items.Count); + result.Items[0].BookId.Should().Be(order.Items[0].BookId); + result.Items[0].Quantity.Should().Be(order.Items[0].Quantity); + } +} \ No newline at end of file diff --git a/tests/Library.API.Tests/Services/BookOrderServiceTests.cs b/tests/Library.API.Tests/Services/BookOrderServiceTests.cs new file mode 100644 index 0000000..f002c5f --- /dev/null +++ b/tests/Library.API.Tests/Services/BookOrderServiceTests.cs @@ -0,0 +1,428 @@ +using System.Diagnostics; + +using AutoMapper; + +using FluentAssertions; + +using Library.API.Domain.Models; +using Library.API.Domain.Repositories; +using Library.API.Domain.Services.Communication; +using Library.API.Infrastructure.Services; +using Library.API.Tests.Helpers; +using Library.Events.Messages; + +using MassTransit; + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +using Moq; + +namespace Library.API.Tests.Services; + +public class BookOrderServiceTests +{ + private readonly Mock _bookOrderRepositoryMock; + private readonly Mock _unitOfWorkMock; + private readonly Mock> _loggerMock; + private readonly ActivitySource _activitySource; + private readonly Mock _mapperMock; + private readonly Mock _publishEndpointMock; + + public BookOrderServiceTests() + { + _bookOrderRepositoryMock = new Mock(); + _unitOfWorkMock = new Mock(); + _loggerMock = new Mock>(); + _activitySource = new ActivitySource("Library.API.Tests"); + _mapperMock = new Mock(); + _publishEndpointMock = new Mock(); + } + + private BookOrderService CreateService() => + new BookOrderService( + _bookOrderRepositoryMock.Object, + _unitOfWorkMock.Object, + _loggerMock.Object, + _activitySource, + _mapperMock.Object, + _publishEndpointMock.Object); + + [Fact] + public async Task FindByIdAsync_ShouldReturnBookOrder_WhenSuccessful() + { + // Arrange + var bookOrderService = CreateService(); + var bookOrderId = 1; + var checkoutDate = new DateTime(2025, 5, 1); + var bookOrder = new BookOrder + { + Id = bookOrderId, + CheckoutDate = checkoutDate, + Status = BookOrderStatus.Placed, + Items = + [ + new BookOrderItem + { + Id = 1, + Book = TestDataHelper.Books[0], + Quantity = 1 + } + ] + }; + + _bookOrderRepositoryMock.Setup(repo => repo.FindByIdAsync(bookOrderId)).ReturnsAsync(bookOrder); + + // Act + var result = await bookOrderService.FindByIdAsync(bookOrderId); + + // Assert + result.Should().NotBeNull(); + result.Success.Should().BeTrue(); + result.Model.Should().NotBeNull(); + result.Model.Should().BeEquivalentTo(bookOrder); + result.Error.Should().BeNull(); + result.Message.Should().BeNull(); + + _bookOrderRepositoryMock.Verify(repo => repo.FindByIdAsync(bookOrderId), Times.Once); + } + + [Fact] + public async Task FindByIdAsync_ShouldReturnError_WhenBookOrderNotFound() + { + // Arrange + var bookOrderService = CreateService(); + var bookOrderId = 1; + _bookOrderRepositoryMock.Setup(repo => repo.FindByIdAsync(bookOrderId)).ReturnsAsync((BookOrder?)null); + + // Act + var result = await bookOrderService.FindByIdAsync(bookOrderId); + + // Assert + result.Should().NotBeNull(); + result.Success.Should().BeFalse(); + result.Model.Should().BeNull(); + result.Error.Should().NotBeNull(); + result.Error.Should().Be(ErrorType.NotFound); + result.Message.Should().NotBeNull(); + result.Message.Should().Be($"Book order with id {bookOrderId} was not found"); + + _bookOrderRepositoryMock.Verify(repo => repo.FindByIdAsync(bookOrderId), Times.Once); + } + + [Fact] + public async Task FindByIdAsync_ShouldReturnError_WhenRepositoryFails() + { + // Arrange + var bookOrderService = CreateService(); + var bookOrderId = 1; + _bookOrderRepositoryMock.Setup(repo => repo.FindByIdAsync(bookOrderId)).ThrowsAsync(new DbUpdateException("Repository error")); + + // Act + var result = await bookOrderService.FindByIdAsync(bookOrderId); + + // Assert + result.Should().NotBeNull(); + result.Success.Should().BeFalse(); + result.Model.Should().BeNull(); + result.Error.Should().NotBeNull(); + result.Error.Should().Be(ErrorType.DatabaseError); + result.Message.Should().NotBeNull(); + result.Message.Should().Be("An error occurred while retrieving the book order"); + + _bookOrderRepositoryMock.Verify(repo => repo.FindByIdAsync(bookOrderId), Times.Once); + _loggerMock.Verify( + x => x.Log( + LogLevel.Error, + It.IsAny(), + It.Is((v, t) => v!.ToString()!.Contains("Error occurred while finding book order")), + It.IsAny(), + It.IsAny>() + ), + Times.Once + ); + } + + [Fact] + public async Task AddAsync_ShouldReturnBookOrder_WhenSuccessful() + { + // Arrange + var bookOrderService = CreateService(); + var bookOrder = TestDataHelper.BookOrders[0]; + var bookOrderId = bookOrder.Id; + + _bookOrderRepositoryMock.Setup(repo => repo.AddAsync(bookOrder)).Returns(Task.CompletedTask); + _bookOrderRepositoryMock.Setup(repo => repo.FindByIdAsync(bookOrderId)).ReturnsAsync(bookOrder); + _unitOfWorkMock.Setup(uow => uow.CompleteAsync()).Returns(Task.CompletedTask); + _publishEndpointMock.Setup(publishEndpoint => publishEndpoint.Publish(It.IsAny(), CancellationToken.None)).Returns(Task.CompletedTask); + + // Act + var result = await bookOrderService.AddAsync(bookOrder); + + // Assert + result.Should().NotBeNull(); + result.Success.Should().BeTrue(); + result.Model.Should().NotBeNull(); + result.Model.Should().BeEquivalentTo(bookOrder); + result.Error.Should().BeNull(); + result.Message.Should().BeNull(); + + _bookOrderRepositoryMock.Verify(repo => repo.AddAsync(bookOrder), Times.Once); + _unitOfWorkMock.Verify(uow => uow.CompleteAsync(), Times.Once); + _publishEndpointMock.Verify(publishEndpoint => publishEndpoint.Publish(It.IsAny(), CancellationToken.None), Times.Once); + } + + [Fact] + public async Task AddAsync_ShouldReturnError_WhenRepositoryFails() + { + // Arrange + var bookOrderService = CreateService(); + var bookOrder = TestDataHelper.BookOrders[0]; + var bookOrderId = bookOrder.Id; + + _bookOrderRepositoryMock.Setup(repo => repo.AddAsync(bookOrder)).ThrowsAsync(new DbUpdateException("Repository error")); + + // Act + var result = await bookOrderService.AddAsync(bookOrder); + + // Assert + result.Should().NotBeNull(); + result.Success.Should().BeFalse(); + result.Model.Should().BeNull(); + result.Error.Should().NotBeNull(); + result.Error.Should().Be(ErrorType.DatabaseError); + result.Message.Should().NotBeNull(); + result.Message.Should().Be("An error occurred while saving the book order"); + + _bookOrderRepositoryMock.Verify(repo => repo.AddAsync(bookOrder), Times.Once); + _unitOfWorkMock.Verify(uow => uow.CompleteAsync(), Times.Never); + _publishEndpointMock.Verify(publishEndpoint => publishEndpoint.Publish(It.IsAny(), CancellationToken.None), Times.Never); + _loggerMock.Verify( + x => x.Log( + LogLevel.Error, + It.IsAny(), + It.Is((v, t) => v!.ToString()!.Contains("Error occurred while saving book order.")), + It.IsAny(), + It.IsAny>() + ), + Times.Once + ); + } + + [Fact] + public async Task AddAsync_ShouldReturnError_WhenUnitOfWorkFails() + { + // Arrange + var bookOrderService = CreateService(); + var bookOrder = TestDataHelper.BookOrders[0]; + var bookOrderId = bookOrder.Id; + + _bookOrderRepositoryMock.Setup(repo => repo.AddAsync(bookOrder)).Returns(Task.CompletedTask); + _bookOrderRepositoryMock.Setup(repo => repo.FindByIdAsync(bookOrderId)).ReturnsAsync(bookOrder); + _unitOfWorkMock.Setup(uow => uow.CompleteAsync()).ThrowsAsync(new DbUpdateException("Unit of work error")); + + // Act + var result = await bookOrderService.AddAsync(bookOrder); + + // Assert + result.Should().NotBeNull(); + result.Success.Should().BeFalse(); + result.Model.Should().BeNull(); + result.Error.Should().NotBeNull(); + result.Error.Should().Be(ErrorType.DatabaseError); + result.Message.Should().NotBeNull(); + result.Message.Should().Be("An error occurred while saving the book order"); + + _bookOrderRepositoryMock.Verify(repo => repo.AddAsync(bookOrder), Times.Once); + _unitOfWorkMock.Verify(uow => uow.CompleteAsync(), Times.Once); + _publishEndpointMock.Verify(publishEndpoint => publishEndpoint.Publish(It.IsAny(), CancellationToken.None), Times.Never); + } + + [Fact] + public async Task AddAsync_ShouldReturnError_WhenUnexpectedErrorOccurs() + { + // Arrange + var bookOrderService = CreateService(); + var bookOrder = TestDataHelper.BookOrders[0]; + var bookOrderId = bookOrder.Id; + + _bookOrderRepositoryMock.Setup(repo => repo.AddAsync(bookOrder)).Returns(Task.CompletedTask); + _bookOrderRepositoryMock.Setup(repo => repo.FindByIdAsync(bookOrderId)).ReturnsAsync(bookOrder); + _unitOfWorkMock.Setup(uow => uow.CompleteAsync()).ThrowsAsync(new Exception("Unexpected error")); + + // Act + var result = await bookOrderService.AddAsync(bookOrder); + + // Assert + result.Should().NotBeNull(); + result.Success.Should().BeFalse(); + result.Model.Should().BeNull(); + result.Error.Should().NotBeNull(); + result.Error.Should().Be(ErrorType.Unknown); + result.Message.Should().NotBeNull(); + result.Message.Should().Be("An unexpected error occurred while processing your request"); + + _bookOrderRepositoryMock.Verify(repo => repo.AddAsync(bookOrder), Times.Once); + _unitOfWorkMock.Verify(uow => uow.CompleteAsync(), Times.Once); + _publishEndpointMock.Verify(publishEndpoint => publishEndpoint.Publish(It.IsAny(), CancellationToken.None), Times.Never); + } + + [Fact] + public async Task AddAsync_ShouldReturnError_WhenBookOrderItemsAreEmpty() + { + // Arrange + var bookOrderService = CreateService(); + var bookOrder = TestDataHelper.BookOrders[0]; + bookOrder.Items = []; + + // Act + var result = await bookOrderService.AddAsync(bookOrder); + + // Assert + result.Should().NotBeNull(); + result.Success.Should().BeFalse(); + result.Model.Should().BeNull(); + result.Error.Should().NotBeNull(); + result.Error.Should().Be(ErrorType.ValidationError); + result.Message.Should().NotBeNull(); + result.Message.Should().Be("Book order must have at least one item"); + + _bookOrderRepositoryMock.Verify(repo => repo.AddAsync(bookOrder), Times.Never); + _unitOfWorkMock.Verify(uow => uow.CompleteAsync(), Times.Never); + _publishEndpointMock.Verify(publishEndpoint => publishEndpoint.Publish(It.IsAny(), CancellationToken.None), Times.Never); + } + + [Fact] + public async Task AddAsync_ShouldReturnError_WhenPublishFails() + { + // Arrange + var bookOrderService = CreateService(); + var bookOrder = TestDataHelper.BookOrders[0]; + var bookOrderId = bookOrder.Id; + + _bookOrderRepositoryMock.Setup(repo => repo.AddAsync(bookOrder)).Returns(Task.CompletedTask); + _bookOrderRepositoryMock.Setup(repo => repo.FindByIdAsync(bookOrderId)).ReturnsAsync(bookOrder); + _unitOfWorkMock.Setup(uow => uow.CompleteAsync()).Returns(Task.CompletedTask); + _publishEndpointMock + .Setup(publishEndpoint => publishEndpoint.Publish(It.IsAny(), CancellationToken.None)) + .ThrowsAsync(new MessageException()); + + // Act + var result = await bookOrderService.AddAsync(bookOrder); + + // Assert + result.Should().NotBeNull(); + result.Success.Should().BeFalse(); + result.Model.Should().BeNull(); + result.Error.Should().NotBeNull(); + result.Error.Should().Be(ErrorType.MessageBrokerError); + result.Message.Should().NotBeNull(); + result.Message.Should().Be("An error occurred while publishing the order event"); + + _bookOrderRepositoryMock.Verify(repo => repo.AddAsync(bookOrder), Times.Once); + _bookOrderRepositoryMock.Verify(repo => repo.FindByIdAsync(bookOrderId), Times.Once); + _unitOfWorkMock.Verify(uow => uow.CompleteAsync(), Times.Once); + _publishEndpointMock.Verify(publishEndpoint => publishEndpoint.Publish(It.IsAny(), CancellationToken.None), Times.Once); + _loggerMock.Verify( + x => x.Log( + LogLevel.Error, + It.IsAny(), + It.Is((v, t) => v!.ToString()!.Contains("Error occurred while publishing order placed event")), + It.IsAny(), + It.IsAny>() + ), + Times.Once + ); + } + + [Fact] + public async Task UpdateStatusAsync_ShouldReturnBookOrder_WhenSuccessful() + { + // Arrange + var bookOrderService = CreateService(); + var bookOrder = TestDataHelper.BookOrders[0]; + var bookOrderId = bookOrder.Id; + var newStatus = BookOrderStatus.Processing; + + _bookOrderRepositoryMock.Setup(repo => repo.FindByIdAsync(bookOrderId)).ReturnsAsync(bookOrder); + _unitOfWorkMock.Setup(uow => uow.CompleteAsync()).Returns(Task.CompletedTask); + + // Act + var result = await bookOrderService.UpdateStatusAsync(bookOrderId, newStatus); + + // Assert + result.Should().NotBeNull(); + result.Success.Should().BeTrue(); + result.Model.Should().NotBeNull(); + result.Model.Status.Should().Be(newStatus); + result.Error.Should().BeNull(); + result.Message.Should().BeNull(); + + _bookOrderRepositoryMock.Verify(repo => repo.FindByIdAsync(bookOrderId), Times.Once); + _unitOfWorkMock.Verify(uow => uow.CompleteAsync(), Times.Once); + } + + [Fact] + public async Task UpdateStatusAsync_ShouldReturnError_WhenBookOrderNotFound() + { + // Arrange + var bookOrderService = CreateService(); + var bookOrderId = 999; + var newStatus = BookOrderStatus.Processing; + + _bookOrderRepositoryMock.Setup(repo => repo.FindByIdAsync(bookOrderId)).ReturnsAsync((BookOrder?)null); + + // Act + var result = await bookOrderService.UpdateStatusAsync(bookOrderId, newStatus); + + // Assert + result.Should().NotBeNull(); + result.Success.Should().BeFalse(); + result.Model.Should().BeNull(); + result.Error.Should().NotBeNull(); + result.Error.Should().Be(ErrorType.NotFound); + result.Message.Should().NotBeNull(); + result.Message.Should().Be($"Book order with id {bookOrderId} was not found"); + + _bookOrderRepositoryMock.Verify(repo => repo.FindByIdAsync(bookOrderId), Times.Once); + _unitOfWorkMock.Verify(uow => uow.CompleteAsync(), Times.Never); + } + + [Fact] + public async Task UpdateStatusAsync_ShouldReturnError_WhenRepositoryFails() + { + // Arrange + var bookOrderService = CreateService(); + var bookOrder = TestDataHelper.BookOrders[0]; + var bookOrderId = bookOrder.Id; + var newStatus = BookOrderStatus.Processing; + + _bookOrderRepositoryMock.Setup(repo => repo.FindByIdAsync(bookOrderId)).ReturnsAsync(bookOrder); + _unitOfWorkMock.Setup(uow => uow.CompleteAsync()).ThrowsAsync(new Exception("Repository error")); + + // Act + var result = await bookOrderService.UpdateStatusAsync(bookOrderId, newStatus); + + // Assert + result.Should().NotBeNull(); + result.Success.Should().BeFalse(); + result.Model.Should().BeNull(); + result.Error.Should().NotBeNull(); + result.Error.Should().Be(ErrorType.DatabaseError); + result.Message.Should().NotBeNull(); + result.Message.Should().Be("An error occurred while updating the book order status"); + + _bookOrderRepositoryMock.Verify(repo => repo.FindByIdAsync(bookOrderId), Times.Once); + _unitOfWorkMock.Verify(uow => uow.CompleteAsync(), Times.Once); + _loggerMock.Verify( + x => x.Log( + LogLevel.Error, + It.IsAny(), + It.Is((v, t) => v!.ToString()!.Contains("Error occurred while updating status for book order")), + It.IsAny(), + It.IsAny>() + ), + Times.Once + ); + } +}