Skip to content
This repository has been archived by the owner on May 8, 2023. It is now read-only.

Commit

Permalink
feat: an asynchronous rejection is created for price series, if charg…
Browse files Browse the repository at this point in the history
…e does not exist (#1849)

* new price update rule, clean up old unused rule

* extra test, some modifications

* parameter correction

* Simplified use of ChargeDoesNotExistRule

Co-authored-by: Jonas Daugaard Møller <[email protected]>
  • Loading branch information
x-platformcoder and jonasdmoeller authored Nov 11, 2022
1 parent 1ad8ce6 commit 890261f
Show file tree
Hide file tree
Showing 16 changed files with 144 additions and 162 deletions.
3 changes: 2 additions & 1 deletion docs/business-processes/asynchronous-validations.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ The following asynchronous validation rules are currently implemented in the cha
|VR.505-2|Fee must have period type Month|D23|(✓) Fee only|(✓) Fee only||
|VR.505-3|Subscription must have period type Month|D23|(✓) Subscription only|(✓) Subscription only||
|VR.507-1|The Tariff to which the charge price applies must have 1 price for period type Day, 24 prices for period type Hour or 96 prices for period type Quarter of Hour|E87||(✓) Tariff only||
|VR.508|Only System Operator role is allowed to submit requests concerning tax tariffs|E0I|(✓) Tariff only|(✓) Tariff only||
|VR.508|Only System Operator role is allowed to submit requests concerning tax tariffs|E0I|(✓) Tariff only|||
|VR.509|The charge price must be plausible (i.e. value less than 1.000.000)|E90||||
|VR.513|Charge owner must match sender of a message|E0I||||
|VR.531|The occurrence of a charge is mandatory|E0H||||
Expand All @@ -51,6 +51,7 @@ The following asynchronous validation rules are currently implemented in the cha
|VR.924|Price series start interval and effective date must have the same value|E0H||||
|VR.925|Price series resolution must match the resolution from the charge|D14||||
|VR.926|Price series must include prices|E87||||
|VR.927|Charge must be created before price series can be handled |D14||||

* VR.152 is not fully implemented. Right now we only validate that it is filled with something
* VR.679 is not fully implemented. For now it verifies that the charge exist, not checking that the linked period is within the charge's validity period
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,16 @@ public async Task HandleAsync(ChargePriceCommandReceivedEvent chargePriceCommand
break;
}

var charge = await GetChargeAsync(operation).ConfigureAwait(false);

try
{
if (charge is null)
var charge = await GetChargeAsync(operation).ConfigureAwait(false);
if (charge == null)
{
throw new InvalidOperationException($"Charge ID '{operation.SenderProvidedChargeId}' does not exist.");
var failure = CreateFailure(new ChargeDoesNotExistRule(), operation);
throw new ChargeOperationFailedException(failure.InvalidRules);
}

charge.UpdatePrices(
charge?.UpdatePrices(
operation.Resolution,
operation.PointsStartInterval,
operation.PointsEndInterval,
Expand All @@ -116,6 +116,21 @@ public async Task HandleAsync(ChargePriceCommandReceivedEvent chargePriceCommand
HandleRejections(document, operationsToBeRejected, rejectionRules);
}

private static ValidationResult CreateFailure(
IValidationRule chargeExist,
ChargePriceOperationDto operation)
{
var rules = new List<IValidationRuleContainer>
{
new OperationValidationRuleContainer(
chargeExist,
operation.OperationId),
};

var failure = ValidationResult.CreateFailure(rules);
return failure;
}

private void HandleConfirmations(
DocumentDto document,
IReadOnlyCollection<ChargePriceOperationDto> operationsToBeConfirmed)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ private async Task<List<IValidationRuleContainer>> GetRulesForChargeLinkDtoAsync

var charge = await _chargeRepository.SingleOrNullAsync(chargeIdentifier).ConfigureAwait(false);

rules.Add(new OperationValidationRuleContainer(new ChargeMustExistRule(charge), operation.OperationId));
rules.Add(new OperationValidationRuleContainer(new ChargeMustExistOnLinkStartDateRule(charge), operation.OperationId));

if (charge == null)
return rules;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@

namespace GreenEnergyHub.Charges.Domain.Dtos.ChargeLinksCommands.Validation.BusinessValidation.ValidationRules
{
public class ChargeMustExistRule : IValidationRule
public class ChargeMustExistOnLinkStartDateRule : IValidationRule
{
private readonly Charge? _existingCharge;

public ChargeMustExistRule(Charge? existingCharge)
public ChargeMustExistOnLinkStartDateRule(Charge? existingCharge)
{
_existingCharge = existingCharge;
}

public ValidationRuleIdentifier ValidationRuleIdentifier => ValidationRuleIdentifier.ChargeDoesNotExist;
public ValidationRuleIdentifier ValidationRuleIdentifier => ValidationRuleIdentifier.ChargeDoesNotExistOnLinkStartDate;

public bool IsValid => _existingCharge is not null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2020 Energinet DataHub A/S
//
// Licensed under the Apache License, Version 2.0 (the "License2");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using GreenEnergyHub.Charges.Domain.Dtos.Validation;

namespace GreenEnergyHub.Charges.Domain.Dtos.ChargePriceCommands.Validation.BusinessValidation.ValidationRules
{
public class ChargeDoesNotExistRule : IValidationRule
{
public ValidationRuleIdentifier ValidationRuleIdentifier => ValidationRuleIdentifier.ChargeDoesNotExist;

public bool IsValid => false;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public enum ValidationRuleIdentifier
ChargePriceMaximumDigitsAndDecimals = 24, // VR457 / E86
CommandSenderMustBeAnExistingMarketParticipant = 27, // VR152 / D02
MeteringPointDoesNotExist = 29, // VR200 / E10
ChargeDoesNotExist = 30, // VR679 / E0I
ChargeDoesNotExistOnLinkStartDate = 30, // VR679 / E0I
ChargeLinkUpdateNotYetSupported = 31, // VR902 / D13
UpdateChargeMustHaveEffectiveDateBeforeOrOnStopDate = 32, // VR905 / D14
SubsequentBundleOperationsFail = 33, // VR906 / D14
Expand All @@ -60,10 +60,10 @@ public enum ValidationRuleIdentifier
ChargeOperationIdLengthValidation = 49, // VR922 / E86
ChargeOwnerMustMatchSender = 50, // VR513 / E0I
ChargeTypeTariffTaxIndicatorOnlyAllowedBySystemOperator = 51, // VR508 / E0I
UpdateTaxTariffOnlyAllowedBySystemOperator = 52, // VR508 / E0I
MonthlyPriceSeriesEndDateMustBeFirstOfMonthOrEqualChargeStopDate = 53, // VR923 / D14
EffectiveDateMustMatchPriceSeriesStartInterval = 54, // VR924 / E0H
PriceSeriesResolutionMustMatchChargeResolution = 55, // VR925 / D14
PointsRequired = 56, // VR926 / E87
ChargeDoesNotExist = 57, // VR927 / D14
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,6 @@ public static class CimValidationErrorTextTemplateMessages
public const string ChargeTypeTariffTaxIndicatorOnlyAllowedBySystemOperatorErrorText =
"The sender role used is not allowed to set tax indicator to {{ChargeTaxIndicator}} for charge ID {{DocumentSenderProvidedChargeId}} of type {{ChargeType}} owned by {{ChargeOwner}}";

[ErrorMessageFor(ValidationRuleIdentifier.UpdateTaxTariffOnlyAllowedBySystemOperator)]
public const string UpdateTaxTariffOnlyAllowedBySystemOperatorErrorText =
"The sender role used is not allowed to submit a price series for charge ID {{DocumentSenderProvidedChargeId}} of type {{ChargeType}} owned by {{ChargeOwner}} as it is marked as a tax";

[ErrorMessageFor(ValidationRuleIdentifier.MaximumPrice)]
public const string MaximumPriceErrorText =
"Price {{ChargePointPrice}} not allowed: The specified charge price for position {{ChargePointPosition}} is not plausible (too large) for charge with ID {{DocumentSenderProvidedChargeId}} of type {{ChargeType}} owned by {{ChargeOwner}}.";
Expand All @@ -129,8 +125,8 @@ public static class CimValidationErrorTextTemplateMessages
public const string MeteringPointDoesNotExistValidationErrorText =
"Metering point ID {{MeteringPointId}} is unknown: The specified metering point has not been registered in the system on the charge link start date.";

[ErrorMessageFor(ValidationRuleIdentifier.ChargeDoesNotExist)]
public const string ChargeDoesNotExistValidationErrorText =
[ErrorMessageFor(ValidationRuleIdentifier.ChargeDoesNotExistOnLinkStartDate)]
public const string ChargeDoesNotExistOnLinkStartDateValidationErrorText =
"Charge ID {{DocumentSenderProvidedChargeId}} not allowed: The charge is not an existing charge on date {{ChargeLinkStartDate}}.";

[ErrorMessageFor(ValidationRuleIdentifier.ChargeLinkUpdateNotYetSupported)]
Expand Down Expand Up @@ -221,6 +217,10 @@ public static class CimValidationErrorTextTemplateMessages
public const string PointsIsRequiredErrorText =
"Price series with transaction id {{ChargeOperationId}} does not contain any prices for charge with ID {{DocumentSenderProvidedChargeId}} of type {{ChargeType}} owned by {{ChargeOwner}}.";

[ErrorMessageFor(ValidationRuleIdentifier.ChargeDoesNotExist)]
public const string ChargeDoesNotExistErrorText =
"Charge ID {{DocumentSenderProvidedChargeId}} of type {{ChargeType}} owned by {{ChargeOwner}} does not exist.";

public const string Unknown = "unknown";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public ReasonCode Create(ValidationRuleIdentifier validationRuleIdentifier)
ValidationRuleIdentifier.ChargePriceMaximumDigitsAndDecimals => ReasonCode.E86,
ValidationRuleIdentifier.CommandSenderMustBeAnExistingMarketParticipant => ReasonCode.D02,
ValidationRuleIdentifier.MeteringPointDoesNotExist => ReasonCode.E10,
ValidationRuleIdentifier.ChargeDoesNotExist => ReasonCode.E0I,
ValidationRuleIdentifier.ChargeDoesNotExistOnLinkStartDate => ReasonCode.E0I,
ValidationRuleIdentifier.ChargeLinkUpdateNotYetSupported => ReasonCode.D13,
ValidationRuleIdentifier.UpdateChargeMustHaveEffectiveDateBeforeOrOnStopDate => ReasonCode.D14,
ValidationRuleIdentifier.SubsequentBundleOperationsFail => ReasonCode.D14,
Expand All @@ -68,11 +68,11 @@ public ReasonCode Create(ValidationRuleIdentifier validationRuleIdentifier)
ValidationRuleIdentifier.ChargeOperationIdLengthValidation => ReasonCode.E86,
ValidationRuleIdentifier.ChargeOwnerMustMatchSender => ReasonCode.E0I,
ValidationRuleIdentifier.ChargeTypeTariffTaxIndicatorOnlyAllowedBySystemOperator => ReasonCode.E0I,
ValidationRuleIdentifier.UpdateTaxTariffOnlyAllowedBySystemOperator => ReasonCode.E0I,
ValidationRuleIdentifier.MonthlyPriceSeriesEndDateMustBeFirstOfMonthOrEqualChargeStopDate => ReasonCode.D14,
ValidationRuleIdentifier.EffectiveDateMustMatchPriceSeriesStartInterval => ReasonCode.E0H,
ValidationRuleIdentifier.PriceSeriesResolutionMustMatchChargeResolution => ReasonCode.D14,
ValidationRuleIdentifier.PointsRequired => ReasonCode.E87,
ValidationRuleIdentifier.ChargeDoesNotExist => ReasonCode.D14,
_ => throw new NotImplementedException(),
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,42 @@ public async Task HandleAsync_WhenValidationFails_ThenRejectedEventRaised(
domainEventPublisher.VerifyNoOtherCalls();
}

[Theory]
[InlineAutoMoqData]
public async Task HandleAsync_WhenChargeDoesNotExist_ThenRejectedEventRaised(
[Frozen] Mock<IChargeIdentifierFactory> chargeIdentifierFactory,
[Frozen] Mock<IInputValidator<ChargePriceOperationDto>> inputValidator,
[Frozen] Mock<IChargeRepository> chargeRepository,
[Frozen] Mock<IMarketParticipantRepository> marketParticipantRepository,
[Frozen] Mock<IDomainEventPublisher> domainEventPublisher,
[Frozen] Mock<IChargePriceOperationsRejectedEventFactory> chargePriceOperationsRejectedEventFactory,
TestMarketParticipant sender,
ChargePriceCommandReceivedEvent receivedEvent,
ChargePriceOperationsRejectedEvent chargePriceOperationsRejectedEvent,
ChargePriceOperationsHandler sut)
{
// Arrange
var validationResult = ValidationResult.CreateSuccess();
inputValidator.Setup(v => v.Validate(It.IsAny<ChargePriceOperationDto>(), It.IsAny<DocumentDto>())).Returns(validationResult);
chargePriceOperationsRejectedEventFactory
.Setup(c => c.Create(
It.IsAny<DocumentDto>(),
It.IsAny<List<ChargePriceOperationDto>>(),
It.IsAny<ValidationResult>()))
.Returns(chargePriceOperationsRejectedEvent);
SetupChargeRepository(chargeRepository, null);
SetupMarketParticipantRepository(marketParticipantRepository, sender);
SetupChargeIdentifierFactoryMock(chargeIdentifierFactory);

// Act
await sut.HandleAsync(receivedEvent);

// Assert
domainEventPublisher.Verify(
x => x.Publish(It.IsAny<ChargePriceOperationsRejectedEvent>()), Times.Once);
domainEventPublisher.VerifyNoOtherCalls();
}

[Theory]
[InlineAutoMoqData]
public async Task HandleAsync_WhenValidationFails_ThenValidationErrorsAreLogged(
Expand Down Expand Up @@ -228,7 +264,7 @@ private static void SetupChargeIdentifierFactoryMock(Mock<IChargeIdentifierFacto

private static void SetupChargeRepository(
[Frozen] Mock<IChargeRepository> chargeRepository,
Charge charge)
Charge? charge)
{
chargeRepository
.Setup(r => r.SingleOrNullAsync(It.IsAny<ChargeIdentifier>()))!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public async Task CreateRulesForChargeCommandAsync_WhenMeteringPointDoesNotExist
}

[Theory]
[InlineAutoMoqData(typeof(ChargeMustExistRule))]
[InlineAutoMoqData(typeof(ChargeMustExistOnLinkStartDateRule))]
public async Task CreateRulesForChargeCommandAsync_WhenChargeDoesNotExistForLinks_ReturnsExpectedMandatoryRules(
Type expectedRule,
[Frozen] Mock<IMeteringPointRepository> meteringPointRepository,
Expand All @@ -83,7 +83,7 @@ public async Task CreateRulesForChargeCommandAsync_WhenChargeDoesNotExistForLink
}

[Theory]
[InlineAutoMoqData(typeof(ChargeMustExistRule))]
[InlineAutoMoqData(typeof(ChargeMustExistOnLinkStartDateRule))]
[InlineAutoMoqData(typeof(ChargeLinksUpdateNotYetSupportedRule))]
public async Task CreateRulesForChargeCommandAsync_WhenChargeDoesExist_ReturnsExpectedMandatoryRulesForSingleChargeLinks(
Type expectedRule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ public class ChargeMustExistRuleTests
public void IsValid_WhenCalledWithCharge_ReturnsTrue(ChargeBuilder chargeBuilder)
{
var charge = chargeBuilder.Build();
var sut = new ChargeMustExistRule(charge);
var sut = new ChargeMustExistOnLinkStartDateRule(charge);
sut.IsValid.Should().BeTrue();
}

[Fact]
public void IsValid_WhenCalledWithNull_ReturnsFalse()
{
var sut = new ChargeMustExistRule(null);
var sut = new ChargeMustExistOnLinkStartDateRule(null);
sut.IsValid.Should().BeFalse();
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright 2020 Energinet DataHub A/S
//
// Licensed under the Apache License, Version 2.0 (the "License2");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using FluentAssertions;
using GreenEnergyHub.Charges.Domain.Dtos.ChargePriceCommands.Validation.BusinessValidation.ValidationRules;
using Xunit;
using Xunit.Categories;

namespace GreenEnergyHub.Charges.Tests.Domain.Dtos.ChargePriceCommands.Validation.BusinessRules.ValidationRules
{
[UnitTest]
public class ChargeDoesNotExistRuleTests
{
[Fact]
public void IsValid_ShouldAlwaysBeFalse()
{
// Arrange
var sut = new ChargeDoesNotExistRule();

// Act
var actual = sut.IsValid;

// Assert
actual.Should().BeFalse();
}
}
}
Loading

0 comments on commit 890261f

Please sign in to comment.