Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
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;
using ContractsApi.V1.Domain;

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 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");
}
[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");
}
}
}
15 changes: 14 additions & 1 deletion ContractsApi.Tests/V1/Factories/EntityFactoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
4 changes: 3 additions & 1 deletion ContractsApi.Tests/V1/Factories/ResponseFactoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

}
}
}
22 changes: 22 additions & 0 deletions ContractsApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,27 @@ public async Task PatchContractThrowsConflictVersionError()
Times.Never());
}

[Fact]
public async Task PatchContractThrowsErrorIfTryingToSuspendBlock()
{
var today = DateTime.Today;
var tomorrow = today.AddDays(1);
var currentContract = _fixture.Build<ContractDb>().With(x => x.VersionNumber, (int?) null).With(x => x.StartDate, (DateTime?) today).Create();
currentContract.ContractManagement.ContractHierarchy = ContractHierarchy.Block;
await InsertDataIntoDynamoDB(currentContract).ConfigureAwait(false);

var contractId = currentContract.Id;
var request = _fixture.Create<EditContractRequest>();
var suppliedVersion = 0;
request.HandbackDate = tomorrow;
request.SuspensionDate = tomorrow;

Func<Task<UpdateEntityResult<ContractDb>>> func = async () =>
await _classUnderTest.PatchContract(contractId, request, It.IsAny<string>(), suppliedVersion).ConfigureAwait(false);

await func.Should().ThrowAsync<SuspendingBlockException>().WithMessage("It is not possible to add a suspension to blocks");
}

[Fact]
public async Task PatchContractSuccessfullyUpdatesAContract()
{
Expand All @@ -264,6 +285,7 @@ public async Task PatchContractSuccessfullyUpdatesAContract()
var contractId = currentContract.Id;
var request = _fixture.Create<EditContractRequest>();
request.HandbackDate = tomorrow;
request.SuspensionDate = null;
var requestBody = "{ \"StartDate\":\"key7d2d6e42-0cbf-411a-b66c-bc35da8b6061\":{ },\"EndDate\":\"89017f11-95f7-434d-96f8-178e33685fb4\"}}";
var suppliedVersion = 0;

Expand Down
32 changes: 30 additions & 2 deletions ContractsApi.Tests/V1/UseCase/PostNewContractUseCaseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Contract>();
var token = new Token();
var request = BoundaryHelper.ConstructPostRequest();
_mockGateway.Setup(x => x.PostNewContractAsync(It.IsAny<ContractDb>())).ReturnsAsync(contract);
Func<Task<ContractResponseObject>> response = async () => await _classUnderTest.ExecuteAsync(request, token).ConfigureAwait(false);
await response.Should().ThrowAsync<Exception>()
.WithMessage($"Failed creating contract: no parent contract with id [{request.ContractManagement.ParentContractId}] was found");
}
[Fact]
public async Task UseCaseShouldReturnContractIfSuccessfullyCreatedWithNoParentContractId()
{
var contract = _fixture.Create<Contract>();
var token = new Token();
var request = BoundaryHelper.ConstructPostRequest();
request.ContractManagement.ParentContractId = null;
_mockGateway.Setup(x => x.PostNewContractAsync(It.IsAny<ContractDb>())).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<Contract>();
var ParentContract = _fixture.Create<Contract>();
var token = new Token();
var request = BoundaryHelper.ConstructPostRequest();
request.ContractManagement.ParentContractId = ParentContract.Id;
_mockGateway.Setup(x => x.PostNewContractAsync(It.IsAny<ContractDb>())).ReturnsAsync(contract);
_mockGateway.Setup(x => x.GetContractById(It.IsAny<ContractQueryRequest>())).ReturnsAsync(ParentContract);
var response = await _classUnderTest.ExecuteAsync(request, token).ConfigureAwait(false);
response.Should().BeEquivalentTo(contract.ToResponse());
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@ public class CreateContractRequestObject
public TenureType DefaultTenureType { get; set; }
public DateTime? SuspensionDate { get; set; }
public string ReasonForSuspensionDate { get; set; }
public ContractManagement ContractManagement { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using ContractsApi.V1.Domain;
using FluentValidation;
using System;

namespace ContractsApi.V1.Boundary.Requests.Validation
{
public class CreateContractRequestObjectValidator : AbstractValidator<CreateContractRequestObject>
{
public CreateContractRequestObjectValidator()
{
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");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,6 @@ public class ContractResponseObject
public TenureType DefaultTenureType { get; set; }
public DateTime? SuspensionDate { get; set; }
public string ReasonForSuspensionDate { get; set; }
public ContractManagement ContractManagement { get; set; }
}
}
1 change: 1 addition & 0 deletions ContractsApi/V1/Domain/Contract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,6 @@ public class Contract
public TenureType DefaultTenureType { get; set; }
public DateTime? SuspensionDate { get; set; }
public string ReasonForSuspensionDate { get; set; }
public ContractManagement ContractManagement { get; set; }
}
}
10 changes: 10 additions & 0 deletions ContractsApi/V1/Domain/ContractManagement.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;

namespace ContractsApi.V1.Domain
{
public class ContractManagement
{
public ContractHierarchy ContractHierarchy { get; set; }
public Guid? ParentContractId { get; set; }
}
}
9 changes: 9 additions & 0 deletions ContractsApi/V1/Domain/Enums.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,13 @@ public enum ApprovalStatus
Approved,
PendingReapproval
}

[JsonConverter(typeof(JsonStringEnumConverter))]
public enum ContractHierarchy
{
StandaloneUnit,
Block,
ChildUnit
}

}
3 changes: 2 additions & 1 deletion ContractsApi/V1/Factories/CreateRequestFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
ContractManagement = request.ContractManagement
};
}
}
Expand Down
6 changes: 4 additions & 2 deletions ContractsApi/V1/Factories/EntityFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
ContractManagement = contractDb.ContractManagement
};
}

Expand Down Expand Up @@ -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,
ContractManagement = contract.ContractManagement
};
}
}
Expand Down
3 changes: 2 additions & 1 deletion ContractsApi/V1/Factories/ResponseFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
ContractManagement = contract.ContractManagement
};
}

Expand Down
5 changes: 5 additions & 0 deletions ContractsApi/V1/Gateways/DynamoDbGateway.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,11 @@ public async Task<UpdateEntityResult<ContractDb>> PatchContract(Guid id, EditCon
if (existingContract.StartDate > contractRequestBody.HandbackDate || existingContract.StartDate is null)
throw new StartAndHandbackDatesConflictException(existingContract.StartDate, contractRequestBody.HandbackDate);
}
var existingContractHierarchy = existingContract.ContractManagement?.ContractHierarchy;
if ((existingContractHierarchy == ContractHierarchy.Block) && (contractRequestBody.SuspensionDate is not null))
{
throw new SuspendingBlockException();
}
var response = _updater.UpdateEntity(existingContract, requestBody, contractRequestBody);

if (response.NewValues.Any())
Expand Down
3 changes: 3 additions & 0 deletions ContractsApi/V1/Infrastructure/ContractsDb.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,5 +110,8 @@ public class ContractDb

[DynamoDBProperty]
public string ReasonForSuspensionDate { get; set; }

[DynamoDBProperty]
public ContractManagement ContractManagement { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;

namespace ContractsApi.V1.Infrastructure.Exceptions
{
public class SuspendingBlockException : Exception
{
public SuspendingBlockException()
: base("It is not possible to add a suspension to blocks")
{
}
}
}
10 changes: 9 additions & 1 deletion ContractsApi/V1/UseCase/PostNewContractUseCase.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -26,6 +25,15 @@ public PostNewContractUseCase(IContractGateway contractGateway, ISnsGateway snsG

public async Task<ContractResponseObject> 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)
{
Expand Down