From e15fb267be0a829a33f9a3249f160d910639cc36 Mon Sep 17 00:00:00 2001 From: Tim Bussmann Date: Tue, 6 Dec 2022 21:03:55 +0100 Subject: [PATCH] Populate originator property on saga creation (#457) (#464) * add failing tests * Set originator value when creating new saga --- .../Sagas/ReplyToOriginator.cs | 84 +++++++++++++++++++ src/NServiceBus.Testing/Sagas/TestableSaga.cs | 17 ++-- 2 files changed, 93 insertions(+), 8 deletions(-) create mode 100644 src/NServiceBus.Testing.Tests/Sagas/ReplyToOriginator.cs diff --git a/src/NServiceBus.Testing.Tests/Sagas/ReplyToOriginator.cs b/src/NServiceBus.Testing.Tests/Sagas/ReplyToOriginator.cs new file mode 100644 index 00000000..20a0fef5 --- /dev/null +++ b/src/NServiceBus.Testing.Tests/Sagas/ReplyToOriginator.cs @@ -0,0 +1,84 @@ +namespace NServiceBus.Testing.Tests.Sagas +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using NUnit.Framework; + + [TestFixture] + public class ReplyToOriginator + { + [Test] + public async Task ReplyToOriginatorShouldReplyToInitialOriginator() + { + const string originatorAddress = "expectedReplyAddress"; + + var saga = new TestableSaga(); + // the Originator value is populated by the header value, not the context property + await saga.Handle(new StartSagaMessage() { CorrelationProperty = Guid.NewGuid() }, messageHeaders: new Dictionary() + { + {Headers.ReplyToAddress, originatorAddress} + }); + var result = await saga.HandleQueuedMessage(); + + var reply = result.Context.RepliedMessages.SingleOrDefault(); + Assert.NotNull(reply); + Assert.AreEqual(originatorAddress, reply.Options.GetDestination()); + Assert.AreEqual(originatorAddress, reply.Message().OriginatorAddress); + } + + [Test] + public async Task OriginatorShouldBeSetByDefault() + { + // ensure the testing API also works without explicitly defining a replyTo header value + var saga = new TestableSaga(); + + await saga.Handle(new StartSagaMessage() { CorrelationProperty = Guid.NewGuid() }); + var result = await saga.HandleQueuedMessage(); + + var reply = result.Context.RepliedMessages.SingleOrDefault(); + Assert.NotNull(reply); + string replyAddress = reply.Options.GetDestination(); + Assert.NotNull(replyAddress); + Assert.AreEqual(replyAddress, reply.Message().OriginatorAddress); + } + + class ReplyingSaga : NServiceBus.Saga, IAmStartedByMessages, IHandleMessages + { + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) => mapper + .ConfigureMapping(m => m.CorrelationProperty) + .ToSaga(s => s.CorrelationProperty); + + public Task Handle(StartSagaMessage message, IMessageHandlerContext context) + { + return context.SendLocal(new SendReplyMessage()); + } + + public Task Handle(SendReplyMessage message, IMessageHandlerContext context) + { + return ReplyToOriginator(context, new ReplyMessage { OriginatorAddress = Data.Originator }); + } + } + + class ReplyingSagaData : ContainSagaData + { + public Guid CorrelationProperty { get; set; } + } + + class StartSagaMessage : IMessage + { + public Guid CorrelationProperty { get; set; } + } + + class SendReplyMessage : ICommand + { + public Guid CorrelationProperty { get; set; } + } + + class ReplyMessage : IMessage + { + public string OriginatorAddress { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing/Sagas/TestableSaga.cs b/src/NServiceBus.Testing/Sagas/TestableSaga.cs index eef24097..2a60cf8f 100644 --- a/src/NServiceBus.Testing/Sagas/TestableSaga.cs +++ b/src/NServiceBus.Testing/Sagas/TestableSaga.cs @@ -213,7 +213,7 @@ async Task InnerHandle(QueuedSagaMessage message, string handleMet { await session.Open(context.Extensions, context.CancellationToken).ConfigureAwait(false); - var (data, isNew, mappedValue) = await LoadSagaData(message, session, context.Extensions, context.CancellationToken).ConfigureAwait(false); + var (data, isNew, mappedValue) = await LoadSagaData(message, session, context).ConfigureAwait(false); saga.Entity = data; await sagaMapper.InvokeHandlerMethod(saga, handleMethodName, message, context).ConfigureAwait(false); @@ -279,16 +279,14 @@ async Task HandleTimeout(OutgoingMessage time } async Task<(TSagaData Data, bool IsNew, object MappedValue)> LoadSagaData( - QueuedSagaMessage message, ISynchronizedStorageSession session, ContextBag contextBag, CancellationToken cancellationToken) + QueuedSagaMessage message, ISynchronizedStorageSession session, TestableMessageHandlerContext context) { var messageMetadata = sagaMapper.GetMessageMetadata(message.Type); TSagaData sagaData; - if (message.Headers != null && - message.Headers.TryGetValue(Headers.SagaId, out var sagaIdString) && - Guid.TryParse(sagaIdString, out Guid sagaId)) + if (message.Headers.TryGetValue(Headers.SagaId, out var sagaIdString) && Guid.TryParse(sagaIdString, out Guid sagaId)) { - sagaData = await persister.Get(sagaId, session, contextBag, cancellationToken).ConfigureAwait(false); + sagaData = await persister.Get(sagaId, session, context.Extensions, context.CancellationToken).ConfigureAwait(false); if (sagaData != null) { return (sagaData, false, null); @@ -297,7 +295,7 @@ async Task HandleTimeout(OutgoingMessage time var messageMappedValue = sagaMapper.GetMessageMappedValue(message); - sagaData = await persister.Get(sagaMapper.CorrelationPropertyName, messageMappedValue, session, contextBag, cancellationToken).ConfigureAwait(false); + sagaData = await persister.Get(sagaMapper.CorrelationPropertyName, messageMappedValue, session, context.Extensions, context.CancellationToken).ConfigureAwait(false); if (sagaData != null) { @@ -306,7 +304,10 @@ async Task HandleTimeout(OutgoingMessage time if (messageMetadata.IsAllowedToStartSaga) { - sagaData = new TSagaData { Id = Guid.NewGuid() }; + var originatorAddress = message.Headers.TryGetValue(Headers.ReplyToAddress, out var replyAddress) + ? replyAddress + : context.ReplyToAddress; // This property has a default value set even when the header isn't set to require less setup for testing + sagaData = new TSagaData { Id = Guid.NewGuid(), Originator = originatorAddress }; sagaMapper.SetCorrelationPropertyValue(sagaData, messageMappedValue); return (sagaData, true, messageMappedValue); }