Skip to content

Commit

Permalink
Merge branch 'main' into madu/forward-metered-data-load-test
Browse files Browse the repository at this point in the history
  • Loading branch information
MadsDue authored Dec 13, 2024
2 parents b5b4e79 + 9e0e5c6 commit 4da57df
Show file tree
Hide file tree
Showing 20 changed files with 922 additions and 178 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,9 @@ private async Task<IReadOnlyCollection<ValidationError>> CheckTransactionIdsAsyn
{
var transactionId = series.TransactionId;

var errorsForSeries = await CheckTransactionIdAsync(
var errorsForSeries = CheckTransactionId(
transactionId,
message.SenderNumber,
transactionIdsToBeStored,
cancellationToken)
.ConfigureAwait(false);
transactionIdsToBeStored);

if (errorsForSeries is null)
{
Expand All @@ -188,34 +185,28 @@ private async Task<IReadOnlyCollection<ValidationError>> CheckTransactionIdsAsyn
}
}

var duplicatedTransactionIds = await transactionIdRepository
.GetDuplicatedTransactionIdsAsync(message.SenderNumber, transactionIdsToBeStored, cancellationToken)
.ConfigureAwait(false);
foreach (var duplicatedTransactionId in duplicatedTransactionIds)
{
errors.Add(new DuplicateTransactionIdDetected(duplicatedTransactionId));
}

return errors;
}

private async Task<ValidationError?> CheckTransactionIdAsync(
private ValidationError? CheckTransactionId(
string transactionId,
string senderNumber,
IReadOnlyCollection<string> transactionIdsToBeStored,
CancellationToken cancellationToken)
IReadOnlyCollection<string> transactionIdsToBeStored)
{
return transactionId switch
{
_ when string.IsNullOrEmpty(transactionId) => new EmptyTransactionId(),
_ when transactionId.Length > MaxTransactionIdLength => new InvalidTransactionIdSize(transactionId),
_ when await TransactionIdIsDuplicatedAsync(senderNumber, transactionId, cancellationToken)
.ConfigureAwait(false) => new DuplicateTransactionIdDetected(transactionId),
_ when transactionIdsToBeStored.Contains(transactionId) =>
new DuplicateTransactionIdDetected(transactionId),
_ => null,
};
}

private async Task<bool> TransactionIdIsDuplicatedAsync(
string senderNumber,
string transactionId,
CancellationToken cancellationToken)
{
return await transactionIdRepository
.TransactionIdExistsAsync(senderNumber, transactionId, cancellationToken)
.ConfigureAwait(false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ namespace Energinet.DataHub.EDI.IncomingMessages.Infrastructure.Repositories.Tra
public interface ITransactionIdRepository
{
/// <summary>
/// Checks if <paramref name="transactionId"/> is already registered by the sender <paramref name="senderId"/>
/// Returns a list of existing <paramref name="transactionIds"/> if they already is registered by the sender <paramref name="senderId"/>
/// </summary>
/// <param name="senderId"></param>
/// <param name="transactionId"></param>
/// <param name="transactionIds"></param>
/// <param name="cancellationToken"></param>
Task<bool> TransactionIdExistsAsync(string senderId, string transactionId, CancellationToken cancellationToken);
Task<IReadOnlyList<string>> GetDuplicatedTransactionIdsAsync(string senderId, IReadOnlyCollection<string> transactionIds, CancellationToken cancellationToken);

/// <summary>
/// Store transaction ids for the specified sender
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,17 @@ public TransactionIdRepository(IncomingMessagesContext incomingMessagesContext)
_incomingMessagesContext = incomingMessagesContext;
}

public async Task<bool> TransactionIdExistsAsync(
public async Task<IReadOnlyList<string>> GetDuplicatedTransactionIdsAsync(
string senderId,
string transactionId,
IReadOnlyCollection<string> transactionIds,
CancellationToken cancellationToken)
{
var transaction = await GetTransactionFromDbAsync(senderId, transactionId, cancellationToken).ConfigureAwait(false)
?? GetTransactionFromInMemoryCollection(senderId, transactionId);
var duplicatedTransactionIdsForSender = await GetDuplicatedTransactionsFromDbAsync(senderId, transactionIds, cancellationToken)
.ConfigureAwait(false);
if (!transactionIds.Any())
duplicatedTransactionIdsForSender = GetDuplicatedTransactionsFromInMemoryCollection(senderId, transactionIds);

return transaction != null;
return duplicatedTransactionIdsForSender.Select(x => x.TransactionId).ToList();
}

public async Task AddAsync(
Expand All @@ -50,19 +52,26 @@ public async Task AddAsync(
}
}

private TransactionIdForSender? GetTransactionFromInMemoryCollection(string senderId, string transactionId)
private IReadOnlyList<TransactionIdForSender> GetDuplicatedTransactionsFromInMemoryCollection(
string senderId,
IReadOnlyCollection<string> transactionIds)
{
return _incomingMessagesContext.TransactionIdForSenders.Local
.FirstOrDefault(x => x.TransactionId == transactionId && x.SenderId == senderId);
.Where(
transactionIdForSender => transactionIds.Contains(transactionIdForSender.TransactionId)
&& transactionIdForSender.SenderId == senderId)
.ToList();
}

private async Task<TransactionIdForSender?> GetTransactionFromDbAsync(string senderId, string transactionId, CancellationToken cancellationToken)
private async Task<IReadOnlyList<TransactionIdForSender>> GetDuplicatedTransactionsFromDbAsync(
string senderId,
IReadOnlyCollection<string> transactionIds,
CancellationToken cancellationToken)
{
return await _incomingMessagesContext.TransactionIdForSenders
.FirstOrDefaultAsync(
transactionIdForSender => transactionIdForSender.TransactionId == transactionId
&& transactionIdForSender.SenderId == senderId,
cancellationToken)
.Where(transactionIdForSender => transactionIds.Contains(transactionIdForSender.TransactionId)
&& transactionIdForSender.SenderId == senderId)
.ToListAsync(cancellationToken)
.ConfigureAwait(false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,37 @@ public async Task When_MultipleTransactionsWithSameId_Then_ResultContainExcepted
result.Errors.Should().Contain(error => error is DuplicateTransactionIdDetected);
}

[Fact]
public async Task When_MultipleTransactionsWithSameIdAsExisting_Then_ResultContainExceptedValidationError()
{
var documentFormat = DocumentFormat.Ebix;
var existingTransactionIdForSender = "123456";
var newTransactionIdForSender = "654321";
await StoreTransactionIdForActorAsync(existingTransactionIdForSender, _actorIdentity.ActorNumber.Value);
var message = MeteredDataForMeasurementPointBuilder.CreateIncomingMessage(
documentFormat,
_actorIdentity.ActorNumber,
[
(existingTransactionIdForSender,
Instant.FromUtc(2024, 1, 1, 0, 0),
Instant.FromUtc(2024, 1, 2, 0, 0),
Resolution.QuarterHourly),
(newTransactionIdForSender,
Instant.FromUtc(2024, 1, 1, 0, 0),
Instant.FromUtc(2024, 1, 2, 0, 0),
Resolution.QuarterHourly),
]);

var (incomingMessage, _) = await ParseMessageAsync(message.Stream, documentFormat);
var result = await _validateIncomingMessage.ValidateAsync(
incomingMessage!,
documentFormat,
CancellationToken.None);

result.Success.Should().BeFalse();
result.Errors.Should().Contain(error => error is DuplicateTransactionIdDetected);
}

[Fact]
public async Task When_TransactionIdIsEmpty_Then_ResultContainExceptedValidationError()
{
Expand Down Expand Up @@ -618,6 +649,17 @@ public async Task When_BusinessTypeIsNotAllowed_Then_ExpectedValidationError()
throw new NotSupportedException($"No message parser found for message format '{documentFormat}' and document type '{IncomingDocumentType.NotifyValidatedMeasureData}'");
}

private async Task StoreTransactionIdForActorAsync(string existingTransactionIdForSender, string senderActorNumber)
{
var databaseConnectionFactory = GetService<IDatabaseConnectionFactory>();
using var dbConnection = await databaseConnectionFactory.GetConnectionAndOpenAsync(CancellationToken.None).ConfigureAwait(false);

await dbConnection.ExecuteAsync(
"INSERT INTO [dbo].[TransactionRegistry] ([TransactionId], [SenderId]) VALUES (@TransactionId, @SenderId)",
new { TransactionId = existingTransactionIdForSender, SenderId = senderActorNumber })
.ConfigureAwait(false);
}

private async Task StoreMessageIdForActorAsync(string messageId, string senderActorNumber)
{
var databaseConnectionFactory = GetService<IDatabaseConnectionFactory>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using System.Diagnostics.CodeAnalysis;
using System.Text;
using Energinet.DataHub.EDI.BuildingBlocks.Domain.Models;
using Energinet.DataHub.EDI.IntegrationTests.Fixtures;
using Energinet.DataHub.EDI.OutgoingMessages.IntegrationTests.DocumentAsserters;
using Energinet.DataHub.EDI.OutgoingMessages.Interfaces.Models.Peek;
using Energinet.DataHub.EDI.Tests.Infrastructure.OutgoingMessages.RSM012;
using FluentAssertions;
using FluentAssertions.Execution;
using NodaTime;
Expand All @@ -26,6 +28,10 @@

namespace Energinet.DataHub.EDI.IntegrationTests.Behaviours.IncomingRequests;

[SuppressMessage(
"StyleCop.CSharp.ReadabilityRules",
"SA1118:Parameter should not span multiple lines",
Justification = "Readability")]
public sealed class GivenMeteredDataForMeasurementPointTests(
IntegrationTestFixture integrationTestFixture,
ITestOutputHelper testOutputHelper)
Expand All @@ -50,15 +56,18 @@ public async Task When_ActorPeeksAllMessages_Then_ReceivesOneDocumentWithCorrect

var transactionIdPrefix = Guid.NewGuid().ToString("N");

var transactionId1 = $"{transactionIdPrefix}-1";
var transactionId2 = $"{transactionIdPrefix}-2";

await GivenReceivedMeteredDataForMeasurementPoint(
documentFormat: DocumentFormat.Xml,
senderActorNumber: currentActor.ActorNumber,
[
($"{transactionIdPrefix}-1",
(transactionId1,
InstantPattern.General.Parse("2024-11-28T13:51:42Z").Value,
InstantPattern.General.Parse("2024-11-29T09:15:28Z").Value,
Resolution.Hourly),
($"{transactionIdPrefix}-2",
(transactionId2,
InstantPattern.General.Parse("2024-11-24T18:51:58Z").Value,
InstantPattern.General.Parse("2024-11-25T03:39:45Z").Value,
Resolution.QuarterHourly),
Expand All @@ -75,12 +84,111 @@ await GivenReceivedMeteredDataForMeasurementPoint(
peekFormat);

// Assert
peekResults.Should().HaveCount(2);

foreach (var peekResultDto in peekResults)
{
// This is not pretty, but it works for now
var foo = new StreamReader(peekResultDto.Bundle);
var content = await foo.ReadToEndAsync();
var isTransOne = content.Contains(transactionId1);
peekResultDto.Bundle.Position = 0;

await ThenNotifyValidatedMeasureDataDocumentIsCorrect(
peekResultDto.Bundle,
peekFormat,
new NotifyValidatedMeasureDataDocumentAssertionInput());
new NotifyValidatedMeasureDataDocumentAssertionInput(
new RequiredHeaderDocumentFields(
"E23",
"8100000000115",
"A10",
"5790001330552",
"A10",
"DGL",
"DDQ",
"2024-07-01T14:57:09Z"),
new OptionalHeaderDocumentFields(
"23",
[
isTransOne
? new AssertSeriesDocumentFieldsInput(
1,
new RequiredSeriesFields(
TransactionId.From(string.Join(string.Empty, transactionId1.Reverse())),
"579999993331812345",
"A10",
"E17",
"KWH",
new RequiredPeriodDocumentFields(
"PT1H",
"2024-11-28T13:51Z",
"2024-11-29T09:15Z",
Enumerable.Range(1, 24)
.Select(
i => new AssertPointDocumentFieldsInput(
new RequiredPointDocumentFields(i),
new OptionalPointDocumentFields("A03", 1000 + i)))
.ToList())),
new OptionalSeriesFields(
transactionId1,
"2022-12-17T09:30:47Z",
null,
null,
"8716867000030"))
: new AssertSeriesDocumentFieldsInput(
1,
new RequiredSeriesFields(
TransactionId.From(string.Join(string.Empty, transactionId2.Reverse())),
"579999993331812345",
"A10",
"E17",
"KWH",
new RequiredPeriodDocumentFields(
"PT15M",
"2024-11-24T18:51Z",
"2024-11-25T03:39Z",
Enumerable.Range(1, 96)
.Select(
i => new AssertPointDocumentFieldsInput(
new RequiredPointDocumentFields(i),
new OptionalPointDocumentFields("A03", 1000 + i)))
.ToList())),
new OptionalSeriesFields(
transactionId2,
"2022-12-17T09:30:47Z",
null,
null,
"8716867000030")),
])));
}
}

[Theory]
[MemberData(nameof(PeekFormats))]
public async Task AndGiven_MessageIsEmpty_When_ActorPeeksAllMessages_Then_ReceivesNoMessages(
DocumentFormat peekFormat)
{
// Arrange
var senderSpy = CreateServiceBusSenderSpy();
var currentActor = (ActorNumber: ActorNumber.Create("1111111111111"), ActorRole: ActorRole.GridAccessProvider);

GivenNowIs(Instant.FromUtc(2024, 7, 1, 14, 57, 09));
GivenAuthenticatedActorIs(currentActor.ActorNumber, currentActor.ActorRole);

await GivenReceivedMeteredDataForMeasurementPoint(
documentFormat: DocumentFormat.Xml,
senderActorNumber: currentActor.ActorNumber,
[]);

await WhenMeteredDataForMeasurementPointProcessIsInitialized(senderSpy.LatestMessage!);

// Act
var peekResults = await WhenActorPeeksAllMessages(
ActorNumber.Create("8100000000115"),
ActorRole.EnergySupplier,
peekFormat);

// Assert
peekResults.Should().BeEmpty();
}
}
Loading

0 comments on commit 4da57df

Please sign in to comment.