From a7e47b2d1b2b25d074c52759ed5d708d7b40eded Mon Sep 17 00:00:00 2001 From: Marta Pederiva <86296201+martapederiva@users.noreply.github.com> Date: Mon, 16 Jun 2025 15:35:45 +0100 Subject: [PATCH 01/19] Add ContractManagement to track PH and parent Id if present --- .../Boundary/Requests/CreateContractRequestObject.cs | 1 + .../V1/Boundary/Requests/EditContractRequest.cs | 1 + .../V1/Boundary/Response/ContractResponseObject.cs | 1 + ContractsApi/V1/Domain/Contract.cs | 1 + ContractsApi/V1/Domain/ContractManagement.cs | 10 ++++++++++ ContractsApi/V1/Domain/Enums.cs | 9 +++++++++ ContractsApi/V1/Factories/CreateRequestFactory.cs | 3 ++- ContractsApi/V1/Factories/EntityFactory.cs | 6 ++++-- ContractsApi/V1/Factories/ResponseFactory.cs | 3 ++- ContractsApi/V1/Infrastructure/ContractsDb.cs | 3 +++ 10 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 ContractsApi/V1/Domain/ContractManagement.cs diff --git a/ContractsApi/V1/Boundary/Requests/CreateContractRequestObject.cs b/ContractsApi/V1/Boundary/Requests/CreateContractRequestObject.cs index 6b85bf6..fba88ed 100644 --- a/ContractsApi/V1/Boundary/Requests/CreateContractRequestObject.cs +++ b/ContractsApi/V1/Boundary/Requests/CreateContractRequestObject.cs @@ -38,5 +38,6 @@ public class CreateContractRequestObject public TenureType DefaultTenureType { get; set; } public DateTime? SuspensionDate { get; set; } public string ReasonForSuspensionDate { get; set; } + public ContractManagement ContractManagementInfo { get; set; } } } diff --git a/ContractsApi/V1/Boundary/Requests/EditContractRequest.cs b/ContractsApi/V1/Boundary/Requests/EditContractRequest.cs index 2a6cb59..984072a 100644 --- a/ContractsApi/V1/Boundary/Requests/EditContractRequest.cs +++ b/ContractsApi/V1/Boundary/Requests/EditContractRequest.cs @@ -35,6 +35,7 @@ public class EditContractRequest public TenureType DefaultTenureType { get; set; } public DateTime? SuspensionDate { get; set; } public string ReasonForSuspensionDate { get; set; } + public ContractManagement ContractManagement { get; set; } } public class CustomEditContractValidation : AbstractValidator diff --git a/ContractsApi/V1/Boundary/Response/ContractResponseObject.cs b/ContractsApi/V1/Boundary/Response/ContractResponseObject.cs index 47db3ab..237c7d8 100644 --- a/ContractsApi/V1/Boundary/Response/ContractResponseObject.cs +++ b/ContractsApi/V1/Boundary/Response/ContractResponseObject.cs @@ -41,5 +41,6 @@ public class ContractResponseObject public TenureType DefaultTenureType { get; set; } public DateTime? SuspensionDate { get; set; } public string ReasonForSuspensionDate { get; set; } + public ContractManagement ContractManagementInfo { get; set; } } } diff --git a/ContractsApi/V1/Domain/Contract.cs b/ContractsApi/V1/Domain/Contract.cs index e74ca6c..ef8c2aa 100644 --- a/ContractsApi/V1/Domain/Contract.cs +++ b/ContractsApi/V1/Domain/Contract.cs @@ -40,5 +40,6 @@ public class Contract public TenureType DefaultTenureType { get; set; } public DateTime? SuspensionDate { get; set; } public string ReasonForSuspensionDate { get; set; } + public ContractManagement ContractManagementInfo { get; set; } } } diff --git a/ContractsApi/V1/Domain/ContractManagement.cs b/ContractsApi/V1/Domain/ContractManagement.cs new file mode 100644 index 0000000..a5dacef --- /dev/null +++ b/ContractsApi/V1/Domain/ContractManagement.cs @@ -0,0 +1,10 @@ +using System; + +namespace ContractsApi.V1.Domain +{ + public class ContractManagement + { + public AssetHierarchy AssetHierarchy { get; set; } + public Guid ParentAssetId { get; set; } + } +} diff --git a/ContractsApi/V1/Domain/Enums.cs b/ContractsApi/V1/Domain/Enums.cs index bc3ac42..6227313 100644 --- a/ContractsApi/V1/Domain/Enums.cs +++ b/ContractsApi/V1/Domain/Enums.cs @@ -19,4 +19,13 @@ public enum ApprovalStatus Approved, PendingReapproval } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum AssetHierarchy + { + StandaloneUnit, + Block, + ChildUnit + } + } diff --git a/ContractsApi/V1/Factories/CreateRequestFactory.cs b/ContractsApi/V1/Factories/CreateRequestFactory.cs index aa9235f..c95a087 100644 --- a/ContractsApi/V1/Factories/CreateRequestFactory.cs +++ b/ContractsApi/V1/Factories/CreateRequestFactory.cs @@ -42,7 +42,8 @@ public static ContractDb ToDatabase(this CreateContractRequestObject request) Rates = request.Rates, DefaultTenureType = request.DefaultTenureType, SuspensionDate = request.SuspensionDate, - ReasonForSuspensionDate = request.ReasonForSuspensionDate + ReasonForSuspensionDate = request.ReasonForSuspensionDate, + ContractManagementInfo = request.ContractManagementInfo }; } } diff --git a/ContractsApi/V1/Factories/EntityFactory.cs b/ContractsApi/V1/Factories/EntityFactory.cs index d3bef28..86bf9d2 100644 --- a/ContractsApi/V1/Factories/EntityFactory.cs +++ b/ContractsApi/V1/Factories/EntityFactory.cs @@ -44,7 +44,8 @@ public static Contract ToDomain(this ContractDb contractDb) Rates = contractDb.Rates, DefaultTenureType = contractDb.DefaultTenureType, SuspensionDate = contractDb.SuspensionDate, - ReasonForSuspensionDate = contractDb.ReasonForSuspensionDate + ReasonForSuspensionDate = contractDb.ReasonForSuspensionDate, + ContractManagementInfo = contractDb.ContractManagementInfo }; } @@ -84,7 +85,8 @@ public static ContractDb ToDatabase(this Contract contract) Rates = contract.Rates, DefaultTenureType = contract.DefaultTenureType, SuspensionDate = contract.SuspensionDate, - ReasonForSuspensionDate = contract.ReasonForSuspensionDate + ReasonForSuspensionDate = contract.ReasonForSuspensionDate, + ContractManagementInfo = contract.ContractManagementInfo }; } } diff --git a/ContractsApi/V1/Factories/ResponseFactory.cs b/ContractsApi/V1/Factories/ResponseFactory.cs index da9cb95..de73897 100644 --- a/ContractsApi/V1/Factories/ResponseFactory.cs +++ b/ContractsApi/V1/Factories/ResponseFactory.cs @@ -44,7 +44,8 @@ public static ContractResponseObject ToResponse(this Contract contract) Rates = contract.Rates, DefaultTenureType = contract.DefaultTenureType, SuspensionDate = contract.SuspensionDate, - ReasonForSuspensionDate = contract.ReasonForSuspensionDate + ReasonForSuspensionDate = contract.ReasonForSuspensionDate, + ContractManagementInfo = contract.ContractManagementInfo }; } diff --git a/ContractsApi/V1/Infrastructure/ContractsDb.cs b/ContractsApi/V1/Infrastructure/ContractsDb.cs index 8d47901..ca010db 100644 --- a/ContractsApi/V1/Infrastructure/ContractsDb.cs +++ b/ContractsApi/V1/Infrastructure/ContractsDb.cs @@ -110,5 +110,8 @@ public class ContractDb [DynamoDBProperty] public string ReasonForSuspensionDate { get; set; } + + [DynamoDBProperty] + public ContractManagement ContractManagementInfo { get; set; } } } From b8872e6457a3bdd5599d3688ae49df43a8a49eb5 Mon Sep 17 00:00:00 2001 From: Marta Pederiva <86296201+martapederiva@users.noreply.github.com> Date: Mon, 16 Jun 2025 16:26:30 +0100 Subject: [PATCH 02/19] Block suspension on blocks --- ContractsApi/V1/Gateways/DynamoDbGateway.cs | 4 ++++ .../Exceptions/SuspendingBlockException.cs | 12 ++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 ContractsApi/V1/Infrastructure/Exceptions/SuspendingBlockException.cs diff --git a/ContractsApi/V1/Gateways/DynamoDbGateway.cs b/ContractsApi/V1/Gateways/DynamoDbGateway.cs index ff4d457..be7b8e8 100644 --- a/ContractsApi/V1/Gateways/DynamoDbGateway.cs +++ b/ContractsApi/V1/Gateways/DynamoDbGateway.cs @@ -153,6 +153,10 @@ public async Task> PatchContract(Guid id, EditCon if (existingContract.StartDate > contractRequestBody.HandbackDate || existingContract.StartDate is null) throw new StartAndHandbackDatesConflictException(existingContract.StartDate, contractRequestBody.HandbackDate); } + if ((contractRequestBody.ContractManagement.AssetHierarchy == AssetHierarchy.Block) && (contractRequestBody.SuspensionDate is not null)) + { + throw new SuspendingBlockException(); + } var response = _updater.UpdateEntity(existingContract, requestBody, contractRequestBody); if (response.NewValues.Any()) diff --git a/ContractsApi/V1/Infrastructure/Exceptions/SuspendingBlockException.cs b/ContractsApi/V1/Infrastructure/Exceptions/SuspendingBlockException.cs new file mode 100644 index 0000000..f6e9f9f --- /dev/null +++ b/ContractsApi/V1/Infrastructure/Exceptions/SuspendingBlockException.cs @@ -0,0 +1,12 @@ +using System; + +namespace ContractsApi.V1.Infrastructure.Exceptions +{ + public class SuspendingBlockException : Exception + { + public SuspendingBlockException() + : base(string.Format("It is not possible to add a suspesion to blocks")) + { + } + } +} From 36d5b841bb7119e157bb2b9c760ccc7ed3bb24fe Mon Sep 17 00:00:00 2001 From: Marta Pederiva <86296201+martapederiva@users.noreply.github.com> Date: Mon, 16 Jun 2025 16:45:38 +0100 Subject: [PATCH 03/19] Test suspension on blocks exception + typo --- .../V1/Gateways/DynamoDbGatewayTests.cs | 25 +++++++++++++++++++ .../Exceptions/SuspendingBlockException.cs | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/ContractsApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs b/ContractsApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs index f1d2540..fccff1e 100644 --- a/ContractsApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs +++ b/ContractsApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs @@ -220,6 +220,7 @@ public async Task PatchContractThrowsDatesErrorWhenStartDateIsNull() var contractId = currentContract.Id; var request = _fixture.Create(); request.HandbackDate = today; + request.SuspensionDate = null; Func>> func = async () => await _classUnderTest.PatchContract(contractId, request, It.IsAny(), It.IsAny()).ConfigureAwait(false); @@ -252,6 +253,30 @@ public async Task PatchContractThrowsConflictVersionError() Times.Never()); } + [Fact] + public async Task PatchContractThrowsErrorIfTryingToSuspendBlock() + { + var today = DateTime.Today; + var tomorrow = today.AddDays(1); + var currentContract = _fixture.Build().With(x => x.VersionNumber, (int?) null).With(x => x.StartDate, (DateTime?) today).Create(); + + await InsertDataIntoDynamoDB(currentContract).ConfigureAwait(false); + + var contractId = currentContract.Id; + var request = _fixture.Create(); + var suppliedVersion = 1; + request.HandbackDate = tomorrow; + request.SuspensionDate = tomorrow; + request.ContractManagement.AssetHierarchy = AssetHierarchy.Block; + + Func>> func = async () => + await _classUnderTest.PatchContract(contractId, request, It.IsAny(), suppliedVersion).ConfigureAwait(false); + + await func.Should().ThrowAsync().WithMessage($"It is not possible to add a suspension to blocks"); + _logger.VerifyExact(LogLevel.Debug, $"Calling IDynamoDBContext.SaveAsync to update id {contractId}", + Times.Never()); + } + [Fact] public async Task PatchContractSuccessfullyUpdatesAContract() { diff --git a/ContractsApi/V1/Infrastructure/Exceptions/SuspendingBlockException.cs b/ContractsApi/V1/Infrastructure/Exceptions/SuspendingBlockException.cs index f6e9f9f..efac002 100644 --- a/ContractsApi/V1/Infrastructure/Exceptions/SuspendingBlockException.cs +++ b/ContractsApi/V1/Infrastructure/Exceptions/SuspendingBlockException.cs @@ -5,7 +5,7 @@ namespace ContractsApi.V1.Infrastructure.Exceptions public class SuspendingBlockException : Exception { public SuspendingBlockException() - : base(string.Format("It is not possible to add a suspesion to blocks")) + : base(string.Format("It is not possible to add a suspension to blocks")) { } } From 9a26c346725b9c16f7eff4e666e46227a8273f20 Mon Sep 17 00:00:00 2001 From: Marta Pederiva <86296201+martapederiva@users.noreply.github.com> Date: Mon, 16 Jun 2025 16:57:50 +0100 Subject: [PATCH 04/19] Test suspension on blocks exception --- ContractsApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ContractsApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs b/ContractsApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs index fccff1e..43ddb19 100644 --- a/ContractsApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs +++ b/ContractsApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs @@ -220,7 +220,6 @@ public async Task PatchContractThrowsDatesErrorWhenStartDateIsNull() var contractId = currentContract.Id; var request = _fixture.Create(); request.HandbackDate = today; - request.SuspensionDate = null; Func>> func = async () => await _classUnderTest.PatchContract(contractId, request, It.IsAny(), It.IsAny()).ConfigureAwait(false); @@ -264,7 +263,7 @@ public async Task PatchContractThrowsErrorIfTryingToSuspendBlock() var contractId = currentContract.Id; var request = _fixture.Create(); - var suppliedVersion = 1; + var suppliedVersion = 0; request.HandbackDate = tomorrow; request.SuspensionDate = tomorrow; request.ContractManagement.AssetHierarchy = AssetHierarchy.Block; @@ -275,7 +274,7 @@ public async Task PatchContractThrowsErrorIfTryingToSuspendBlock() await func.Should().ThrowAsync().WithMessage($"It is not possible to add a suspension to blocks"); _logger.VerifyExact(LogLevel.Debug, $"Calling IDynamoDBContext.SaveAsync to update id {contractId}", Times.Never()); - } + } [Fact] public async Task PatchContractSuccessfullyUpdatesAContract() @@ -289,6 +288,7 @@ public async Task PatchContractSuccessfullyUpdatesAContract() var contractId = currentContract.Id; var request = _fixture.Create(); request.HandbackDate = tomorrow; + request.SuspensionDate = null; var requestBody = "{ \"StartDate\":\"key7d2d6e42-0cbf-411a-b66c-bc35da8b6061\":{ },\"EndDate\":\"89017f11-95f7-434d-96f8-178e33685fb4\"}}"; var suppliedVersion = 0; From 472ebf777126fad3ef62415211cb94759399a820 Mon Sep 17 00:00:00 2001 From: Marta Pederiva <86296201+martapederiva@users.noreply.github.com> Date: Mon, 16 Jun 2025 17:09:41 +0100 Subject: [PATCH 05/19] ContractManagement - make ParentAssetId optional --- ContractsApi/V1/Domain/ContractManagement.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ContractsApi/V1/Domain/ContractManagement.cs b/ContractsApi/V1/Domain/ContractManagement.cs index a5dacef..b0a8ab6 100644 --- a/ContractsApi/V1/Domain/ContractManagement.cs +++ b/ContractsApi/V1/Domain/ContractManagement.cs @@ -5,6 +5,6 @@ namespace ContractsApi.V1.Domain public class ContractManagement { public AssetHierarchy AssetHierarchy { get; set; } - public Guid ParentAssetId { get; set; } + public Guid? ParentAssetId { get; set; } } } From 100122f16104d0f9a7a903af5e2049b3c4b49358 Mon Sep 17 00:00:00 2001 From: Marta Pederiva <86296201+martapederiva@users.noreply.github.com> Date: Mon, 16 Jun 2025 17:13:29 +0100 Subject: [PATCH 06/19] ContractManagement - rename property, not sure what I was thinking --- ContractsApi/V1/Domain/ContractManagement.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ContractsApi/V1/Domain/ContractManagement.cs b/ContractsApi/V1/Domain/ContractManagement.cs index b0a8ab6..c37f521 100644 --- a/ContractsApi/V1/Domain/ContractManagement.cs +++ b/ContractsApi/V1/Domain/ContractManagement.cs @@ -5,6 +5,6 @@ namespace ContractsApi.V1.Domain public class ContractManagement { public AssetHierarchy AssetHierarchy { get; set; } - public Guid? ParentAssetId { get; set; } + public Guid? ParentContractId { get; set; } } } From 84736c32c90161a90b95e6faedb527753bc0ca2d Mon Sep 17 00:00:00 2001 From: Marta Pederiva <86296201+martapederiva@users.noreply.github.com> Date: Thu, 19 Jun 2025 11:22:57 +0100 Subject: [PATCH 07/19] ContractManagement - rename --- .../V1/Boundary/Requests/CreateContractRequestObject.cs | 2 +- ContractsApi/V1/Boundary/Response/ContractResponseObject.cs | 2 +- ContractsApi/V1/Domain/Contract.cs | 2 +- ContractsApi/V1/Factories/CreateRequestFactory.cs | 2 +- ContractsApi/V1/Factories/EntityFactory.cs | 4 ++-- ContractsApi/V1/Factories/ResponseFactory.cs | 2 +- ContractsApi/V1/Infrastructure/ContractsDb.cs | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ContractsApi/V1/Boundary/Requests/CreateContractRequestObject.cs b/ContractsApi/V1/Boundary/Requests/CreateContractRequestObject.cs index fba88ed..3e25af6 100644 --- a/ContractsApi/V1/Boundary/Requests/CreateContractRequestObject.cs +++ b/ContractsApi/V1/Boundary/Requests/CreateContractRequestObject.cs @@ -38,6 +38,6 @@ public class CreateContractRequestObject public TenureType DefaultTenureType { get; set; } public DateTime? SuspensionDate { get; set; } public string ReasonForSuspensionDate { get; set; } - public ContractManagement ContractManagementInfo { get; set; } + public ContractManagement ContractManagement { get; set; } } } diff --git a/ContractsApi/V1/Boundary/Response/ContractResponseObject.cs b/ContractsApi/V1/Boundary/Response/ContractResponseObject.cs index 237c7d8..8bfaf80 100644 --- a/ContractsApi/V1/Boundary/Response/ContractResponseObject.cs +++ b/ContractsApi/V1/Boundary/Response/ContractResponseObject.cs @@ -41,6 +41,6 @@ public class ContractResponseObject public TenureType DefaultTenureType { get; set; } public DateTime? SuspensionDate { get; set; } public string ReasonForSuspensionDate { get; set; } - public ContractManagement ContractManagementInfo { get; set; } + public ContractManagement ContractManagement { get; set; } } } diff --git a/ContractsApi/V1/Domain/Contract.cs b/ContractsApi/V1/Domain/Contract.cs index ef8c2aa..99d68e1 100644 --- a/ContractsApi/V1/Domain/Contract.cs +++ b/ContractsApi/V1/Domain/Contract.cs @@ -40,6 +40,6 @@ public class Contract public TenureType DefaultTenureType { get; set; } public DateTime? SuspensionDate { get; set; } public string ReasonForSuspensionDate { get; set; } - public ContractManagement ContractManagementInfo { get; set; } + public ContractManagement ContractManagement { get; set; } } } diff --git a/ContractsApi/V1/Factories/CreateRequestFactory.cs b/ContractsApi/V1/Factories/CreateRequestFactory.cs index c95a087..9083ed6 100644 --- a/ContractsApi/V1/Factories/CreateRequestFactory.cs +++ b/ContractsApi/V1/Factories/CreateRequestFactory.cs @@ -43,7 +43,7 @@ public static ContractDb ToDatabase(this CreateContractRequestObject request) DefaultTenureType = request.DefaultTenureType, SuspensionDate = request.SuspensionDate, ReasonForSuspensionDate = request.ReasonForSuspensionDate, - ContractManagementInfo = request.ContractManagementInfo + ContractManagement = request.ContractManagement }; } } diff --git a/ContractsApi/V1/Factories/EntityFactory.cs b/ContractsApi/V1/Factories/EntityFactory.cs index 86bf9d2..408bb21 100644 --- a/ContractsApi/V1/Factories/EntityFactory.cs +++ b/ContractsApi/V1/Factories/EntityFactory.cs @@ -45,7 +45,7 @@ public static Contract ToDomain(this ContractDb contractDb) DefaultTenureType = contractDb.DefaultTenureType, SuspensionDate = contractDb.SuspensionDate, ReasonForSuspensionDate = contractDb.ReasonForSuspensionDate, - ContractManagementInfo = contractDb.ContractManagementInfo + ContractManagement = contractDb.ContractManagement }; } @@ -86,7 +86,7 @@ public static ContractDb ToDatabase(this Contract contract) DefaultTenureType = contract.DefaultTenureType, SuspensionDate = contract.SuspensionDate, ReasonForSuspensionDate = contract.ReasonForSuspensionDate, - ContractManagementInfo = contract.ContractManagementInfo + ContractManagement = contract.ContractManagement }; } } diff --git a/ContractsApi/V1/Factories/ResponseFactory.cs b/ContractsApi/V1/Factories/ResponseFactory.cs index de73897..6a78a51 100644 --- a/ContractsApi/V1/Factories/ResponseFactory.cs +++ b/ContractsApi/V1/Factories/ResponseFactory.cs @@ -45,7 +45,7 @@ public static ContractResponseObject ToResponse(this Contract contract) DefaultTenureType = contract.DefaultTenureType, SuspensionDate = contract.SuspensionDate, ReasonForSuspensionDate = contract.ReasonForSuspensionDate, - ContractManagementInfo = contract.ContractManagementInfo + ContractManagement = contract.ContractManagement }; } diff --git a/ContractsApi/V1/Infrastructure/ContractsDb.cs b/ContractsApi/V1/Infrastructure/ContractsDb.cs index ca010db..60b8414 100644 --- a/ContractsApi/V1/Infrastructure/ContractsDb.cs +++ b/ContractsApi/V1/Infrastructure/ContractsDb.cs @@ -112,6 +112,6 @@ public class ContractDb public string ReasonForSuspensionDate { get; set; } [DynamoDBProperty] - public ContractManagement ContractManagementInfo { get; set; } + public ContractManagement ContractManagement { get; set; } } } From 75d29fa0ed7c1989a0830a3fd39fb851e4b55f6a Mon Sep 17 00:00:00 2001 From: Marta Pederiva <86296201+martapederiva@users.noreply.github.com> Date: Thu, 19 Jun 2025 11:30:03 +0100 Subject: [PATCH 08/19] ContractManagement - check hierarchy on existing one and not incoming from patch --- ContractsApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs | 2 +- ContractsApi/V1/Gateways/DynamoDbGateway.cs | 2 +- .../V1/Infrastructure/Exceptions/SuspendingBlockException.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ContractsApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs b/ContractsApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs index 43ddb19..2fe31c5 100644 --- a/ContractsApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs +++ b/ContractsApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs @@ -266,7 +266,7 @@ public async Task PatchContractThrowsErrorIfTryingToSuspendBlock() var suppliedVersion = 0; request.HandbackDate = tomorrow; request.SuspensionDate = tomorrow; - request.ContractManagement.AssetHierarchy = AssetHierarchy.Block; + currentContract.ContractManagement.AssetHierarchy = AssetHierarchy.Block; Func>> func = async () => await _classUnderTest.PatchContract(contractId, request, It.IsAny(), suppliedVersion).ConfigureAwait(false); diff --git a/ContractsApi/V1/Gateways/DynamoDbGateway.cs b/ContractsApi/V1/Gateways/DynamoDbGateway.cs index be7b8e8..161ec14 100644 --- a/ContractsApi/V1/Gateways/DynamoDbGateway.cs +++ b/ContractsApi/V1/Gateways/DynamoDbGateway.cs @@ -153,7 +153,7 @@ public async Task> PatchContract(Guid id, EditCon if (existingContract.StartDate > contractRequestBody.HandbackDate || existingContract.StartDate is null) throw new StartAndHandbackDatesConflictException(existingContract.StartDate, contractRequestBody.HandbackDate); } - if ((contractRequestBody.ContractManagement.AssetHierarchy == AssetHierarchy.Block) && (contractRequestBody.SuspensionDate is not null)) + if ((existingContract.ContractManagement?.AssetHierarchy == AssetHierarchy.Block) && (contractRequestBody.SuspensionDate is not null)) { throw new SuspendingBlockException(); } diff --git a/ContractsApi/V1/Infrastructure/Exceptions/SuspendingBlockException.cs b/ContractsApi/V1/Infrastructure/Exceptions/SuspendingBlockException.cs index efac002..146ad7e 100644 --- a/ContractsApi/V1/Infrastructure/Exceptions/SuspendingBlockException.cs +++ b/ContractsApi/V1/Infrastructure/Exceptions/SuspendingBlockException.cs @@ -5,7 +5,7 @@ namespace ContractsApi.V1.Infrastructure.Exceptions public class SuspendingBlockException : Exception { public SuspendingBlockException() - : base(string.Format("It is not possible to add a suspension to blocks")) + : base("It is not possible to add a suspension to blocks") { } } From 89e2a1bb9f6035f7042486980182ede4b4198965 Mon Sep 17 00:00:00 2001 From: Marta Pederiva <86296201+martapederiva@users.noreply.github.com> Date: Thu, 19 Jun 2025 11:33:58 +0100 Subject: [PATCH 09/19] ContractManagement - remove object from patch request --- ContractsApi/V1/Boundary/Requests/EditContractRequest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/ContractsApi/V1/Boundary/Requests/EditContractRequest.cs b/ContractsApi/V1/Boundary/Requests/EditContractRequest.cs index 984072a..2a6cb59 100644 --- a/ContractsApi/V1/Boundary/Requests/EditContractRequest.cs +++ b/ContractsApi/V1/Boundary/Requests/EditContractRequest.cs @@ -35,7 +35,6 @@ public class EditContractRequest public TenureType DefaultTenureType { get; set; } public DateTime? SuspensionDate { get; set; } public string ReasonForSuspensionDate { get; set; } - public ContractManagement ContractManagement { get; set; } } public class CustomEditContractValidation : AbstractValidator From 463a07f13cb9ee578eca8b16aa04eb2b7c00f6aa Mon Sep 17 00:00:00 2001 From: Marta Pederiva <86296201+martapederiva@users.noreply.github.com> Date: Thu, 19 Jun 2025 15:20:41 +0100 Subject: [PATCH 10/19] ContractManagement - gateway changes --- ContractsApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs | 5 +---- ContractsApi/V1/Gateways/DynamoDbGateway.cs | 3 ++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/ContractsApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs b/ContractsApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs index 2fe31c5..4b75a48 100644 --- a/ContractsApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs +++ b/ContractsApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs @@ -258,7 +258,7 @@ public async Task PatchContractThrowsErrorIfTryingToSuspendBlock() var today = DateTime.Today; var tomorrow = today.AddDays(1); var currentContract = _fixture.Build().With(x => x.VersionNumber, (int?) null).With(x => x.StartDate, (DateTime?) today).Create(); - + currentContract.ContractManagement.AssetHierarchy = AssetHierarchy.Block; await InsertDataIntoDynamoDB(currentContract).ConfigureAwait(false); var contractId = currentContract.Id; @@ -266,14 +266,11 @@ public async Task PatchContractThrowsErrorIfTryingToSuspendBlock() var suppliedVersion = 0; request.HandbackDate = tomorrow; request.SuspensionDate = tomorrow; - currentContract.ContractManagement.AssetHierarchy = AssetHierarchy.Block; Func>> func = async () => await _classUnderTest.PatchContract(contractId, request, It.IsAny(), suppliedVersion).ConfigureAwait(false); await func.Should().ThrowAsync().WithMessage($"It is not possible to add a suspension to blocks"); - _logger.VerifyExact(LogLevel.Debug, $"Calling IDynamoDBContext.SaveAsync to update id {contractId}", - Times.Never()); } [Fact] diff --git a/ContractsApi/V1/Gateways/DynamoDbGateway.cs b/ContractsApi/V1/Gateways/DynamoDbGateway.cs index 161ec14..19d39d9 100644 --- a/ContractsApi/V1/Gateways/DynamoDbGateway.cs +++ b/ContractsApi/V1/Gateways/DynamoDbGateway.cs @@ -144,6 +144,7 @@ public async Task> PatchContract(Guid id, EditCon { _logger.LogDebug($"Calling IDynamoDBContext.SaveAsync for id {id}"); var existingContract = await _dynamoDbContext.LoadAsync(id).ConfigureAwait(false); + var existingContractHierarchy = existingContract.ContractManagement.AssetHierarchy; if (existingContract == null) return null; if (ifMatch != existingContract.VersionNumber) @@ -153,7 +154,7 @@ public async Task> PatchContract(Guid id, EditCon if (existingContract.StartDate > contractRequestBody.HandbackDate || existingContract.StartDate is null) throw new StartAndHandbackDatesConflictException(existingContract.StartDate, contractRequestBody.HandbackDate); } - if ((existingContract.ContractManagement?.AssetHierarchy == AssetHierarchy.Block) && (contractRequestBody.SuspensionDate is not null)) + if ((existingContractHierarchy == AssetHierarchy.Block) && (contractRequestBody.SuspensionDate is not null)) { throw new SuspendingBlockException(); } From a818e4bd5a815dee7d8d58933bad96fb4139ec8c Mon Sep 17 00:00:00 2001 From: Marta Pederiva <86296201+martapederiva@users.noreply.github.com> Date: Thu, 19 Jun 2025 16:22:57 +0100 Subject: [PATCH 11/19] ContractManagement - gateway changes --- ContractsApi/V1/Gateways/DynamoDbGateway.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ContractsApi/V1/Gateways/DynamoDbGateway.cs b/ContractsApi/V1/Gateways/DynamoDbGateway.cs index 19d39d9..062fc59 100644 --- a/ContractsApi/V1/Gateways/DynamoDbGateway.cs +++ b/ContractsApi/V1/Gateways/DynamoDbGateway.cs @@ -144,7 +144,6 @@ public async Task> PatchContract(Guid id, EditCon { _logger.LogDebug($"Calling IDynamoDBContext.SaveAsync for id {id}"); var existingContract = await _dynamoDbContext.LoadAsync(id).ConfigureAwait(false); - var existingContractHierarchy = existingContract.ContractManagement.AssetHierarchy; if (existingContract == null) return null; if (ifMatch != existingContract.VersionNumber) @@ -154,6 +153,7 @@ public async Task> PatchContract(Guid id, EditCon if (existingContract.StartDate > contractRequestBody.HandbackDate || existingContract.StartDate is null) throw new StartAndHandbackDatesConflictException(existingContract.StartDate, contractRequestBody.HandbackDate); } + var existingContractHierarchy = existingContract.ContractManagement?.AssetHierarchy; if ((existingContractHierarchy == AssetHierarchy.Block) && (contractRequestBody.SuspensionDate is not null)) { throw new SuspendingBlockException(); From 63bee7c9a6d72b1479e633fe8f81dfc76d7c669e Mon Sep 17 00:00:00 2001 From: Marta Pederiva <86296201+martapederiva@users.noreply.github.com> Date: Mon, 23 Jun 2025 20:06:49 +0100 Subject: [PATCH 12/19] ContractHierarchy rename --- ContractsApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs | 2 +- ContractsApi/V1/Domain/ContractManagement.cs | 2 +- ContractsApi/V1/Domain/Enums.cs | 2 +- ContractsApi/V1/Gateways/DynamoDbGateway.cs | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ContractsApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs b/ContractsApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs index 4b75a48..7ae8d5d 100644 --- a/ContractsApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs +++ b/ContractsApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs @@ -258,7 +258,7 @@ public async Task PatchContractThrowsErrorIfTryingToSuspendBlock() var today = DateTime.Today; var tomorrow = today.AddDays(1); var currentContract = _fixture.Build().With(x => x.VersionNumber, (int?) null).With(x => x.StartDate, (DateTime?) today).Create(); - currentContract.ContractManagement.AssetHierarchy = AssetHierarchy.Block; + currentContract.ContractManagement.ContractHierarchy = ContractHierarchy.Block; await InsertDataIntoDynamoDB(currentContract).ConfigureAwait(false); var contractId = currentContract.Id; diff --git a/ContractsApi/V1/Domain/ContractManagement.cs b/ContractsApi/V1/Domain/ContractManagement.cs index c37f521..1fe8d29 100644 --- a/ContractsApi/V1/Domain/ContractManagement.cs +++ b/ContractsApi/V1/Domain/ContractManagement.cs @@ -4,7 +4,7 @@ namespace ContractsApi.V1.Domain { public class ContractManagement { - public AssetHierarchy AssetHierarchy { get; set; } + public ContractHierarchy ContractHierarchy { get; set; } public Guid? ParentContractId { get; set; } } } diff --git a/ContractsApi/V1/Domain/Enums.cs b/ContractsApi/V1/Domain/Enums.cs index 6227313..35461cb 100644 --- a/ContractsApi/V1/Domain/Enums.cs +++ b/ContractsApi/V1/Domain/Enums.cs @@ -21,7 +21,7 @@ public enum ApprovalStatus } [JsonConverter(typeof(JsonStringEnumConverter))] - public enum AssetHierarchy + public enum ContractHierarchy { StandaloneUnit, Block, diff --git a/ContractsApi/V1/Gateways/DynamoDbGateway.cs b/ContractsApi/V1/Gateways/DynamoDbGateway.cs index 062fc59..ebe8117 100644 --- a/ContractsApi/V1/Gateways/DynamoDbGateway.cs +++ b/ContractsApi/V1/Gateways/DynamoDbGateway.cs @@ -153,8 +153,8 @@ public async Task> PatchContract(Guid id, EditCon if (existingContract.StartDate > contractRequestBody.HandbackDate || existingContract.StartDate is null) throw new StartAndHandbackDatesConflictException(existingContract.StartDate, contractRequestBody.HandbackDate); } - var existingContractHierarchy = existingContract.ContractManagement?.AssetHierarchy; - if ((existingContractHierarchy == AssetHierarchy.Block) && (contractRequestBody.SuspensionDate is not null)) + var existingContractHierarchy = existingContract.ContractManagement?.ContractHierarchy; + if ((existingContractHierarchy == ContractHierarchy.Block) && (contractRequestBody.SuspensionDate is not null)) { throw new SuspendingBlockException(); } From 432d665b9bf0268604732b53df43ef48143668bd Mon Sep 17 00:00:00 2001 From: Marta Pederiva <86296201+martapederiva@users.noreply.github.com> Date: Mon, 23 Jun 2025 20:20:40 +0100 Subject: [PATCH 13/19] ContractManagement - complete factory tests --- .../V1/Factories/EntityFactoryTests.cs | 15 ++++++++++++++- .../V1/Factories/ResponseFactoryTests.cs | 4 +++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/ContractsApi.Tests/V1/Factories/EntityFactoryTests.cs b/ContractsApi.Tests/V1/Factories/EntityFactoryTests.cs index a69faac..98e086b 100644 --- a/ContractsApi.Tests/V1/Factories/EntityFactoryTests.cs +++ b/ContractsApi.Tests/V1/Factories/EntityFactoryTests.cs @@ -40,7 +40,20 @@ public void ToDomainConvertsAllPropertiesCorrectly() result.Brma.Should().Be(contractDb.Brma); result.IsActive.Should().Be(contractDb.IsActive); result.ApprovalStatus.Should().Be(contractDb.ApprovalStatus); - result.Stage.Should().Be(contractDb.Stage); + result.ApprovalStatusReason.Should().Be(contractDb.ApprovalStatusReason); + result.HoldPayment.Should().Be(contractDb.HoldPayment); + result.VatRegistrationNumber.Should().Be(contractDb.VatRegistrationNumber); + result.ReviewDate.Should().Be(contractDb.ReviewDate); + result.ExtensionDate.Should().Be(contractDb.ExtensionDate); + result.ReasonForExtensionDate.Should().Be(contractDb.ReasonForExtensionDate); + result.SelfBillingAgreement.Should().Be(contractDb.SelfBillingAgreement); + result.OptionToTax.Should().Be(contractDb.OptionToTax); + result.OptionToTaxLinkToGoogleDrive.Should().Be(contractDb.OptionToTaxLinkToGoogleDrive); + result.Rates.Should().Be(contractDb.Rates); + result.DefaultTenureType.Should().Be(contractDb.DefaultTenureType); + result.SuspensionDate.Should().Be(contractDb.SuspensionDate); + result.ReasonForSuspensionDate.Should().Be(contractDb.ReasonForSuspensionDate); + result.ContractManagement.Should().Be(contractDb.ContractManagement); } [Fact] diff --git a/ContractsApi.Tests/V1/Factories/ResponseFactoryTests.cs b/ContractsApi.Tests/V1/Factories/ResponseFactoryTests.cs index 2c963e1..46732c8 100644 --- a/ContractsApi.Tests/V1/Factories/ResponseFactoryTests.cs +++ b/ContractsApi.Tests/V1/Factories/ResponseFactoryTests.cs @@ -54,7 +54,9 @@ public void CanMapContractDomainToResponse() response.Rates.Should().Be(contract.Rates); response.DefaultTenureType.Should().Be(contract.DefaultTenureType); response.SuspensionDate.Should().Be(contract.SuspensionDate); - response.ReasonForExtensionDate.Should().Be(contract.ReasonForExtensionDate); + response.ReasonForSuspensionDate.Should().Be(contract.ReasonForSuspensionDate); + response.ContractManagement.Should().Be(contract.ContractManagement); + } } } From 846488266a4ba4024c3a15612b9c29d0d75e354d Mon Sep 17 00:00:00 2001 From: Marta Pederiva <86296201+martapederiva@users.noreply.github.com> Date: Mon, 23 Jun 2025 20:56:09 +0100 Subject: [PATCH 14/19] Add skeleton boundary validation --- ...eateContractRequestObjectValidatorTests.cs | 31 +++++++++++++++++++ .../CreateContractRequestObjectValidator.cs | 14 +++++++++ 2 files changed, 45 insertions(+) create mode 100644 ContractsApi.Tests/V1/Boundary/CreateContractRequestObjectValidatorTests.cs create mode 100644 ContractsApi/V1/Boundary/Requests/Validation/CreateContractRequestObjectValidator.cs diff --git a/ContractsApi.Tests/V1/Boundary/CreateContractRequestObjectValidatorTests.cs b/ContractsApi.Tests/V1/Boundary/CreateContractRequestObjectValidatorTests.cs new file mode 100644 index 0000000..547b807 --- /dev/null +++ b/ContractsApi.Tests/V1/Boundary/CreateContractRequestObjectValidatorTests.cs @@ -0,0 +1,31 @@ +using ContractsApi.V1.Boundary.Requests; +using ContractsApi.V1.Boundary.Requests.Validation; +using AutoFixture; +using FluentAssertions; +using FluentValidation.TestHelper; +using System; +using System.Linq; +using Xunit; + +namespace ContractsApi.Tests.V1.Boundary.Request.Validation +{ +#nullable enable + public class CreateContractRequestObjectValidatorTests + { + private readonly CreateContractRequestObjectValidator _ccrov; + private readonly Fixture _fixture = new(); + + public CreateContractRequestObjectValidatorTests() + { + _ccrov = new CreateContractRequestObjectValidator(); + } + + [Fact] + public void RequestShouldErrorForNoContractHierarchyd() + { + var model = new CreateContractRequestObject() { ContractManagement = null }; + var result = _ccrov.TestValidate(model); + result.ShouldHaveValidationErrorFor(x => x.ContractManagement); + } + } +} diff --git a/ContractsApi/V1/Boundary/Requests/Validation/CreateContractRequestObjectValidator.cs b/ContractsApi/V1/Boundary/Requests/Validation/CreateContractRequestObjectValidator.cs new file mode 100644 index 0000000..1c186fe --- /dev/null +++ b/ContractsApi/V1/Boundary/Requests/Validation/CreateContractRequestObjectValidator.cs @@ -0,0 +1,14 @@ +using FluentValidation; +using Hackney.Core.Validation; + +namespace ContractsApi.V1.Boundary.Requests.Validation +{ + public class CreateContractRequestObjectValidator : AbstractValidator + { + public CreateContractRequestObjectValidator() + { + RuleFor(x => x.ContractManagement).NotEmpty() + .WithMessage("The hierarchy of the contract must be present upon contract creation"); + } + } +} From 3ea412f89add73e6eca0e2408bfe42aea57adced Mon Sep 17 00:00:00 2001 From: Marta Pederiva <86296201+martapederiva@users.noreply.github.com> Date: Mon, 23 Jun 2025 21:07:57 +0100 Subject: [PATCH 15/19] Remove unnecessary interpolation --- ContractsApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ContractsApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs b/ContractsApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs index 7ae8d5d..7131144 100644 --- a/ContractsApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs +++ b/ContractsApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs @@ -270,7 +270,7 @@ public async Task PatchContractThrowsErrorIfTryingToSuspendBlock() Func>> func = async () => await _classUnderTest.PatchContract(contractId, request, It.IsAny(), suppliedVersion).ConfigureAwait(false); - await func.Should().ThrowAsync().WithMessage($"It is not possible to add a suspension to blocks"); + await func.Should().ThrowAsync().WithMessage("It is not possible to add a suspension to blocks"); } [Fact] From ae99290b0875239a32eb977d98d91d122305fbce Mon Sep 17 00:00:00 2001 From: Marta Pederiva <86296201+martapederiva@users.noreply.github.com> Date: Tue, 24 Jun 2025 20:35:37 +0100 Subject: [PATCH 16/19] Expand boundary validation tests --- ...eateContractRequestObjectValidatorTests.cs | 20 +++++++++++++++++-- .../CreateContractRequestObjectValidator.cs | 4 +++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/ContractsApi.Tests/V1/Boundary/CreateContractRequestObjectValidatorTests.cs b/ContractsApi.Tests/V1/Boundary/CreateContractRequestObjectValidatorTests.cs index 547b807..5201086 100644 --- a/ContractsApi.Tests/V1/Boundary/CreateContractRequestObjectValidatorTests.cs +++ b/ContractsApi.Tests/V1/Boundary/CreateContractRequestObjectValidatorTests.cs @@ -6,6 +6,7 @@ using System; using System.Linq; using Xunit; +using ContractsApi.V1.Domain; namespace ContractsApi.Tests.V1.Boundary.Request.Validation { @@ -21,11 +22,26 @@ public CreateContractRequestObjectValidatorTests() } [Fact] - public void RequestShouldErrorForNoContractHierarchyd() + public void RequestShouldErrorForNoContractHierarchy() { var model = new CreateContractRequestObject() { ContractManagement = null }; var result = _ccrov.TestValidate(model); - result.ShouldHaveValidationErrorFor(x => x.ContractManagement); + result.ShouldHaveValidationErrorFor(x => x.ContractManagement) + .WithErrorMessage("The hierarchy of the contract must be present upon contract creation"); + } + [Fact] + public void RequestShouldErrorForInvalidEnumValue() + { + var model = new CreateContractRequestObject() + { + ContractManagement = new() + { + ContractHierarchy = (ContractHierarchy) 9 + } + }; + var result = _ccrov.TestValidate(model); + result.ShouldHaveValidationErrorFor(x => x.ContractManagement.ContractHierarchy) + .WithErrorMessage("The hierarchy provided is not valid"); } } } diff --git a/ContractsApi/V1/Boundary/Requests/Validation/CreateContractRequestObjectValidator.cs b/ContractsApi/V1/Boundary/Requests/Validation/CreateContractRequestObjectValidator.cs index 1c186fe..10ba5c5 100644 --- a/ContractsApi/V1/Boundary/Requests/Validation/CreateContractRequestObjectValidator.cs +++ b/ContractsApi/V1/Boundary/Requests/Validation/CreateContractRequestObjectValidator.cs @@ -1,5 +1,5 @@ using FluentValidation; -using Hackney.Core.Validation; +using System; namespace ContractsApi.V1.Boundary.Requests.Validation { @@ -9,6 +9,8 @@ public CreateContractRequestObjectValidator() { RuleFor(x => x.ContractManagement).NotEmpty() .WithMessage("The hierarchy of the contract must be present upon contract creation"); + RuleFor(x => x.ContractManagement.ContractHierarchy).IsInEnum() + .WithMessage("The hierarchy provided is not valid"); } } } From 9a45ff081a42f09a7022c4d6a9426acee8fb0fdb Mon Sep 17 00:00:00 2001 From: Marta Pederiva <86296201+martapederiva@users.noreply.github.com> Date: Wed, 25 Jun 2025 13:38:59 +0100 Subject: [PATCH 17/19] Better boundary validation and tests --- .../V1/Boundary/CreateContractRequestObjectValidatorTests.cs | 2 +- .../Validation/CreateContractRequestObjectValidator.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ContractsApi.Tests/V1/Boundary/CreateContractRequestObjectValidatorTests.cs b/ContractsApi.Tests/V1/Boundary/CreateContractRequestObjectValidatorTests.cs index 5201086..810867e 100644 --- a/ContractsApi.Tests/V1/Boundary/CreateContractRequestObjectValidatorTests.cs +++ b/ContractsApi.Tests/V1/Boundary/CreateContractRequestObjectValidatorTests.cs @@ -27,7 +27,7 @@ public void RequestShouldErrorForNoContractHierarchy() var model = new CreateContractRequestObject() { ContractManagement = null }; var result = _ccrov.TestValidate(model); result.ShouldHaveValidationErrorFor(x => x.ContractManagement) - .WithErrorMessage("The hierarchy of the contract must be present upon contract creation"); + .WithErrorMessage("ContractManagement must be present upon contract creation"); } [Fact] public void RequestShouldErrorForInvalidEnumValue() diff --git a/ContractsApi/V1/Boundary/Requests/Validation/CreateContractRequestObjectValidator.cs b/ContractsApi/V1/Boundary/Requests/Validation/CreateContractRequestObjectValidator.cs index 10ba5c5..8cf46ef 100644 --- a/ContractsApi/V1/Boundary/Requests/Validation/CreateContractRequestObjectValidator.cs +++ b/ContractsApi/V1/Boundary/Requests/Validation/CreateContractRequestObjectValidator.cs @@ -8,8 +8,9 @@ public class CreateContractRequestObjectValidator : AbstractValidator x.ContractManagement).NotEmpty() - .WithMessage("The hierarchy of the contract must be present upon contract creation"); + .WithMessage("ContractManagement must be present upon contract creation"); RuleFor(x => x.ContractManagement.ContractHierarchy).IsInEnum() + .When(x => x.ContractManagement != null) .WithMessage("The hierarchy provided is not valid"); } } From d0544897ec12e085fbec2d83a7fdbd9d71bc2df8 Mon Sep 17 00:00:00 2001 From: Marta Pederiva <86296201+martapederiva@users.noreply.github.com> Date: Wed, 25 Jun 2025 20:12:18 +0100 Subject: [PATCH 18/19] Add check on ParentContractId --- .../V1/UseCase/PostNewContractUseCaseTests.cs | 32 +++++++++++++++++-- .../V1/UseCase/PostNewContractUseCase.cs | 10 +++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/ContractsApi.Tests/V1/UseCase/PostNewContractUseCaseTests.cs b/ContractsApi.Tests/V1/UseCase/PostNewContractUseCaseTests.cs index da3efe8..6131aaa 100644 --- a/ContractsApi.Tests/V1/UseCase/PostNewContractUseCaseTests.cs +++ b/ContractsApi.Tests/V1/UseCase/PostNewContractUseCaseTests.cs @@ -2,10 +2,13 @@ using System.Threading.Tasks; using AutoFixture; using ContractsApi.Tests.V1.Helper; +using ContractsApi.V1.Boundary.Requests; +using ContractsApi.V1.Boundary.Response; using ContractsApi.V1.Domain; using ContractsApi.V1.Factories; using ContractsApi.V1.Gateways; using ContractsApi.V1.Infrastructure; +using ContractsApi.V1.Infrastructure.Exceptions; using ContractsApi.V1.UseCase; using FluentAssertions; using Hackney.Core.JWT; @@ -31,18 +34,43 @@ public PostNewContractUseCaseTests() _contractSnsFactory = new ContractSnsFactory(); _classUnderTest = new PostNewContractUseCase(_mockGateway.Object, _contractSnsGateway.Object, _contractSnsFactory); } - [Fact] - public async Task UseCaseShouldReturnContractIfSuccessfullyCreated() + public async Task UseCaseShouldThrowExceptionIfParentContractIdNotFound() { var contract = _fixture.Create(); var token = new Token(); var request = BoundaryHelper.ConstructPostRequest(); _mockGateway.Setup(x => x.PostNewContractAsync(It.IsAny())).ReturnsAsync(contract); + Func> response = async () => await _classUnderTest.ExecuteAsync(request, token).ConfigureAwait(false); + await response.Should().ThrowAsync() + .WithMessage($"Failed creating contract: no parent contract with id [{request.ContractManagement.ParentContractId}] was found"); + } + [Fact] + public async Task UseCaseShouldReturnContractIfSuccessfullyCreatedWithNoParentContractId() + { + var contract = _fixture.Create(); + var token = new Token(); + var request = BoundaryHelper.ConstructPostRequest(); + request.ContractManagement.ParentContractId = null; + _mockGateway.Setup(x => x.PostNewContractAsync(It.IsAny())).ReturnsAsync(contract); var response = await _classUnderTest.ExecuteAsync(request, token).ConfigureAwait(false); response.Should().BeEquivalentTo(contract.ToResponse()); } + [Fact] + public async Task UseCaseShouldReturnContractIfSuccessfullyCreatedWithValidParentContractId() + { + var contract = _fixture.Create(); + var ParentContract = _fixture.Create(); + var token = new Token(); + var request = BoundaryHelper.ConstructPostRequest(); + request.ContractManagement.ParentContractId = ParentContract.Id; + _mockGateway.Setup(x => x.PostNewContractAsync(It.IsAny())).ReturnsAsync(contract); + _mockGateway.Setup(x => x.GetContractById(It.IsAny())).ReturnsAsync(ParentContract); + var response = await _classUnderTest.ExecuteAsync(request, token).ConfigureAwait(false); + response.Should().BeEquivalentTo(contract.ToResponse()); + } + } } diff --git a/ContractsApi/V1/UseCase/PostNewContractUseCase.cs b/ContractsApi/V1/UseCase/PostNewContractUseCase.cs index ba908bf..6b68886 100644 --- a/ContractsApi/V1/UseCase/PostNewContractUseCase.cs +++ b/ContractsApi/V1/UseCase/PostNewContractUseCase.cs @@ -1,6 +1,5 @@ using ContractsApi.V1.Boundary.Requests; using ContractsApi.V1.Boundary.Response; -using ContractsApi.V1.Domain; using ContractsApi.V1.Factories; using ContractsApi.V1.Gateways; using ContractsApi.V1.UseCase.Interfaces; @@ -26,6 +25,15 @@ public PostNewContractUseCase(IContractGateway contractGateway, ISnsGateway snsG public async Task ExecuteAsync(CreateContractRequestObject createContractRequestObject, Token token) { + if (createContractRequestObject.ContractManagement.ParentContractId != null) + { + var requestForGateway = new ContractQueryRequest + { + Id = createContractRequestObject.ContractManagement.ParentContractId.Value + }; + var existingParentContract = await _contractGateway.GetContractById(requestForGateway).ConfigureAwait(false); + if (existingParentContract == null) { throw new Exception($"Failed creating contract: no parent contract with id [{createContractRequestObject.ContractManagement.ParentContractId.Value}] was found"); } + } var contract = await _contractGateway.PostNewContractAsync(createContractRequestObject.ToDatabase()).ConfigureAwait(false); if (contract != null && token != null) { From e1e9dd4a8b25f2399234c632d96f258817b1fb4b Mon Sep 17 00:00:00 2001 From: Marta Pederiva <86296201+martapederiva@users.noreply.github.com> Date: Mon, 7 Jul 2025 15:02:06 +0100 Subject: [PATCH 19/19] Better boundary validation - business logic --- ...eateContractRequestObjectValidatorTests.cs | 38 +++++++++++++++---- .../CreateContractRequestObjectValidator.cs | 9 ++++- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/ContractsApi.Tests/V1/Boundary/CreateContractRequestObjectValidatorTests.cs b/ContractsApi.Tests/V1/Boundary/CreateContractRequestObjectValidatorTests.cs index 810867e..ae8570a 100644 --- a/ContractsApi.Tests/V1/Boundary/CreateContractRequestObjectValidatorTests.cs +++ b/ContractsApi.Tests/V1/Boundary/CreateContractRequestObjectValidatorTests.cs @@ -21,14 +21,6 @@ public CreateContractRequestObjectValidatorTests() _ccrov = new CreateContractRequestObjectValidator(); } - [Fact] - public void RequestShouldErrorForNoContractHierarchy() - { - var model = new CreateContractRequestObject() { ContractManagement = null }; - var result = _ccrov.TestValidate(model); - result.ShouldHaveValidationErrorFor(x => x.ContractManagement) - .WithErrorMessage("ContractManagement must be present upon contract creation"); - } [Fact] public void RequestShouldErrorForInvalidEnumValue() { @@ -43,5 +35,35 @@ public void RequestShouldErrorForInvalidEnumValue() result.ShouldHaveValidationErrorFor(x => x.ContractManagement.ContractHierarchy) .WithErrorMessage("The hierarchy provided is not valid"); } + [Fact] + public void RequestShouldErrorIfHierarchyIsBlockOrStandaloneAndParentContractIdIsProvided() + { + var model = new CreateContractRequestObject() + { + ContractManagement = new() + { + ContractHierarchy = (ContractHierarchy) 1, + ParentContractId = new Guid() + } + }; + var result = _ccrov.TestValidate(model); + result.ShouldHaveValidationErrorFor(x => x.ContractManagement.ParentContractId) + .WithErrorMessage("ParentContractId must be empty for Blocks and Standalone Units"); + } + [Fact] + public void RequestShouldErrorIfHierarchyIsChildUnitAndParentContractIdIsNotProvided() + { + var model = new CreateContractRequestObject() + { + ContractManagement = new() + { + ContractHierarchy = (ContractHierarchy) 2, + ParentContractId = null + } + }; + var result = _ccrov.TestValidate(model); + result.ShouldHaveValidationErrorFor(x => x.ContractManagement.ParentContractId) + .WithErrorMessage("ParentContractId must be provided for Child Units"); + } } } diff --git a/ContractsApi/V1/Boundary/Requests/Validation/CreateContractRequestObjectValidator.cs b/ContractsApi/V1/Boundary/Requests/Validation/CreateContractRequestObjectValidator.cs index 8cf46ef..e5f0cda 100644 --- a/ContractsApi/V1/Boundary/Requests/Validation/CreateContractRequestObjectValidator.cs +++ b/ContractsApi/V1/Boundary/Requests/Validation/CreateContractRequestObjectValidator.cs @@ -1,3 +1,4 @@ +using ContractsApi.V1.Domain; using FluentValidation; using System; @@ -7,11 +8,15 @@ public class CreateContractRequestObjectValidator : AbstractValidator x.ContractManagement).NotEmpty() - .WithMessage("ContractManagement must be present upon contract creation"); RuleFor(x => x.ContractManagement.ContractHierarchy).IsInEnum() .When(x => x.ContractManagement != null) .WithMessage("The hierarchy provided is not valid"); + RuleFor(x => x.ContractManagement.ParentContractId).Empty() + .When(x => x.ContractManagement?.ContractHierarchy != ContractHierarchy.ChildUnit) + .WithMessage("ParentContractId must be empty for Blocks and Standalone Units"); + RuleFor(x => x.ContractManagement.ParentContractId).NotEmpty() + .When(x => x.ContractManagement?.ContractHierarchy == ContractHierarchy.ChildUnit) + .WithMessage("ParentContractId must be provided for Child Units"); } } }