diff --git a/LibraryManagement.Api.Tests.Unit/Services/Foundations/Readers/ReaderServiceTests.Exceptions.RetrieveAll.cs b/LibraryManagement.Api.Tests.Unit/Services/Foundations/Readers/ReaderServiceTests.Exceptions.RetrieveAll.cs new file mode 100644 index 0000000..dba93ee --- /dev/null +++ b/LibraryManagement.Api.Tests.Unit/Services/Foundations/Readers/ReaderServiceTests.Exceptions.RetrieveAll.cs @@ -0,0 +1,94 @@ +//----------------------------------------------------------- +// Copyright (c) Coalition of Good-Hearted Engineers +// Free To Use To Build Reliable Library Management Solutions +//----------------------------------------------------------- + +using FluentAssertions; +using LibraryManagement.Api.Models.Foundations.Readers.Exceptions; +using Microsoft.Data.SqlClient; +using Moq; + +namespace LibraryManagement.Api.Tests.Unit.Services.Foundations.Readers +{ + public partial class ReaderServiceTests + { + [Fact] + public void ShouldThrowCriticalDependencyExceptionOnRetrieveAllWhenSqlExceptionOccursAndLogIt() + { + // given + SqlException sqlException = GetSqlError(); + + var failedReaderStorageException = + new FailedReaderStorageException(sqlException); + + var expectedReaderDependencyException = + new ReaderDependencyException(failedReaderStorageException); + + this.storageBrokerMock.Setup(broker => + broker.SelectAllReaders()).Throws(sqlException); + + // when + Action retrieveAllReadersAction = () => + this.readerService.RetrieveAllReaders(); + + ReaderDependencyException actualReaderDependencyException = + Assert.Throws(retrieveAllReadersAction); + + // then + actualReaderDependencyException.Should() + .BeEquivalentTo(expectedReaderDependencyException); + + this.storageBrokerMock.Verify(broker => + broker.SelectAllReaders(), + Times.Once()); + + this.loggingBrokerMock.Verify(broker => + broker.LogCritical(It.Is(SameExceptionAs( + expectedReaderDependencyException))), + Times.Once); + + this.storageBrokerMock.VerifyNoOtherCalls(); + this.loggingBrokerMock.VerifyNoOtherCalls(); + } + + [Fact] + public void ShouldThrowServiceExceptionOnRetrieveAllIfServiceErrorOccursAndLogItAsync() + { + // given + string exceptionMessage = GetRandomString(); + var serverException = new Exception(exceptionMessage); + + var failedReaderServiceException = + new FailedReaderServiceException(serverException); + + var expectedReaderServiceException = + new ReaderServiceException(failedReaderServiceException); + + this.storageBrokerMock.Setup(broker => + broker.SelectAllReaders()).Throws(serverException); + + // when + Action retrieveAllReaderActions = () => + this.readerService.RetrieveAllReaders(); + + ReaderServiceException actualReaderServiceException = + Assert.Throws(retrieveAllReaderActions); + + // then + actualReaderServiceException.Should() + .BeEquivalentTo(expectedReaderServiceException); + + this.storageBrokerMock.Verify(broker => + broker.SelectAllReaders(), + Times.Once()); + + this.loggingBrokerMock.Verify(broker => + broker.LogError(It.Is(SameExceptionAs( + expectedReaderServiceException))), + Times.Once); + + this.storageBrokerMock.VerifyNoOtherCalls(); + this.loggingBrokerMock.VerifyNoOtherCalls(); + } + } +} diff --git a/LibraryManagement.Api.Tests.Unit/Services/Foundations/Readers/ReaderServiceTests.Exceptions.RetrieveById.cs b/LibraryManagement.Api.Tests.Unit/Services/Foundations/Readers/ReaderServiceTests.Exceptions.RetrieveById.cs new file mode 100644 index 0000000..3ba27b6 --- /dev/null +++ b/LibraryManagement.Api.Tests.Unit/Services/Foundations/Readers/ReaderServiceTests.Exceptions.RetrieveById.cs @@ -0,0 +1,100 @@ +//----------------------------------------------------------- +// Copyright (c) Coalition of Good-Hearted Engineers +// Free To Use To Build Reliable Library Management Solutions +//----------------------------------------------------------- + +using FluentAssertions; +using LibraryManagement.Api.Models.Foundations.Readers; +using LibraryManagement.Api.Models.Foundations.Readers.Exceptions; +using Microsoft.Data.SqlClient; +using Moq; + +namespace LibraryManagement.Api.Tests.Unit.Services.Foundations.Readers +{ + public partial class ReaderServiceTests + { + [Fact] + public async Task ShouldThrowCriticalDependencyExceptionOnRetrieveByIdIfSqlErrorOccursAndLogItAsync() + { + // given + Guid someId = Guid.NewGuid(); + SqlException sqlException = GetSqlError(); + + var failedReaderStorageException = + new FailedReaderStorageException(sqlException); + + var expectedReaderDependencyException = + new ReaderDependencyException(failedReaderStorageException); + + this.storageBrokerMock.Setup(broker => + broker.SelectReaderByIdAsync(It.IsAny())) + .ThrowsAsync(sqlException); + + // when + ValueTask retrieveReaderById = + this.readerService.RetrieveReaderByIdAsync(someId); + + ReaderDependencyException actualReaderDependencyException = + await Assert.ThrowsAsync(() => + retrieveReaderById.AsTask()); + + // then + actualReaderDependencyException.Should() + .BeEquivalentTo(expectedReaderDependencyException); + + this.storageBrokerMock.Verify(broker => + broker.SelectReaderByIdAsync(someId), + Times.Once()); + + this.loggingBrokerMock.Verify(broker => + broker.LogCritical(It.Is(SameExceptionAs( + expectedReaderDependencyException))), + Times.Once); + + this.storageBrokerMock.VerifyNoOtherCalls(); + this.loggingBrokerMock.VerifyNoOtherCalls(); + } + + [Fact] + public async Task ShouldThrowServiceExceptionOnRetrieveByIdAsyncIfServiceErrorOccursAndLogItAsync() + { + // given + Guid someId = Guid.NewGuid(); + var serverException = new Exception(); + + var failedReaderServiceException = + new FailedReaderServiceException(serverException); + + var expectedReaderServiceException = + new ReaderServiceException(failedReaderServiceException); + + this.storageBrokerMock.Setup(broker => + broker.SelectReaderByIdAsync(It.IsAny())) + .ThrowsAsync(serverException); + + // when + ValueTask retrieveReaderById = + this.readerService.RetrieveReaderByIdAsync(someId); + + ReaderServiceException actualReaderServiceException = + await Assert.ThrowsAsync(() => + retrieveReaderById.AsTask()); + + // then + actualReaderServiceException.Should() + .BeEquivalentTo(expectedReaderServiceException); + + this.storageBrokerMock.Verify(broker => + broker.SelectReaderByIdAsync(someId), + Times.Once); + + this.loggingBrokerMock.Verify(broker => + broker.LogError(It.Is(SameExceptionAs( + expectedReaderServiceException))), + Times.Once); + + this.storageBrokerMock.VerifyNoOtherCalls(); + this.loggingBrokerMock.VerifyNoOtherCalls(); + } + } +} diff --git a/LibraryManagement.Api.Tests.Unit/Services/Foundations/Readers/ReaderServiceTests.Logic.RetrieveAll.cs b/LibraryManagement.Api.Tests.Unit/Services/Foundations/Readers/ReaderServiceTests.Logic.RetrieveAll.cs new file mode 100644 index 0000000..49a4868 --- /dev/null +++ b/LibraryManagement.Api.Tests.Unit/Services/Foundations/Readers/ReaderServiceTests.Logic.RetrieveAll.cs @@ -0,0 +1,42 @@ +//----------------------------------------------------------- +// Copyright (c) Coalition of Good-Hearted Engineers +// Free To Use To Build Reliable Library Management Solutions +//----------------------------------------------------------- + +using FluentAssertions; +using Force.DeepCloner; +using LibraryManagement.Api.Models.Foundations.Readers; +using Moq; + +namespace LibraryManagement.Api.Tests.Unit.Services.Foundations.Readers +{ + public partial class ReaderServiceTests + { + [Fact] + public void ShouldRetrieveAllReaders() + { + // given + IQueryable randomReader = CreateRandomReaders(); + IQueryable storageReader = randomReader; + IQueryable expectedReader = storageReader.DeepClone(); + + this.storageBrokerMock.Setup(broker => + broker.SelectAllReaders()) + .Returns(storageReader); + + // when + IQueryable actualReader = + this.readerService.RetrieveAllReaders(); + + // then + actualReader.Should().BeEquivalentTo(expectedReader); + + this.storageBrokerMock.Verify(broker => + broker.SelectAllReaders(), + Times.Once); + + this.storageBrokerMock.VerifyNoOtherCalls(); + this.loggingBrokerMock.VerifyNoOtherCalls(); + } + } +} diff --git a/LibraryManagement.Api.Tests.Unit/Services/Foundations/Readers/ReaderServiceTests.Logic.RetrieveById.cs b/LibraryManagement.Api.Tests.Unit/Services/Foundations/Readers/ReaderServiceTests.Logic.RetrieveById.cs new file mode 100644 index 0000000..1882fdc --- /dev/null +++ b/LibraryManagement.Api.Tests.Unit/Services/Foundations/Readers/ReaderServiceTests.Logic.RetrieveById.cs @@ -0,0 +1,44 @@ +//----------------------------------------------------------- +// Copyright (c) Coalition of Good-Hearted Engineers +// Free To Use To Build Reliable Library Management Solutions +//----------------------------------------------------------- + +using FluentAssertions; +using Force.DeepCloner; +using LibraryManagement.Api.Models.Foundations.Readers; +using Moq; + +namespace LibraryManagement.Api.Tests.Unit.Services.Foundations.Readers +{ + public partial class ReaderServiceTests + { + [Fact] + public async Task ShouldRetrieveReaderByIdAsync() + { + // given + Guid randomReaderId = Guid.NewGuid(); + Guid inputReaderId = randomReaderId; + Reader randomReader = CreateRandomReader(); + Reader storageReader = randomReader; + Reader expectedReader = storageReader.DeepClone(); + + this.storageBrokerMock.Setup(broker => + broker.SelectReaderByIdAsync(inputReaderId)) + .ReturnsAsync(storageReader); + + // when + Reader actualReader = await this + .readerService.RetrieveReaderByIdAsync(inputReaderId); + + // then + actualReader.Should().BeEquivalentTo(expectedReader); + + this.storageBrokerMock.Verify(broker => + broker.SelectReaderByIdAsync(inputReaderId), + Times.Once); + + this.storageBrokerMock.VerifyNoOtherCalls(); + this.loggingBrokerMock.VerifyNoOtherCalls(); + } + } +} diff --git a/LibraryManagement.Api.Tests.Unit/Services/Foundations/Readers/ReaderServiceTests.Validations.RetrieveById.cs b/LibraryManagement.Api.Tests.Unit/Services/Foundations/Readers/ReaderServiceTests.Validations.RetrieveById.cs new file mode 100644 index 0000000..fc77836 --- /dev/null +++ b/LibraryManagement.Api.Tests.Unit/Services/Foundations/Readers/ReaderServiceTests.Validations.RetrieveById.cs @@ -0,0 +1,96 @@ +//----------------------------------------------------------- +// Copyright (c) Coalition of Good-Hearted Engineers +// Free To Use To Build Reliable Library Management Solutions +//----------------------------------------------------------- + +using FluentAssertions; +using LibraryManagement.Api.Models.Foundations.Readers; +using LibraryManagement.Api.Models.Foundations.Readers.Exceptions; +using Moq; + +namespace LibraryManagement.Api.Tests.Unit.Services.Foundations.Readers +{ + public partial class ReaderServiceTests + { + [Fact] + public async Task ShouldThrowValidationExceptionOnRetrieveByIdIfIdIsInvalidAndLogItAsync() + { + // given + Guid invalidReaderId = Guid.Empty; + var invalidReaderException = new InvalidReaderException(); + + invalidReaderException.AddData( + key: nameof(Reader.ReaderId), + values: "Id is required"); + + var expectedReaderValidationException = + new ReaderValidationException(invalidReaderException); + + // when + ValueTask retrieveReaderById = + this.readerService.RetrieveReaderByIdAsync(invalidReaderId); + + ReaderValidationException actualReaderValidationException = + await Assert.ThrowsAsync(() => + retrieveReaderById.AsTask()); + + // then + actualReaderValidationException.Should() + .BeEquivalentTo(expectedReaderValidationException); + + this.loggingBrokerMock.Verify(broker => + broker.LogError(It.Is(SameExceptionAs( + expectedReaderValidationException))), + Times.Once); + + this.storageBrokerMock.Verify(broker => + broker.SelectReaderByIdAsync(It.IsAny()), + Times.Never); + + this.loggingBrokerMock.VerifyNoOtherCalls(); + this.storageBrokerMock.VerifyNoOtherCalls(); + } + + [Fact] + public async Task ShouldThrowValidationExceptionOnRetrieveByIdIfReaderNotFoundAndLogItAsync() + { + // given + Guid someReaderId = Guid.NewGuid(); + Reader noReader = null; + + var notFoundReaderException = + new NotFoundReaderException(someReaderId); + + var expectedReaderValidationException = + new ReaderValidationException(notFoundReaderException); + + this.storageBrokerMock.Setup(broker => + broker.SelectReaderByIdAsync(It.IsAny())) + .ReturnsAsync(noReader); + + // when + ValueTask retriveByIdReaderTask = + this.readerService.RetrieveReaderByIdAsync(someReaderId); + + var actualReaderValidationException = + await Assert.ThrowsAsync(() => + retriveByIdReaderTask.AsTask()); + + // then + actualReaderValidationException.Should() + .BeEquivalentTo(expectedReaderValidationException); + + this.storageBrokerMock.Verify(broker => + broker.SelectReaderByIdAsync(someReaderId), + Times.Once); + + this.loggingBrokerMock.Verify(broker => + broker.LogError(It.Is(SameExceptionAs( + expectedReaderValidationException))), + Times.Once); + + this.storageBrokerMock.VerifyNoOtherCalls(); + this.loggingBrokerMock.VerifyNoOtherCalls(); + } + } +} diff --git a/LibraryManagement.Api.Tests.Unit/Services/Foundations/Readers/ReaderServiceTests.cs b/LibraryManagement.Api.Tests.Unit/Services/Foundations/Readers/ReaderServiceTests.cs index 184c932..344bb53 100644 --- a/LibraryManagement.Api.Tests.Unit/Services/Foundations/Readers/ReaderServiceTests.cs +++ b/LibraryManagement.Api.Tests.Unit/Services/Foundations/Readers/ReaderServiceTests.cs @@ -37,6 +37,15 @@ private static Reader CreateRandomReader() => private static DateTimeOffset GetRandomDateTimeOffset() => new DateTimeRange(earliestDate: new DateTime()).GetValue(); + private IQueryable CreateRandomReaders() + { + return CreateReaderFiller(GetRandomDateTimeOffset()) + .Create(count: GetRandomNumber()).AsQueryable(); + } + + private static int GetRandomNumber() => + new IntRange(2, 9).GetValue(); + private static SqlException GetSqlError() => (SqlException)RuntimeHelpers.GetUninitializedObject(typeof(SqlException)); diff --git a/LibraryManagement.Api/Models/Foundations/Readers/Exceptions/NotFoundReaderException.cs b/LibraryManagement.Api/Models/Foundations/Readers/Exceptions/NotFoundReaderException.cs new file mode 100644 index 0000000..6a59047 --- /dev/null +++ b/LibraryManagement.Api/Models/Foundations/Readers/Exceptions/NotFoundReaderException.cs @@ -0,0 +1,16 @@ +//----------------------------------------------------------- +// Copyright (c) Coalition of Good-Hearted Engineers +// Free To Use To Build Reliable Library Management Solutions +//----------------------------------------------------------- + +using Xeptions; + +namespace LibraryManagement.Api.Models.Foundations.Readers.Exceptions +{ + public class NotFoundReaderException : Xeption + { + public NotFoundReaderException(Guid readerId) + : base(message: $"Reader is not found with id: {readerId}") + { } + } +} diff --git a/LibraryManagement.Api/Services/Foundations/Readers/IReaderService.cs b/LibraryManagement.Api/Services/Foundations/Readers/IReaderService.cs index 35410d3..44822b2 100644 --- a/LibraryManagement.Api/Services/Foundations/Readers/IReaderService.cs +++ b/LibraryManagement.Api/Services/Foundations/Readers/IReaderService.cs @@ -10,5 +10,7 @@ namespace LibraryManagement.Api.Services.Foundations.Readers public interface IReaderService { ValueTask AddReaderAsync(Reader reader); + IQueryable RetrieveAllReaders(); + ValueTask RetrieveReaderByIdAsync(Guid readerId); } } diff --git a/LibraryManagement.Api/Services/Foundations/Readers/ReaderService.Exceptions.cs b/LibraryManagement.Api/Services/Foundations/Readers/ReaderService.Exceptions.cs index d45a0e7..c046ebc 100644 --- a/LibraryManagement.Api/Services/Foundations/Readers/ReaderService.Exceptions.cs +++ b/LibraryManagement.Api/Services/Foundations/Readers/ReaderService.Exceptions.cs @@ -14,6 +14,7 @@ namespace LibraryManagement.Api.Services.Foundations.Readers public partial class ReaderService { private delegate ValueTask ReturningReaderFunction(); + private delegate IQueryable ReturningReadersFunction(); private async ValueTask TryCatch(ReturningReaderFunction returningReaderFunction) { @@ -29,6 +30,10 @@ private async ValueTask TryCatch(ReturningReaderFunction returningReader { throw CreateAndLogValidationException(invalidReaderException); } + catch (NotFoundReaderException notFoundReaderException) + { + throw CreateAndLogValidationException(notFoundReaderException); + } catch (SqlException sqlException) { var failedReaderStorageException = @@ -52,6 +57,28 @@ private async ValueTask TryCatch(ReturningReaderFunction returningReader } } + private IQueryable TryCatch(ReturningReadersFunction returningReadersFunction) + { + try + { + return returningReadersFunction(); + } + catch (SqlException sqlException) + { + var failedReaderStorageException = + new FailedReaderStorageException(sqlException); + + throw CreateAndLogCriticalDependencyException(failedReaderStorageException); + } + catch (Exception exception) + { + var failedReaderServiceException = + new FailedReaderServiceException(exception); + + throw CreateAndLogServiceException(failedReaderServiceException); + } + } + private ReaderValidationException CreateAndLogValidationException(Xeption exception) { var readerValidationException = diff --git a/LibraryManagement.Api/Services/Foundations/Readers/ReaderService.Validations.cs b/LibraryManagement.Api/Services/Foundations/Readers/ReaderService.Validations.cs index d88a4d4..613cb5e 100644 --- a/LibraryManagement.Api/Services/Foundations/Readers/ReaderService.Validations.cs +++ b/LibraryManagement.Api/Services/Foundations/Readers/ReaderService.Validations.cs @@ -29,6 +29,17 @@ private void ValidateReaderNotNull(Reader reader) } } + private static void ValidateReaderId(Guid readerId) => + Validate((Rule: IsInvalid(readerId), Parameter: nameof(Reader.ReaderId))); + + private static void ValidateStorageReader(Reader maybeReader, Guid readerId) + { + if (maybeReader is null) + { + throw new NotFoundReaderException(readerId); + } + } + private static dynamic IsInvalid(Guid id) => new { Condition = id == Guid.Empty, diff --git a/LibraryManagement.Api/Services/Foundations/Readers/ReaderService.cs b/LibraryManagement.Api/Services/Foundations/Readers/ReaderService.cs index 5c99c30..a6a7753 100644 --- a/LibraryManagement.Api/Services/Foundations/Readers/ReaderService.cs +++ b/LibraryManagement.Api/Services/Foundations/Readers/ReaderService.cs @@ -29,5 +29,21 @@ public ValueTask AddReaderAsync(Reader reader) => return await this.storageBroker.InsertReaderAsync(reader); }); + + public IQueryable RetrieveAllReaders() => + TryCatch(() => this.storageBroker.SelectAllReaders()); + + public ValueTask RetrieveReaderByIdAsync(Guid readerId) => + TryCatch(async () => + { + ValidateReaderId(readerId); + + Reader maybeReader = + await this.storageBroker.SelectReaderByIdAsync(readerId); + + ValidateStorageReader(maybeReader, readerId); + + return maybeReader; + }); } }