Repository: The-Standard-Organization/GitFyle.Core.Api
Pull request #296: MEDIUM CONTROLLERS: Contribution Put Tests
Author: login: SlimAhmad
Labels: name: MEDIUM CONTROLLERS
Created: 2025-03-24T12:55:58Z | Merged: 2025-03-28T15:28:40Z

Closes #237 

Linked issue #237: CONTROLLERS: Contribution Controller - Put
Linked issue comments: user: login: SlimAhmad
created_at: 2024-11-20T19:29:47Z
body: I'm grabbing this too
author_association: COLLABORATOR

Diff:
diff --git a/GitFyle.Core.Api.Tests.Unit/Controllers/Contributions/ContributionsControllerTests.Exception.GetById.cs b/GitFyle.Core.Api.Tests.Unit/Controllers/Contributions/ContributionsControllerTests.Exception.GetById.cs
index 040f2f3e..7a64a4f3 100644
--- a/GitFyle.Core.Api.Tests.Unit/Controllers/Contributions/ContributionsControllerTests.Exception.GetById.cs
+++ b/GitFyle.Core.Api.Tests.Unit/Controllers/Contributions/ContributionsControllerTests.Exception.GetById.cs
@@ -18,10 +18,11 @@ public partial class ContributionsControllerTests
     {
         [Theory]
         [MemberData(nameof(ValidationExceptions))]
-        public async Task ShouldReturnBadRequestOnGetByIdIfValidationErrorOccurredAsync(Xeption validationException)
+        public async Task ShouldReturnBadRequestOnGetByIdIfValidationErrorOccurredAsync(
+                Xeption validationException)
         {
             // given
-            Guid someId = Guid.NewGuid();
+            Guid someContributionId = Guid.NewGuid();
 
             BadRequestObjectResult expectedBadRequestObjectResult =
                 BadRequest(validationException.InnerException);
@@ -35,7 +36,7 @@ public async Task ShouldReturnBadRequestOnGetByIdIfValidationErrorOccurredAsync(
 
             // when
             ActionResult<Contribution> actualActionResult =
-                await this.contributionsController.GetContributionByIdAsync(someId);
+                await this.contributionsController.GetContributionByIdAsync(someContributionId);
 
             // then
             actualActionResult.ShouldBeEquivalentTo(expectedActionResult);
@@ -53,7 +54,7 @@ public async Task ShouldReturnInternalServerErrorOnGetByIdIfServerErrorOccurredA
             Xeption validationException)
         {
             // given
-            Guid someId = Guid.NewGuid();
+            Guid someContributionId = Guid.NewGuid();
 
             InternalServerErrorObjectResult expectedBadRequestObjectResult =
                 InternalServerError(validationException);
@@ -67,7 +68,7 @@ public async Task ShouldReturnInternalServerErrorOnGetByIdIfServerErrorOccurredA
 
             // when
             ActionResult<Contribution> actualActionResult =
-                await this.contributionsController.GetContributionByIdAsync(someId);
+                await this.contributionsController.GetContributionByIdAsync(someContributionId);
 
             // then
             actualActionResult.ShouldBeEquivalentTo(expectedActionResult);
@@ -83,7 +84,7 @@ public async Task ShouldReturnInternalServerErrorOnGetByIdIfServerErrorOccurredA
         public async Task ShouldReturnNotFoundOnGetByIdIfItemDoesNotExistAsync()
         {
             // given
-            Guid someId = Guid.NewGuid();
+            Guid someContributionId = Guid.NewGuid();
             string someMessage = GetRandomString();
 
             var notFoundContributionException =
@@ -107,7 +108,7 @@ public async Task ShouldReturnNotFoundOnGetByIdIfItemDoesNotExistAsync()
 
             // when
             ActionResult<Contribution> actualActionResult =
-                await this.contributionsController.GetContributionByIdAsync(someId);
+                await this.contributionsController.GetContributionByIdAsync(someContributionId);
 
             // then
             actualActionResult.ShouldBeEquivalentTo(expectedActionResult);
diff --git a/GitFyle.Core.Api.Tests.Unit/Controllers/Contributions/ContributionsControllerTests.Exceptions.Put.cs b/GitFyle.Core.Api.Tests.Unit/Controllers/Contributions/ContributionsControllerTests.Exceptions.Put.cs
new file mode 100644
index 00000000..1c82e030
--- /dev/null
+++ b/GitFyle.Core.Api.Tests.Unit/Controllers/Contributions/ContributionsControllerTests.Exceptions.Put.cs
@@ -0,0 +1,214 @@
+﻿// ----------------------------------------------------------------------------------
+// Copyright (c) The Standard Organization: A coalition of the Good-Hearted Engineers
+// ----------------------------------------------------------------------------------
+
+using System;
+using System.Threading.Tasks;
+using GitFyle.Core.Api.Models.Foundations.Contributions;
+using GitFyle.Core.Api.Models.Foundations.Contributions.Exceptions;
+using GitFyle.Core.Api.Models.Foundations.ContributionTypes.Exceptions;
+using GitFyle.Core.Api.Models.Foundations.ContributionTypes;
+using Microsoft.AspNetCore.Mvc;
+using Moq;
+using RESTFulSense.Clients.Extensions;
+using RESTFulSense.Models;
+using Xeptions;
+
+namespace GitFyle.Core.Api.Tests.Unit.Controllers.Contributions
+{
+    public partial class ContributionsControllerTests
+    {
+        [Theory]
+        [MemberData(nameof(ValidationExceptions))]
+        public async Task ShouldReturnBadRequestOnPutIfValidationErrorOccursAsync(Xeption validationException)
+        {
+            // given
+            Contribution someContribution = CreateRandomContribution();
+
+            BadRequestObjectResult expectedBadRequestObjectResult =
+                BadRequest(validationException.InnerException);
+
+            var expectedActionResult =
+                new ActionResult<Contribution>(expectedBadRequestObjectResult);
+
+            this.contributionServiceMock.Setup(service =>
+                service.ModifyContributionAsync(It.IsAny<Contribution>()))
+                    .ThrowsAsync(validationException);
+
+            // when
+            ActionResult<Contribution> actualActionResult =
+                await this.contributionsController.PutContributionAsync(someContribution);
+
+            // then
+            actualActionResult.ShouldBeEquivalentTo(expectedActionResult);
+
+            this.contributionServiceMock.Verify(service =>
+                service.ModifyContributionAsync(It.IsAny<Contribution>()),
+                    Times.Once);
+
+            this.contributionServiceMock.VerifyNoOtherCalls();
+        }
+
+        [Fact]
+        public async Task ShouldReturnFailedDependencyOnPutIfReferenceErrorOccursAsync()
+        {
+            // given
+            Contribution someContribution = CreateRandomContribution();
+            var someInnerException = new Exception();
+            string someMessage = GetRandomString();
+            var someDictionaryData = GetRandomDictionaryData();
+
+            var invalidReferenceContributionException =
+                new InvalidReferenceContributionException(
+                    message: someMessage,
+                    innerException: someInnerException,
+                    data: someInnerException.Data);
+
+            var contributionDependencyValidationException =
+                new ContributionDependencyValidationException(
+                    message: someMessage,
+                    innerException: invalidReferenceContributionException,
+                    data: someDictionaryData);
+
+            FailedDependencyObjectResult expectedConflictObjectResult =
+               FailedDependency(invalidReferenceContributionException);
+
+            var expectedActionResult =
+                new ActionResult<Contribution>(expectedConflictObjectResult);
+
+            this.contributionServiceMock.Setup(service =>
+                service.ModifyContributionAsync(It.IsAny<Contribution>()))
+                    .ThrowsAsync(contributionDependencyValidationException);
+
+            // when
+            ActionResult<Contribution> actualActionResult =
+                await this.contributionsController.PutContributionAsync(someContribution);
+
+            // then
+            actualActionResult.ShouldBeEquivalentTo(expectedActionResult);
+
+            this.contributionServiceMock.Verify(service =>
+                service.ModifyContributionAsync(It.IsAny<Contribution>()),
+                    Times.Once);
+
+            this.contributionServiceMock.VerifyNoOtherCalls();
+        }
+
+        [Theory]
+        [MemberData(nameof(ServerExceptions))]
+        public async Task ShouldReturnInternalServerErrorOnPutIfServerErrorOccurredAsync(
+            Xeption validationException)
+        {
+            // given
+            Contribution someContribution = CreateRandomContribution();
+
+            InternalServerErrorObjectResult expectedBadRequestObjectResult =
+                InternalServerError(validationException);
+
+            var expectedActionResult =
+                new ActionResult<Contribution>(expectedBadRequestObjectResult);
+
+            this.contributionServiceMock.Setup(service =>
+                service.ModifyContributionAsync(It.IsAny<Contribution>()))
+                    .ThrowsAsync(validationException);
+
+            // when
+            ActionResult<Contribution> actualActionResult =
+                await this.contributionsController.PutContributionAsync(someContribution);
+
+            // then
+            actualActionResult.ShouldBeEquivalentTo(expectedActionResult);
+
+            this.contributionServiceMock.Verify(service =>
+                service.ModifyContributionAsync(It.IsAny<Contribution>()),
+                    Times.Once);
+
+            this.contributionServiceMock.VerifyNoOtherCalls();
+        }
+
+        [Fact]
+        public async Task ShouldReturnNotFoundOnPutIfItemDoesNotExistAsync()
+        {
+            // given
+            Contribution someContribution = CreateRandomContribution();
+            string someMessage = GetRandomString();
+
+            var notFoundContributionException =
+                new NotFoundContributionException(
+                    message: someMessage);
+
+            var contributionValidationException =
+                new ContributionValidationException(
+                    message: someMessage,
+                    innerException: notFoundContributionException);
+
+            NotFoundObjectResult expectedNotFoundObjectResult =
+                NotFound(notFoundContributionException);
+
+            var expectedActionResult =
+                new ActionResult<Contribution>(expectedNotFoundObjectResult);
+
+            this.contributionServiceMock.Setup(service =>
+                service.ModifyContributionAsync(It.IsAny<Contribution>()))
+                    .ThrowsAsync(contributionValidationException);
+
+            // when
+            ActionResult<Contribution> actualActionResult =
+                await this.contributionsController.PutContributionAsync(someContribution);
+
+            // then
+            actualActionResult.ShouldBeEquivalentTo(expectedActionResult);
+
+            this.contributionServiceMock.Verify(service =>
+                service.ModifyContributionAsync(It.IsAny<Contribution>()),
+                    Times.Once);
+
+            this.contributionServiceMock.VerifyNoOtherCalls();
+        }
+
+        [Fact]
+        public async Task ShouldReturnConflictOnPutIfAlreadyExistsContributionErrorOccursAsync()
+        {
+            // given
+            Contribution someContribution = CreateRandomContribution();
+            var someInnerException = new Exception();
+            string someMessage = GetRandomString();
+            var someDictionaryData = GetRandomDictionaryData();
+
+            var alreadyExistsContributionException =
+                new AlreadyExistsContributionException(
+                    message: someMessage,
+                    innerException: someInnerException,
+                    data: someInnerException.Data);
+
+            var contributionDependencyValidationException =
+                new ContributionDependencyValidationException(
+                    message: someMessage,
+                    innerException: alreadyExistsContributionException,
+                    data: someDictionaryData);
+
+            ConflictObjectResult expectedConflictObjectResult =
+                Conflict(alreadyExistsContributionException);
+
+            var expectedActionResult =
+                new ActionResult<Contribution>(expectedConflictObjectResult);
+
+            this.contributionServiceMock.Setup(service =>
+                service.ModifyContributionAsync(It.IsAny<Contribution>()))
+                    .ThrowsAsync(contributionDependencyValidationException);
+
+            // when
+            ActionResult<Contribution> actualActionResult =
+                await this.contributionsController.PutContributionAsync(someContribution);
+
+            // then
+            actualActionResult.ShouldBeEquivalentTo(expectedActionResult);
+
+            this.contributionServiceMock.Verify(service =>
+                service.ModifyContributionAsync(It.IsAny<Contribution>()),
+                    Times.Once);
+
+            this.contributionServiceMock.VerifyNoOtherCalls();
+        }
+    }
+}
diff --git a/GitFyle.Core.Api.Tests.Unit/Controllers/Contributions/ContributionsControllerTests.Logic.Put.cs b/GitFyle.Core.Api.Tests.Unit/Controllers/Contributions/ContributionsControllerTests.Logic.Put.cs
new file mode 100644
index 00000000..24113998
--- /dev/null
+++ b/GitFyle.Core.Api.Tests.Unit/Controllers/Contributions/ContributionsControllerTests.Logic.Put.cs
@@ -0,0 +1,49 @@
+﻿// ----------------------------------------------------------------------------------
+// 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.Contributions;
+using Microsoft.AspNetCore.Mvc;
+using Moq;
+using RESTFulSense.Clients.Extensions;
+
+namespace GitFyle.Core.Api.Tests.Unit.Controllers.Contributions
+{
+    public partial class ContributionsControllerTests
+    {
+        [Fact]
+        public async Task ShouldReturnOkOnPutAsync()
+        {
+            // given
+            Contribution randomContribution = CreateRandomContribution();
+            Contribution inputContribution = randomContribution;
+            Contribution storageContribution = inputContribution.DeepClone();
+            Contribution expectedContribution = storageContribution.DeepClone();
+
+            var expectedObjectResult =
+                new OkObjectResult(expectedContribution);
+
+            var expectedActionResult =
+                new ActionResult<Contribution>(expectedObjectResult);
+
+            contributionServiceMock
+                .Setup(service => service.ModifyContributionAsync(inputContribution))
+                    .ReturnsAsync(storageContribution);
+
+            // when
+            ActionResult<Contribution> actualActionResult =
+                await contributionsController.PutContributionAsync(randomContribution);
+
+            // then
+            actualActionResult.ShouldBeEquivalentTo(expectedActionResult);
+
+            contributionServiceMock
+               .Verify(service => service.ModifyContributionAsync(inputContribution),
+                   Times.Once);
+
+            contributionServiceMock.VerifyNoOtherCalls();
+        }
+    }
+}
diff --git a/GitFyle.Core.Api/Controllers/ContributionsController.cs b/GitFyle.Core.Api/Controllers/ContributionsController.cs
index d45ce294..b07fbf93 100644
--- a/GitFyle.Core.Api/Controllers/ContributionsController.cs
+++ b/GitFyle.Core.Api/Controllers/ContributionsController.cs
@@ -74,5 +74,49 @@ public async ValueTask<ActionResult<Contribution>> GetContributionByIdAsync(Guid
                 return InternalServerError(contributionServiceException);
             }
         }
+
+        [HttpPut]
+        public async ValueTask<ActionResult<Contribution>> PutContributionAsync(Contribution contribution)
+        {
+            try
+            {
+                Contribution modifiedContribution =
+                    await this.contributionService.ModifyContributionAsync(contribution);
+
+                return Ok(modifiedContribution);
+            }
+            catch (ContributionValidationException contributionValidationException)
+                when (contributionValidationException.InnerException is NotFoundContributionException)
+            {
+                return NotFound(contributionValidationException.InnerException);
+            }
+            catch (ContributionValidationException contributionValidationException)
+            {
+                return BadRequest(contributionValidationException.InnerException);
+            }
+            catch (ContributionDependencyValidationException contributionDependencyValidationException)
+                when (contributionDependencyValidationException.InnerException is AlreadyExistsContributionException)
+            {
+                return Conflict(contributionDependencyValidationException.InnerException);
+            }
+            catch (ContributionDependencyValidationException contributionDependencyValidationException)
+                when (contributionDependencyValidationException.InnerException is 
+                        InvalidReferenceContributionException)
+            {
+                return FailedDependency(contributionDependencyValidationException.InnerException);
+            }
+            catch (ContributionDependencyValidationException contributionDependencyValidationException)
+            {
+                return BadRequest(contributionDependencyValidationException.InnerException);
+            }
+            catch (ContributionDependencyException contributionDependencyException)
+            {
+                return InternalServerError(contributionDependencyException);
+            }
+            catch (ContributionServiceException contributionServiceException)
+            {
+                return InternalServerError(contributionServiceException);
+            }
+        }
     }
 }


Reviews:
user: login: cjdutoit
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T19:23:17Z
user: login: cjdutoit
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T19:25:05Z
user: login: cjdutoit
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T19:41:45Z
user: login: cjdutoit
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-28T15:27:54Z

Review comments:
diff_hunk: @@ -40,5 +41,75 @@ public async ValueTask<ActionResult<IQueryable<Contribution>>> GetAllContributio
                 return InternalServerError(contributionServiceException);
             }
         }
+
+        [HttpGet("{contributionId}")]
+        public async ValueTask<ActionResult<Contribution>> GetContributionByIdAsync(Guid contributionId)
+        {
+            try
+            {
+                Contribution contribution = await this.contributionService.RetrieveContributionByIdAsync(contributionId);
+
+                return Ok(contribution);
+            }
+            catch (ContributionValidationException contributionValidationException)
+                when (contributionValidationException.InnerException is NotFoundContributionException)
+            {
+                return NotFound(contributionValidationException.InnerException);
+            }
+            catch (ContributionValidationException contributionValidationException)
+            {
+                return BadRequest(contributionValidationException.InnerException);
+            }
+            catch (ContributionDependencyValidationException contributionDependencyValidationException)
+            {
+                return BadRequest(contributionDependencyValidationException.InnerException);
+            }
+            catch (ContributionDependencyException contributionDependencyException)
+            {
+                return InternalServerError(contributionDependencyException);
+            }
+            catch (ContributionServiceException contributionServiceException)
+            {
+                return InternalServerError(contributionServiceException);
+            }
+        }
+
+        [HttpPut]
+        public async ValueTask<ActionResult<Contribution>> PutContributionAsync(Contribution contribution)
+        {
+            try
+            {
+                Contribution modifiedContribution =
+                    await this.contributionService.ModifyContributionAsync(contribution);
+
+                return Ok(modifiedContribution);
+            }
+            catch (ContributionValidationException contributionValidationException)
+                when (contributionValidationException.InnerException is NotFoundContributionException)
+            {
+                return NotFound(contributionValidationException.InnerException);
+            }
+            catch (ContributionValidationException contributionValidationException)
+            {
+                return BadRequest(contributionValidationException.InnerException);
+            }
+            catch (ContributionDependencyValidationException contributionDependencyValidationException)
+                when (contributionDependencyValidationException.InnerException is AlreadyExistsContributionException)
+            {
+                return Conflict(contributionDependencyValidationException.InnerException);
+            }
+            catch (ContributionDependencyValidationException contributionDependencyValidationException)
path: GitFyle.Core.Api/Controllers/ContributionsController.cs
user: login: cjdutoit
body: Missing exception handling for reference exception above this line.
(If the exception handling is missing for this, please create `MINOR FOUNDATIONS` issue to add this.)

```cs
            catch (ContributionDependencyValidationException contributionDependencyValidationException)
                when (contributionValidationException.InnerException is InvalidReferenceContributionException)
            {
                return FailedDependency(contributionDependencyValidationException.InnerException);
            }
```
created_at: 2025-03-24T19:23:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,122 @@
+﻿// ----------------------------------------------------------------------------------
+// Copyright (c) The Standard Organization: A coalition of the Good-Hearted Engineers
+// ----------------------------------------------------------------------------------
+
+using System;
+using System.Threading.Tasks;
+using GitFyle.Core.Api.Models.Foundations.Contributions;
+using GitFyle.Core.Api.Models.Foundations.Contributions.Exceptions;
+using Microsoft.AspNetCore.Mvc;
+using Moq;
+using RESTFulSense.Clients.Extensions;
+using RESTFulSense.Models;
+using Xeptions;
+
+namespace GitFyle.Core.Api.Tests.Unit.Controllers.Contributions
+{
+    public partial class ContributionsControllerTests
+    {
+        [Theory]
+        [MemberData(nameof(ValidationExceptions))]
+        public async Task ShouldReturnBadRequestOnGetByIdIfValidationErrorOccurredAsync(Xeption validationException)
+        {
+            // given
+            Guid someId = Guid.NewGuid();
+
+            BadRequestObjectResult expectedBadRequestObjectResult =
+                BadRequest(validationException.InnerException);
+
+            var expectedActionResult =
+                new ActionResult<Contribution>(expectedBadRequestObjectResult);
+
+            this.contributionServiceMock.Setup(service =>
+                service.RetrieveContributionByIdAsync(It.IsAny<Guid>()))
+                    .ThrowsAsync(validationException);
+
+            // when
+            ActionResult<Contribution> actualActionResult =
+                await this.contributionsController.GetContributionByIdAsync(someId);
+
+            // then
+            actualActionResult.ShouldBeEquivalentTo(expectedActionResult);
+
+            this.contributionServiceMock.Verify(service =>
+                service.RetrieveContributionByIdAsync(It.IsAny<Guid>()),
+                    Times.Once);
+
+            this.contributionServiceMock.VerifyNoOtherCalls();
+        }
+
+        [Theory]
+        [MemberData(nameof(ServerExceptions))]
+        public async Task ShouldReturnInternalServerErrorOnGetByIdIfServerErrorOccurredAsync(
+            Xeption validationException)
+        {
+            // given
+            Guid someId = Guid.NewGuid();
+
+            InternalServerErrorObjectResult expectedBadRequestObjectResult =
+                InternalServerError(validationException);
+
+            var expectedActionResult =
+                new ActionResult<Contribution>(expectedBadRequestObjectResult);
+
+            this.contributionServiceMock.Setup(service =>
+                service.RetrieveContributionByIdAsync(It.IsAny<Guid>()))
+                    .ThrowsAsync(validationException);
+
+            // when
+            ActionResult<Contribution> actualActionResult =
+                await this.contributionsController.GetContributionByIdAsync(someId);
+
+            // then
+            actualActionResult.ShouldBeEquivalentTo(expectedActionResult);
+
+            this.contributionServiceMock.Verify(service =>
+                service.RetrieveContributionByIdAsync(It.IsAny<Guid>()),
+                    Times.Once);
+
+            this.contributionServiceMock.VerifyNoOtherCalls();
+        }
+
+        [Fact]
+        public async Task ShouldReturnNotFoundOnGetByIdIfItemDoesNotExistAsync()
+        {
+            // given
+            Guid someId = Guid.NewGuid();
+            string someMessage = GetRandomString();
+
+            var notFoundContributionException =
+                new NotFoundContributionException(
+                    message: someMessage);
+
+            var contributionValidationException =
+                new ContributionValidationException(
+                    message: someMessage,
+                    innerException: notFoundContributionException);
+
+            NotFoundObjectResult expectedNotFoundObjectResult =
+                NotFound(notFoundContributionException);
+
+            var expectedActionResult =
+                new ActionResult<Contribution>(expectedNotFoundObjectResult);
+
+            this.contributionServiceMock.Setup(service =>
+                service.RetrieveContributionByIdAsync(It.IsAny<Guid>()))
+                    .ThrowsAsync(contributionValidationException);
+
+            // when
+            ActionResult<Contribution> actualActionResult =
+                await this.contributionsController.GetContributionByIdAsync(someId);
+
+            // then
+            actualActionResult.ShouldBeEquivalentTo(expectedActionResult);
+
+            this.contributionServiceMock.Verify(service =>
+                service.RetrieveContributionByIdAsync(It.IsAny<Guid>()),
+                    Times.Once);
+
+            this.contributionServiceMock.VerifyNoOtherCalls();
+        }
path: GitFyle.Core.Api.Tests.Unit/Controllers/Contributions/ContributionsControllerTests.Exception.GetById.cs
user: login: cjdutoit
body: Add a test for `ShouldReturnFailedDependencyOnPutIfReferenceErrorOccursAsync`
created_at: 2025-03-24T19:25:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,167 @@
+﻿// ----------------------------------------------------------------------------------
+// Copyright (c) The Standard Organization: A coalition of the Good-Hearted Engineers
+// ----------------------------------------------------------------------------------
+
+using System;
+using System.Threading.Tasks;
+using GitFyle.Core.Api.Models.Foundations.Contributions;
+using GitFyle.Core.Api.Models.Foundations.Contributions.Exceptions;
+using Microsoft.AspNetCore.Mvc;
+using Moq;
+using RESTFulSense.Clients.Extensions;
+using RESTFulSense.Models;
+using Xeptions;
+
+namespace GitFyle.Core.Api.Tests.Unit.Controllers.Contributions
+{
+    public partial class ContributionsControllerTests
+    {
+        [Theory]
+        [MemberData(nameof(ValidationExceptions))]
+        public async Task ShouldReturnBadRequestOnPutIfValidationErrorOccursAsync(Xeption validationException)
+        {
+            // given
+            Contribution someContribution = CreateRandomContribution();
+
+            BadRequestObjectResult expectedBadRequestObjectResult =
+                BadRequest(validationException.InnerException);
+
+            var expectedActionResult =
+                new ActionResult<Contribution>(expectedBadRequestObjectResult);
+
+            this.contributionServiceMock.Setup(service =>
+                service.ModifyContributionAsync(It.IsAny<Contribution>()))
+                    .ThrowsAsync(validationException);
+
+            // when
+            ActionResult<Contribution> actualActionResult =
+                await this.contributionsController.PutContributionAsync(someContribution);
+
+            // then
+            actualActionResult.ShouldBeEquivalentTo(expectedActionResult);
+
+            this.contributionServiceMock.Verify(service =>
+                service.ModifyContributionAsync(It.IsAny<Contribution>()),
+                    Times.Once);
+
+            this.contributionServiceMock.VerifyNoOtherCalls();
+        }
+
+        [Theory]
+        [MemberData(nameof(ServerExceptions))]
+        public async Task ShouldReturnInternalServerErrorOnPutIfServerErrorOccurredAsync(
+            Xeption validationException)
+        {
+            // given
+            Contribution someContribution = CreateRandomContribution();
+
+            InternalServerErrorObjectResult expectedBadRequestObjectResult =
+                InternalServerError(validationException);
+
+            var expectedActionResult =
+                new ActionResult<Contribution>(expectedBadRequestObjectResult);
+
+            this.contributionServiceMock.Setup(service =>
+                service.ModifyContributionAsync(It.IsAny<Contribution>()))
+                    .ThrowsAsync(validationException);
+
+            // when
+            ActionResult<Contribution> actualActionResult =
+                await this.contributionsController.PutContributionAsync(someContribution);
+
+            // then
+            actualActionResult.ShouldBeEquivalentTo(expectedActionResult);
+
+            this.contributionServiceMock.Verify(service =>
+                service.ModifyContributionAsync(It.IsAny<Contribution>()),
+                    Times.Once);
+
+            this.contributionServiceMock.VerifyNoOtherCalls();
+        }
+
+        [Fact]
+        public async Task ShouldReturnNotFoundOnPutIfItemDoesNotExistAsync()
+        {
+            // given
+            Contribution someContribution = CreateRandomContribution();
+            string someMessage = GetRandomString();
+
+            var notFoundContributionException =
+                new NotFoundContributionException(
+                    message: someMessage);
+
+            var contributionValidationException =
+                new ContributionValidationException(
+                    message: someMessage,
+                    innerException: notFoundContributionException);
+
+            NotFoundObjectResult expectedNotFoundObjectResult =
+                NotFound(notFoundContributionException);
+
+            var expectedActionResult =
+                new ActionResult<Contribution>(expectedNotFoundObjectResult);
+
+            this.contributionServiceMock.Setup(service =>
+                service.ModifyContributionAsync(It.IsAny<Contribution>()))
+                    .ThrowsAsync(contributionValidationException);
+
+            // when
+            ActionResult<Contribution> actualActionResult =
+                await this.contributionsController.PutContributionAsync(someContribution);
+
+            // then
+            actualActionResult.ShouldBeEquivalentTo(expectedActionResult);
+
+            this.contributionServiceMock.Verify(service =>
+                service.ModifyContributionAsync(It.IsAny<Contribution>()),
+                    Times.Once);
+
+            this.contributionServiceMock.VerifyNoOtherCalls();
+        }
+
+        [Fact]
+        public async Task ShouldReturnConflictOnPutIfAlreadyExistsContributionErrorOccursAsync()
+        {
+            // given
+            Contribution someContribution = CreateRandomContribution();
+            var someInnerException = new Exception();
+            string someMessage = GetRandomString();
+            var someDictionaryData = GetRandomDictionaryData();
+
+            var alreadyExistsContributionException =
+                new AlreadyExistsContributionException(
+                    message: someMessage,
+                    innerException: someInnerException,
+                    data: someInnerException.Data);
+
+            var contributionDependencyValidationException =
+                new ContributionDependencyValidationException(
+                    message: someMessage,
+                    innerException: alreadyExistsContributionException,
+                    data: someDictionaryData);
+
+            ConflictObjectResult expectedConflictObjectResult =
+                Conflict(alreadyExistsContributionException);
+
+            var expectedActionResult =
+                new ActionResult<Contribution>(expectedConflictObjectResult);
+
+            this.contributionServiceMock.Setup(service =>
+                service.ModifyContributionAsync(It.IsAny<Contribution>()))
+                    .ThrowsAsync(contributionDependencyValidationException);
+
+            // when
+            ActionResult<Contribution> actualActionResult =
+                await this.contributionsController.PutContributionAsync(someContribution);
+
+            // then
+            actualActionResult.ShouldBeEquivalentTo(expectedActionResult);
+
+            this.contributionServiceMock.Verify(service =>
+                service.ModifyContributionAsync(It.IsAny<Contribution>()),
+                    Times.Once);
+
+            this.contributionServiceMock.VerifyNoOtherCalls();
+        }
path: GitFyle.Core.Api.Tests.Unit/Controllers/Contributions/ContributionsControllerTests.Exceptions.Put.cs
user: login: cjdutoit
body: Add unit test for the reference exception
created_at: 2025-03-24T19:41:44Z
author_association: CONTRIBUTOR

PR comments:
user: login: SlimAhmad
created_at: 2025-03-27T15:06:24Z
body: ![image](https://github.com/user-attachments/assets/013798f3-48f0-4394-a10d-4d450cc39dbd)

author_association: COLLABORATOR
user: login: aosunlana
created_at: 2025-03-27T16:20:10Z
body: @SlimAhmad  I think you should put the other PRs in draft mode till this one is merged. If the most recent PR is merged before this particular one, you will lose the older PRs including this. 
author_association: COLLABORATOR