diff --git a/GitFyle.Core.Api.Tests.Unit/Services/Foundations/Contributors/ContributorServiceTests.Exceptions.RetrieveById.cs b/GitFyle.Core.Api.Tests.Unit/Services/Foundations/Contributors/ContributorServiceTests.Exceptions.RetrieveById.cs new file mode 100644 index 00000000..496e837f --- /dev/null +++ b/GitFyle.Core.Api.Tests.Unit/Services/Foundations/Contributors/ContributorServiceTests.Exceptions.RetrieveById.cs @@ -0,0 +1,114 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) The Standard Organization: A coalition of the Good-Hearted Engineers +// ---------------------------------------------------------------------------------- + +using System; +using System.Threading.Tasks; +using FluentAssertions; +using GitFyle.Core.Api.Models.Foundations.Contributors; +using GitFyle.Core.Api.Models.Foundations.Contributors.Exceptions; +using Moq; + +namespace GitFyle.Core.Api.Tests.Unit.Services.Foundations.Contributors +{ + public partial class ContributorServiceTests + { + [Fact] + public async Task ShouldThrowCriticalDependencyExceptionOnRetrieveByIdIfSQLErrorOccursAndLogItAsync() + { + // given + Guid someGuid = Guid.NewGuid(); + var sqlException = CreateSqlException(); + + var failedStorageContributorException = + new FailedStorageContributorException( + message: "Failed storage contributor error occurred, contact support.", + innerException: sqlException); + + var expectedContributorDependencyException = + new ContributorDependencyException( + message: "Contributor dependency error occurred, contact support.", + innerException: failedStorageContributorException); + + this.storageBrokerMock.Setup(broker => + broker.SelectContributorByIdAsync(It.IsAny())) + .ThrowsAsync(sqlException); + + // when + ValueTask retrieveContributorByIdTask = + this.contributorService.RetrieveContributorByIdAsync(someGuid); + + // then + await Assert.ThrowsAsync( + testCode: retrieveContributorByIdTask.AsTask); + + this.storageBrokerMock.Verify(broker => + broker.SelectContributorByIdAsync(It.IsAny()), + Times.Once); + + this.loggingBrokerMock.Verify(broker => + broker.LogCriticalAsync(It.Is(SameExceptionAs( + expectedContributorDependencyException))), + Times.Once); + + this.dateTimeBrokerMock.Verify(broker => + broker.GetCurrentDateTimeOffsetAsync(), + Times.Never); + + this.storageBrokerMock.VerifyNoOtherCalls(); + this.loggingBrokerMock.VerifyNoOtherCalls(); + this.dateTimeBrokerMock.VerifyNoOtherCalls(); + } + + [Fact] + public async Task ShouldThrowServiceExceptionOnRetrieveByIdIfServiceErrorOccursAndLogItAsync() + { + //given + var someContributorId = Guid.NewGuid(); + var serviceException = new Exception(); + + var failedServiceContributorException = + new FailedServiceContributorException( + message: "Failed service contributor error occurred, contact support.", + innerException: serviceException); + + var expectedContributorServiceException = + new ContributorServiceException( + message: "Service error occurred, contact support.", + innerException: failedServiceContributorException); + + this.storageBrokerMock.Setup(broker => + broker.SelectContributorByIdAsync(It.IsAny())) + .ThrowsAsync(serviceException); + + //when + ValueTask retrieveContributorByIdTask = + this.contributorService.RetrieveContributorByIdAsync(someContributorId); + + ContributorServiceException actualContributorServiceException = + await Assert.ThrowsAsync( + testCode: retrieveContributorByIdTask.AsTask); + + //then + actualContributorServiceException.Should().BeEquivalentTo( + expectedContributorServiceException); + + this.storageBrokerMock.Verify(broker => + broker.SelectContributorByIdAsync(It.IsAny()), + Times.Once); + + this.loggingBrokerMock.Verify(broker => + broker.LogErrorAsync(It.Is(SameExceptionAs( + expectedContributorServiceException))), + Times.Once); + + this.dateTimeBrokerMock.Verify(broker => + broker.GetCurrentDateTimeOffsetAsync(), + Times.Never); + + this.storageBrokerMock.VerifyNoOtherCalls(); + this.loggingBrokerMock.VerifyNoOtherCalls(); + this.dateTimeBrokerMock.VerifyNoOtherCalls(); + } + } +} \ No newline at end of file diff --git a/GitFyle.Core.Api.Tests.Unit/Services/Foundations/Contributors/ContributorServiceTests.Logic.RetrieveById.cs b/GitFyle.Core.Api.Tests.Unit/Services/Foundations/Contributors/ContributorServiceTests.Logic.RetrieveById.cs new file mode 100644 index 00000000..3578e3d8 --- /dev/null +++ b/GitFyle.Core.Api.Tests.Unit/Services/Foundations/Contributors/ContributorServiceTests.Logic.RetrieveById.cs @@ -0,0 +1,44 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) The Standard Organization: A coalition of the Good-Hearted Engineers +// ---------------------------------------------------------------------------------- + +using System.Threading.Tasks; +using FluentAssertions; +using Force.DeepCloner; +using GitFyle.Core.Api.Models.Foundations.Contributors; +using Moq; + +namespace GitFyle.Core.Api.Tests.Unit.Services.Foundations.Contributors +{ + public partial class ContributorServiceTests + { + [Fact] + public async Task ShouldRetrieveContributorByIdAsync() + { + // given + Contributor randomContributor = CreateRandomContributor(); + Contributor storageContributor = randomContributor; + Contributor expectedContributor = storageContributor.DeepClone(); + + this.storageBrokerMock.Setup(broker => + broker.SelectContributorByIdAsync(randomContributor.Id)) + .ReturnsAsync(storageContributor); + + // when + Contributor actualContributor = + await this.contributorService.RetrieveContributorByIdAsync( + randomContributor.Id); + + // then + actualContributor.Should().BeEquivalentTo(expectedContributor); + + this.storageBrokerMock.Verify(broker => + broker.SelectContributorByIdAsync(randomContributor.Id), + Times.Once); + + this.storageBrokerMock.VerifyNoOtherCalls(); + this.loggingBrokerMock.VerifyNoOtherCalls(); + this.dateTimeBrokerMock.VerifyNoOtherCalls(); + } + } +} \ No newline at end of file diff --git a/GitFyle.Core.Api.Tests.Unit/Services/Foundations/Contributors/ContributorServiceTests.Validations.RetrieveById.cs b/GitFyle.Core.Api.Tests.Unit/Services/Foundations/Contributors/ContributorServiceTests.Validations.RetrieveById.cs new file mode 100644 index 00000000..835116f3 --- /dev/null +++ b/GitFyle.Core.Api.Tests.Unit/Services/Foundations/Contributors/ContributorServiceTests.Validations.RetrieveById.cs @@ -0,0 +1,112 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) The Standard Organization: A coalition of the Good-Hearted Engineers +// ---------------------------------------------------------------------------------- + +using System; +using System.Threading.Tasks; +using FluentAssertions; +using GitFyle.Core.Api.Models.Foundations.Contributors; +using GitFyle.Core.Api.Models.Foundations.Contributors.Exceptions; +using Moq; + +namespace GitFyle.Core.Api.Tests.Unit.Services.Foundations.Contributors +{ + public partial class ContributorServiceTests + { + [Fact] + public async Task ShouldThrowValidationExceptionOnRetrieveByIdWhenContributorIdIsInvalidAndLogItAsync() + { + // given + var invalidContributorId = Guid.Empty; + + var invalidContributorException = + new InvalidContributorException( + message: "Contributor is invalid, fix the errors and try again."); + + invalidContributorException.AddData( + key: nameof(Contributor.Id), + values: "Id is invalid"); + + var expectedContributorValidationException = + new ContributorValidationException( + message: "Contributor validation error occurred, fix errors and try again.", + innerException: invalidContributorException); + + // when + ValueTask retrieveContributorByIdTask = + this.contributorService.RetrieveContributorByIdAsync(invalidContributorId); + + ContributorValidationException actualContributorValidationException = + await Assert.ThrowsAsync( + testCode: retrieveContributorByIdTask.AsTask); + + // then + actualContributorValidationException.Should().BeEquivalentTo( + expectedContributorValidationException); + + this.loggingBrokerMock.Verify(broker => + broker.LogErrorAsync(It.Is(SameExceptionAs( + expectedContributorValidationException))), + Times.Once); + + this.storageBrokerMock.Verify(broker => + broker.SelectContributorByIdAsync(It.IsAny()), + Times.Never); + + this.dateTimeBrokerMock.Verify(broker => + broker.GetCurrentDateTimeOffsetAsync(), + Times.Never); + + this.loggingBrokerMock.VerifyNoOtherCalls(); + this.storageBrokerMock.VerifyNoOtherCalls(); + this.dateTimeBrokerMock.VerifyNoOtherCalls(); + } + + [Fact] + public async Task ShouldThrowValidationExceptionOnRetrieveByIdIfContributorIdNotFoundAndLogitAsync() + { + //given + var someContributorId = Guid.NewGuid(); + Contributor nullContributor = null; + var innerException = new Exception(); + + var notFoundContributorException = + new NotFoundContributorException( + message: $"Contributor not found with id: {someContributorId}"); + + var expectedContributorValidationException = + new ContributorValidationException( + message: "Contributor validation error occurred, fix errors and try again.", + innerException: notFoundContributorException); + + this.storageBrokerMock.Setup(broker => + broker.SelectContributorByIdAsync(someContributorId)) + .ReturnsAsync(nullContributor); + + // when + ValueTask retrieveContributorByIdTask = + this.contributorService.RetrieveContributorByIdAsync(someContributorId); + + ContributorValidationException actualContributorValidationException = + await Assert.ThrowsAsync( + testCode: retrieveContributorByIdTask.AsTask); + + // then + actualContributorValidationException.Should().BeEquivalentTo( + expectedContributorValidationException); + + this.storageBrokerMock.Verify(broker => + broker.SelectContributorByIdAsync(someContributorId), + Times.Once); + + this.loggingBrokerMock.Verify(broker => + broker.LogErrorAsync(It.Is(SameExceptionAs( + expectedContributorValidationException))), + Times.Once); + + this.storageBrokerMock.VerifyNoOtherCalls(); + this.loggingBrokerMock.VerifyNoOtherCalls(); + this.dateTimeBrokerMock.VerifyNoOtherCalls(); + } + } +} \ No newline at end of file diff --git a/GitFyle.Core.Api/Services/Foundations/Contributors/ContributorService.Exceptions.cs b/GitFyle.Core.Api/Services/Foundations/Contributors/ContributorService.Exceptions.cs index 1b1d194e..3082fa05 100644 --- a/GitFyle.Core.Api/Services/Foundations/Contributors/ContributorService.Exceptions.cs +++ b/GitFyle.Core.Api/Services/Foundations/Contributors/ContributorService.Exceptions.cs @@ -33,6 +33,10 @@ private async ValueTask TryCatch(ReturningContributorFunction retur { throw await CreateAndLogValidationExceptionAsync(invalidContributorException); } + catch (NotFoundContributorException notFoundContributorException) + { + throw await CreateAndLogValidationExceptionAsync(notFoundContributorException); + } catch (SqlException sqlException) { var failedStorageContributorException = new FailedStorageContributorException( diff --git a/GitFyle.Core.Api/Services/Foundations/Contributors/ContributorService.Validations.cs b/GitFyle.Core.Api/Services/Foundations/Contributors/ContributorService.Validations.cs index 830a1bb2..44cedc43 100644 --- a/GitFyle.Core.Api/Services/Foundations/Contributors/ContributorService.Validations.cs +++ b/GitFyle.Core.Api/Services/Foundations/Contributors/ContributorService.Validations.cs @@ -160,6 +160,18 @@ private static void ValidateContributorIsNotNull(Contributor contributor) } } + private static void ValidateContributorIdAsync(Guid contributionTypeId) => + Validate((Rule: IsInvalid(contributionTypeId), Parameter: nameof(Contributor.Id))); + + private static void ValidateStorageContributorAsync(Contributor maybeContributor, Guid id) + { + if (maybeContributor is null) + { + throw new NotFoundContributorException( + message: $"Contributor not found with id: {id}"); + } + } + private static void Validate(params (dynamic Rule, string Parameter)[] validations) { var invalidContributorException = diff --git a/GitFyle.Core.Api/Services/Foundations/Contributors/ContributorService.cs b/GitFyle.Core.Api/Services/Foundations/Contributors/ContributorService.cs index 594c6f4e..b2b52a16 100644 --- a/GitFyle.Core.Api/Services/Foundations/Contributors/ContributorService.cs +++ b/GitFyle.Core.Api/Services/Foundations/Contributors/ContributorService.cs @@ -2,6 +2,7 @@ // Copyright (c) The Standard Organization: A coalition of the Good-Hearted Engineers // ---------------------------------------------------------------------------------- +using System; using System.Linq; using System.Threading.Tasks; using GitFyle.Core.Api.Brokers.DateTimes; @@ -37,5 +38,18 @@ public ValueTask AddContributorAsync(Contributor contributor) => public ValueTask> RetrieveAllContributorsAsync() => TryCatch(async () => await this.storageBroker.SelectAllContributorsAsync()); + + public ValueTask RetrieveContributorByIdAsync(Guid contributorId) => + TryCatch(async () => + { + ValidateContributorIdAsync(contributorId); + + Contributor maybeContributor = + await this.storageBroker.SelectContributorByIdAsync(contributorId); + + ValidateStorageContributorAsync(maybeContributor, contributorId); + + return maybeContributor; + }); } } \ No newline at end of file diff --git a/GitFyle.Core.Api/Services/Foundations/Contributors/IContributorService.cs b/GitFyle.Core.Api/Services/Foundations/Contributors/IContributorService.cs index 30f1f4c0..9796397e 100644 --- a/GitFyle.Core.Api/Services/Foundations/Contributors/IContributorService.cs +++ b/GitFyle.Core.Api/Services/Foundations/Contributors/IContributorService.cs @@ -2,6 +2,7 @@ // Copyright (c) The Standard Organization: A coalition of the Good-Hearted Engineers // ---------------------------------------------------------------------------------- +using System; using System.Linq; using System.Threading.Tasks; using GitFyle.Core.Api.Models.Foundations.Contributors; @@ -12,5 +13,6 @@ public interface IContributorService { ValueTask AddContributorAsync(Contributor contributor); ValueTask> RetrieveAllContributorsAsync(); + ValueTask RetrieveContributorByIdAsync(Guid contributorId); } } \ No newline at end of file