Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add async validation to BRS-026 #124

Merged
merged 19 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions docs/ProcessManager.Client/ReleaseNotes/ReleaseNotes.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# ProcessManager.Client Release Notes

## Version 0.25.0

- Add `IBusinessValidatedDto` and `ValidationErrorDto` used to support business validation.

## Version 0.24.0

- Add `INotifyDataDto` type constraint to `NotifyOrchestrationInstanceV1`
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# ProcessManager.Orchestrations.Abstractions Release Notes

## Version 0.16.0

- Add `SettlementMethod` and `SettlementVersion` DataHub types.
- Update `RequestCalculatedEnergyTimeSeries` model with `IBusinessValidatedDto` interface.
- Update `RequestCalculatedEnergyTimeSeriesRejectedV1` properties to be a list of validation errors.

## Version 0.15.2

- Update dependent NuGet package.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// 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.

namespace Energinet.DataHub.ProcessManager.Abstractions.Components.BusinessValidation;

/// <summary>
/// Marker interface to add to types for which a BusinessValidator{T} should be registered
/// </summary>
public interface IBusinessValidatedDto;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// 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.

namespace Energinet.DataHub.ProcessManager.Abstractions.Components.BusinessValidation;

/// <summary>
/// Validation error containing an error message and an error code.
/// </summary>
public record ValidationErrorDto(string Message, string ErrorCode);
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<PropertyGroup>
<PackageId>Energinet.DataHub.ProcessManager.Abstractions</PackageId>
<PackageVersion>0.24.0$(VersionSuffix)</PackageVersion>
<PackageVersion>0.25.0$(VersionSuffix)</PackageVersion>
<Title>DH3 Process Manager Abstractions library</Title>
<Company>Energinet-DataHub</Company>
<Authors>Energinet-DataHub</Authors>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<PropertyGroup>
<PackageId>Energinet.DataHub.ProcessManager.Client</PackageId>
<PackageVersion>0.24.0$(VersionSuffix)</PackageVersion>
<PackageVersion>0.25.0$(VersionSuffix)</PackageVersion>
<Title>DH3 Process Manager Client library</Title>
<Company>Energinet-DataHub</Company>
<Authors>Energinet-DataHub</Authors>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@

<ItemGroup>
<ProjectReference Include="..\ProcessManager.Components\ProcessManager.Components.csproj" />
<ProjectReference Include="..\ProcessManager.Example.Orchestrations\ProcessManager.Example.Orchestrations.csproj" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// 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 System.Reflection;
using Energinet.DataHub.ProcessManager.Abstractions.Components.BusinessValidation;
using Energinet.DataHub.ProcessManager.Components.BusinessValidation;
using Energinet.DataHub.ProcessManager.Components.Extensions.DependencyInjection;
using Energinet.DataHub.ProcessManager.Example.Orchestrations.Abstractions.Processes.BRS_X03_ActorRequestProcessExample.V1;
using Energinet.DataHub.ProcessManager.Example.Orchestrations.Processes.BRS_X03_ActorRequestProcessExample.V1;
using Energinet.DataHub.ProcessManager.Example.Orchestrations.Processes.BRS_X03_ActorRequestProcessExample.V1.BusinessValidation.ValidationRules;
using FluentAssertions;
using FluentAssertions.Execution;
using Microsoft.Extensions.DependencyInjection;
using Xunit;

namespace Energinet.DataHub.ProcessManager.Components.Tests.Unit.Extensions.DependencyInjection;

public class BusinessValidationExtensionsTests
{
private readonly IServiceCollection _serviceCollection = new ServiceCollection();

private readonly Assembly _orchestrationsExampleAssembly = typeof(Orchestration_Brs_X03_V1).Assembly;
private readonly Assembly _orchestrationsExampleAbstractionsAssembly = typeof(ActorRequestProcessExampleInputV1).Assembly;

private readonly List<Type> _businessValidatorTypes =
[
typeof(BusinessValidator<ActorRequestProcessExampleInputV1>),
];

public BusinessValidationExtensionsTests()
{
_serviceCollection.AddLogging();
}

[Fact]
public void Given_BusinessValidatorTypes_When_GettingBusinessValidatedDtoTypesFromAssembly_Then_EachBusinessValidatedDtoTypeShouldHaveABusinessValidatorType()
{
// Given
// => Business validator types are registered in _businessValidatorTypes

// When
var businessValidatedDtoTypesFromAssembly = _orchestrationsExampleAbstractionsAssembly.GetTypes()
.Where(t =>
t is { IsClass: true, IsAbstract: false, IsGenericType: false }
&& t.IsAssignableTo(typeof(IBusinessValidatedDto)))
.ToList();

// Then
using var assertionScope = new AssertionScope();
foreach (var businessValidatedDtoType in businessValidatedDtoTypesFromAssembly)
{
var expectedValidatorType = typeof(BusinessValidator<>).MakeGenericType(businessValidatedDtoType);
_businessValidatorTypes.Should().ContainEquivalentOf(expectedValidatorType);
}
}

[Fact]
public void Given_BusinessValidationAddedForExampleAssemblies_When_ResolvingExpectedValidatorTypes_Then_ValidatorsCanBeResolved()
{
// Given
_serviceCollection.AddBusinessValidation(assembliesToScan: [_orchestrationsExampleAssembly, _orchestrationsExampleAbstractionsAssembly]);

var services = _serviceCollection.BuildServiceProvider();

// When
var resolveActions = _businessValidatorTypes
.Select<Type, Action>(businessValidatorType => () =>
{
services.GetRequiredService(businessValidatorType);
});

using var assertionScope = new AssertionScope();
foreach (var resolveAction in resolveActions)
{
resolveAction.Should().NotThrow("because the expected business validator should be resolvable in the service provider");
}
}

[Fact]
public void Given_BusinessValidationRulesAddedForExampleAssemblies_When_ResolvingAllValidationRules_Then_ContainsExpectedRules()
{
// Given
_serviceCollection.AddBusinessValidation(assembliesToScan: [_orchestrationsExampleAssembly, _orchestrationsExampleAbstractionsAssembly]);
var services = _serviceCollection.BuildServiceProvider();

List<Type> expectedValidationRuleTypes =
[
typeof(BusinessReasonValidationRule),
];

// When
var allResolvedValidationRules = new List<object?>();

// => Get all business validation rules for each type implementing IBusinessValidatedDto
var businessValidatedDtoTypes = _orchestrationsExampleAbstractionsAssembly.DefinedTypes
.Where(t =>
t is { IsClass: true, IsAbstract: false, IsGenericType: false }
&& t.IsAssignableTo(typeof(IBusinessValidatedDto)));
foreach (var businessValidatedDtoType in businessValidatedDtoTypes)
{
var interfaceTypeForBusinessValidatedDtoType = typeof(IBusinessValidationRule<>).MakeGenericType(businessValidatedDtoType);
var validationRulesForBusinessValidatedDtoType = services.GetServices(interfaceTypeForBusinessValidatedDtoType);
allResolvedValidationRules.AddRange(validationRulesForBusinessValidatedDtoType);
}

// Then
using var assertionScope = new AssertionScope();
foreach (var expectedValidationRuleType in expectedValidationRuleTypes)
{
allResolvedValidationRules.Should().ContainSingle(o => o!.GetType() == expectedValidationRuleType);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// 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 Energinet.DataHub.ProcessManager.Abstractions.Components;
using Energinet.DataHub.ProcessManager.Abstractions.Components.BusinessValidation;
using Microsoft.Extensions.Logging;

namespace Energinet.DataHub.ProcessManager.Components.BusinessValidation;

public class BusinessValidator<TInput>(
ILogger<BusinessValidator<TInput>> logger,
IEnumerable<IBusinessValidationRule<TInput>> validationRules)
where TInput : IBusinessValidatedDto
{
private readonly ILogger _logger = logger;
private readonly IReadOnlyCollection<IBusinessValidationRule<TInput>> _validationRules = validationRules.ToList();

/// <summary>
/// Perform all validation rules for the given input.
/// </summary>
public async Task<IReadOnlyCollection<ValidationError>> ValidateAsync(TInput subject)
{
if (subject == null)
throw new ArgumentNullException(nameof(subject));

var errors = new List<ValidationError>();
foreach (var rule in _validationRules)
{
errors.AddRange(await rule.ValidateAsync(subject).ConfigureAwait(false));
}

if (errors.Count > 0)
{
_logger.LogWarning(
"Validation failed for {SubjectType} type. Validation errors: {Errors}",
subject.GetType().Name,
errors);
}

return errors;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// 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.

namespace Energinet.DataHub.ProcessManager.Components.BusinessValidation.GridAreaOwner;

/// <summary>
/// Placeholder / mock until we can get grid area owners from Market Participant.
/// TODO: Replace with market participant client.
/// </summary>
public interface IGridAreaOwnerClient
{
// TODO: Remove or reintroduce GetCurrentOwnerAsync() implementation instead of IsCurrentOwnerAsync()
// Task<GridAreaOwner?> GetCurrentOwnerAsync(string gridArea, CancellationToken cancellationToken);
// public record GridAreaOwner(
// Guid Id,
// string GridAreaCode,
// string OwnerActorNumber,
// Instant ValidFrom,
// int SequenceNumber);

/// <summary>
/// Check if the given <paramref name="actorNumber"/> is the current owner of the given <paramref name="gridArea"/>.
/// </summary>
/// <returns>Returns true of the given <paramref name="actorNumber"/> is the current owner.</returns>
Task<bool> IsCurrentOwnerAsync(string gridArea, string actorNumber, CancellationToken cancellationToken);
}

/// <summary>
/// Mock of the <see cref="IGridAreaOwnerClient"/>, that always returns true.
/// </summary>
public class GridAreaOwnerMockClient : IGridAreaOwnerClient
{
public Task<bool> IsCurrentOwnerAsync(string gridArea, string actorNumber, CancellationToken cancellationToken)
{
return Task.FromResult(true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// 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.

namespace Energinet.DataHub.ProcessManager.Components.BusinessValidation.Helpers;

public static class ActorNumberValidationHelper
{
public static bool IsValidGlnNumber(string actorNumber)
{
return actorNumber.Length == 13;
}

public static bool IsValidEicNumber(string actorNumber)
{
return actorNumber.Length == 16;
}
}
Loading
Loading