diff --git a/GitFyle.Core.Api.Tests.Unit/Controllers/ContributionTypes/ContributionTypesControllerTests.Exceptions.Get.cs b/GitFyle.Core.Api.Tests.Unit/Controllers/ContributionTypes/ContributionTypesControllerTests.Exceptions.Get.cs index 2affa8ff..cdcb311d 100644 --- a/GitFyle.Core.Api.Tests.Unit/Controllers/ContributionTypes/ContributionTypesControllerTests.Exceptions.Get.cs +++ b/GitFyle.Core.Api.Tests.Unit/Controllers/ContributionTypes/ContributionTypesControllerTests.Exceptions.Get.cs @@ -18,7 +18,7 @@ public partial class ContributionTypesControllerTests [Theory] [MemberData(nameof(ServerExceptions))] public async Task ShouldReturnInternalServerErrorOnGetIfServerErrorOccurredAsync( - Xeption serverException) + Xeption serverException) { // given InternalServerErrorObjectResult expectedInternalServerErrorObjectResult = diff --git a/GitFyle.Core.Api.Tests.Unit/Controllers/ContributionTypes/ContributionTypesControllerTests.Exceptions.GetById.cs b/GitFyle.Core.Api.Tests.Unit/Controllers/ContributionTypes/ContributionTypesControllerTests.Exceptions.GetById.cs index 07a863d6..5f729eab 100644 --- a/GitFyle.Core.Api.Tests.Unit/Controllers/ContributionTypes/ContributionTypesControllerTests.Exceptions.GetById.cs +++ b/GitFyle.Core.Api.Tests.Unit/Controllers/ContributionTypes/ContributionTypesControllerTests.Exceptions.GetById.cs @@ -19,7 +19,7 @@ public partial class ContributionTypesControllerTests [Theory] [MemberData(nameof(ValidationExceptions))] public async Task ShouldReturnBadRequestOnGetByIdIfValidationErrorOccursAsync( - Xeption validationException) + Xeption validationException) { // given Guid someId = Guid.NewGuid(); @@ -51,7 +51,7 @@ public async Task ShouldReturnBadRequestOnGetByIdIfValidationErrorOccursAsync( [Theory] [MemberData(nameof(ServerExceptions))] public async Task ShouldReturnInternalServerErrorOnGetByIdIfServerErrorOccursAsync( - Xeption validationException) + Xeption validationException) { // given Guid someId = Guid.NewGuid(); diff --git a/GitFyle.Core.Api.Tests.Unit/Controllers/Contributors/ContributorsControllerTests.Exceptions.Post.cs b/GitFyle.Core.Api.Tests.Unit/Controllers/Contributors/ContributorsControllerTests.Exceptions.Post.cs new file mode 100644 index 00000000..0ab7085f --- /dev/null +++ b/GitFyle.Core.Api.Tests.Unit/Controllers/Contributors/ContributorsControllerTests.Exceptions.Post.cs @@ -0,0 +1,172 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) The Standard Organization: A coalition of the Good-Hearted Engineers +// ---------------------------------------------------------------------------------- + +using System; +using System.Threading.Tasks; +using GitFyle.Core.Api.Models.Foundations.Contributors; +using GitFyle.Core.Api.Models.Foundations.Contributors.Exceptions; +using Microsoft.AspNetCore.Mvc; +using Moq; +using RESTFulSense.Clients.Extensions; +using RESTFulSense.Models; +using Xeptions; + +namespace GitFyle.Core.Api.Tests.Unit.Controllers.Contributors +{ + public partial class ContributorsControllerTests + { + [Theory] + [MemberData(nameof(ValidationExceptions))] + public async Task ShouldReturnBadRequestOnPostIfValidationErrorOccursAsync( + Xeption validationException) + { + // given + Contributor someContributor = CreateRandomContributor(); + + BadRequestObjectResult expectedBadRequestObjectResult = + BadRequest(validationException.InnerException); + + var expectedActionResult = + new ActionResult(expectedBadRequestObjectResult); + + this.contributorServiceMock.Setup(service => + service.AddContributorAsync(It.IsAny())) + .ThrowsAsync(validationException); + + // when + ActionResult actualActionResult = + await this.contributorsController.PostContributorAsync(someContributor); + + // then + actualActionResult.ShouldBeEquivalentTo(expectedActionResult); + + this.contributorServiceMock.Verify(service => + service.AddContributorAsync(It.IsAny()), + Times.Once); + + this.contributorServiceMock.VerifyNoOtherCalls(); + } + + [Theory] + [MemberData(nameof(ServerExceptions))] + public async Task ShouldReturnInternalServerErrorOnPostIfServerErrorOccurredAsync( + Xeption serverException) + { + // given + Contributor someContributor = CreateRandomContributor(); + + InternalServerErrorObjectResult expectedInternalServerErrorObjectResult = + InternalServerError(serverException); + + var expectedActionResult = + new ActionResult(expectedInternalServerErrorObjectResult); + + this.contributorServiceMock.Setup(service => + service.AddContributorAsync(It.IsAny())) + .ThrowsAsync(serverException); + + // when + ActionResult actualActionResult = + await this.contributorsController.PostContributorAsync(someContributor); + + // then + actualActionResult.ShouldBeEquivalentTo(expectedActionResult); + + this.contributorServiceMock.Verify(service => + service.AddContributorAsync(It.IsAny()), + Times.Once); + + this.contributorServiceMock.VerifyNoOtherCalls(); + } + + [Fact] + public async Task ShouldReturnConflictOnPostIfAlreadyExistsContributorErrorOccurredAsync() + { + // given + Contributor someContributor = CreateRandomContributor(); + var someInnerException = new Exception(); + string someMessage = GetRandomString(); + var someDictionaryData = GetRandomDictionaryData(); + + var alreadyExistsContributorException = + new AlreadyExistsContributorException( + message: someMessage, + innerException: someInnerException, + data: someInnerException.Data); + + var contributorDependencyValidationException = + new ContributorDependencyValidationException( + message: someMessage, + innerException: alreadyExistsContributorException, + data: someDictionaryData); + + ConflictObjectResult expectedConflictObjectResult = + Conflict(alreadyExistsContributorException); + + var expectedActionResult = + new ActionResult(expectedConflictObjectResult); + + this.contributorServiceMock.Setup(service => + service.AddContributorAsync(It.IsAny())) + .ThrowsAsync(contributorDependencyValidationException); + + // when + ActionResult actualActionResult = + await this.contributorsController.PostContributorAsync(someContributor); + + // then + actualActionResult.ShouldBeEquivalentTo(expectedActionResult); + + this.contributorServiceMock.Verify(service => + service.AddContributorAsync(It.IsAny()), + Times.Once); + + this.contributorServiceMock.VerifyNoOtherCalls(); + } + + [Fact] + public async Task ShouldReturnFailedDependencyOnPostIfReferenceExceptionOccursAsync() + { + // given + Contributor someContributor = CreateRandomContributor(); + var someInnerException = new Exception(); + string someMessage = GetRandomString(); + + var invalidReferenceContributorException = + new InvalidReferenceContributorException( + message: someMessage, + innerException: someInnerException, + data: someInnerException.Data); + + var contributorDependencyValidationException = + new ContributorDependencyValidationException( + message: someMessage, + innerException: invalidReferenceContributorException, + data: invalidReferenceContributorException.Data); + + FailedDependencyObjectResult expectedFailedDependencyObjectResult = + FailedDependency(invalidReferenceContributorException); + + var expectedActionResult = + new ActionResult(expectedFailedDependencyObjectResult); + + this.contributorServiceMock.Setup(service => + service.AddContributorAsync(It.IsAny())) + .ThrowsAsync(contributorDependencyValidationException); + + // when + ActionResult actualActionResult = + await this.contributorsController.PostContributorAsync(someContributor); + + // then + actualActionResult.ShouldBeEquivalentTo(expectedActionResult); + + this.contributorServiceMock.Verify(service => + service.AddContributorAsync(It.IsAny()), + Times.Once); + + this.contributorServiceMock.VerifyNoOtherCalls(); + } + } +} diff --git a/GitFyle.Core.Api.Tests.Unit/Controllers/Contributors/ContributorsControllerTests.Logic.Post.cs b/GitFyle.Core.Api.Tests.Unit/Controllers/Contributors/ContributorsControllerTests.Logic.Post.cs new file mode 100644 index 00000000..c4ec574e --- /dev/null +++ b/GitFyle.Core.Api.Tests.Unit/Controllers/Contributors/ContributorsControllerTests.Logic.Post.cs @@ -0,0 +1,52 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) The Standard Organization: A coalition of the Good-Hearted Engineers +// ---------------------------------------------------------------------------------- + +using System.Threading.Tasks; +using Force.DeepCloner; +using GitFyle.Core.Api.Models.Foundations.Contributors; +using Microsoft.AspNetCore.Mvc; +using Moq; +using RESTFulSense.Clients.Extensions; +using RESTFulSense.Models; + +namespace GitFyle.Core.Api.Tests.Unit.Controllers.Contributors +{ + public partial class ContributorsControllerTests + { + [Fact] + public async Task ShouldReturnCreatedOnPostAsync() + { + // given + Contributor randomContributor = CreateRandomContributor(); + Contributor inputContributor = randomContributor; + Contributor addedContributor = inputContributor; + Contributor expectedContributor = addedContributor.DeepClone(); + + var expectedObjectResult = + new CreatedObjectResult(expectedContributor); + + var expectedActionResult = + new ActionResult(expectedObjectResult); + + this.contributorServiceMock.Setup(service => + service.AddContributorAsync(inputContributor)) + .ReturnsAsync(addedContributor); + + // when + ActionResult actualActionResult = + await this.contributorsController.PostContributorAsync( + inputContributor); + + // then + actualActionResult.ShouldBeEquivalentTo( + expectedActionResult); + + this.contributorServiceMock.Verify(service => + service.AddContributorAsync(inputContributor), + Times.Once); + + this.contributorServiceMock.VerifyNoOtherCalls(); + } + } +} diff --git a/GitFyle.Core.Api.Tests.Unit/Controllers/Contributors/ContributorsControllerTests.cs b/GitFyle.Core.Api.Tests.Unit/Controllers/Contributors/ContributorsControllerTests.cs new file mode 100644 index 00000000..e9fb4e5d --- /dev/null +++ b/GitFyle.Core.Api.Tests.Unit/Controllers/Contributors/ContributorsControllerTests.cs @@ -0,0 +1,107 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) The Standard Organization: A coalition of the Good-Hearted Engineers +// ---------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using GitFyle.Core.Api.Controllers; +using GitFyle.Core.Api.Models.Foundations.Contributors; +using GitFyle.Core.Api.Models.Foundations.Contributors.Exceptions; +using GitFyle.Core.Api.Services.Foundations.Contributors; +using Moq; +using RESTFulSense.Controllers; +using Tynamix.ObjectFiller; +using Xeptions; + +namespace GitFyle.Core.Api.Tests.Unit.Controllers.Contributors +{ + public partial class ContributorsControllerTests : RESTFulController + { + private readonly Mock contributorServiceMock; + private readonly ContributorsController contributorsController; + + public ContributorsControllerTests() + { + this.contributorServiceMock = new Mock(); + + this.contributorsController = new ContributorsController( + contributorService: this.contributorServiceMock.Object); + } + + public static TheoryData ValidationExceptions() + { + var someInnerException = new Xeption(); + string someMessage = GetRandomString(); + var someDictionaryData = GetRandomDictionaryData(); + + return new TheoryData + { + new ContributorValidationException( + message: someMessage, + innerException: someInnerException), + + new ContributorDependencyValidationException( + message: someMessage, + innerException: someInnerException, + data: someDictionaryData) + }; + } + + public static TheoryData ServerExceptions() + { + var someInnerException = new Xeption(); + string someMessage = GetRandomString(); + + return new TheoryData + { + new ContributorDependencyException( + message: someMessage, + innerException: someInnerException), + + new ContributorServiceException( + message: someMessage, + innerException: someInnerException) + }; + } + + private static string GetRandomString() => + new MnemonicString().GetValue(); + + private static int GetRandomNumber() => + new IntRange(min: 2, max: 9).GetValue(); + + private static Dictionary GetRandomDictionaryData() + { + var filler = new Filler>(); + + filler.Setup() + .DictionaryItemCount(maxCount: 10); + + return filler.Create(); + } + + private static DateTimeOffset GetRandomDateTimeOffset() => + new DateTimeRange(earliestDate: DateTime.UnixEpoch).GetValue(); + + private static IQueryable CreateRandomnContributors() => + CreateContributorFiller().Create(count: GetRandomNumber()).AsQueryable(); + + private static Contributor CreateRandomContributor() => + CreateContributorFiller().Create(); + + private static Filler CreateContributorFiller() + { + var filler = new Filler(); + + filler.Setup() + .OnProperty(contributor => + contributor.Contributions).IgnoreIt() + + .OnType().Use( + GetRandomDateTimeOffset); + + return filler; + } + } +} diff --git a/GitFyle.Core.Api/Controllers/ContributorsController.cs b/GitFyle.Core.Api/Controllers/ContributorsController.cs new file mode 100644 index 00000000..f6ea22a2 --- /dev/null +++ b/GitFyle.Core.Api/Controllers/ContributorsController.cs @@ -0,0 +1,61 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) The Standard Organization: A coalition of the Good-Hearted Engineers +// ---------------------------------------------------------------------------------- + +using System.Threading.Tasks; +using GitFyle.Core.Api.Models.Foundations.Contributors; +using GitFyle.Core.Api.Models.Foundations.Contributors.Exceptions; +using GitFyle.Core.Api.Services.Foundations.Contributors; +using Microsoft.AspNetCore.Mvc; +using RESTFulSense.Controllers; + +namespace GitFyle.Core.Api.Controllers +{ + [ApiController] + [Route("api/contributors")] + public class ContributorsController : RESTFulController + { + private readonly IContributorService contributorService; + + public ContributorsController(IContributorService contributorService) => + this.contributorService = contributorService; + + [HttpPost] + public async ValueTask> PostContributorAsync(Contributor contributor) + { + try + { + Contributor addedContributor = + await this.contributorService.AddContributorAsync(contributor); + + return Created(addedContributor); + } + catch (ContributorValidationException contributorValidationException) + { + return BadRequest(contributorValidationException.InnerException); + } + catch (ContributorDependencyValidationException contributorDependencyValidationException) + when (contributorDependencyValidationException.InnerException is InvalidReferenceContributorException) + { + return FailedDependency(contributorDependencyValidationException.InnerException); + } + catch (ContributorDependencyValidationException contributorDependencyValidationException) + when (contributorDependencyValidationException.InnerException is AlreadyExistsContributorException) + { + return Conflict(contributorDependencyValidationException.InnerException); + } + catch (ContributorDependencyValidationException contributorDependencyValidationException) + { + return BadRequest(contributorDependencyValidationException.InnerException); + } + catch (ContributorDependencyException contributorDependencyException) + { + return InternalServerError(contributorDependencyException); + } + catch (ContributorServiceException contributorServiceException) + { + return InternalServerError(contributorServiceException); + } + } + } +}