Skip to content

Commit c27acae

Browse files
authored
Allow multiple RegisterOrReplace calls for the same pipeline step identifier (#7507)
1 parent 8116a01 commit c27acae

File tree

13 files changed

+549
-431
lines changed

13 files changed

+549
-431
lines changed

src/NServiceBus.Core.Tests/Pipeline/PipelineModelBuilderTests.cs

Lines changed: 122 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,18 @@
1010
public class PipelineModelBuilderTests
1111
{
1212
[Test]
13-
public void ShouldDetectConflictingStepRegistrations()
13+
public void ShouldReturnEmptyStepListWhenNoRegistrations()
14+
{
15+
var builder = ConfigurePipelineModelBuilder.Setup()
16+
.Build(typeof(IParentContext));
17+
18+
var model = builder.Build();
19+
20+
Assert.That(model.Count, Is.Zero);
21+
}
22+
23+
[Test]
24+
public void ShouldDetectConflictingStepRegistrationsFromRegisterSteps()
1425
{
1526
var builder = ConfigurePipelineModelBuilder.Setup()
1627
.Register(RegisterStep.Create("Root1", typeof(RootBehavior), "desc"))
@@ -22,6 +33,41 @@ public void ShouldDetectConflictingStepRegistrations()
2233
Assert.AreEqual("Step registration with id 'Root1' is already registered for 'NServiceBus.Core.Tests.Pipeline.PipelineModelBuilderTests+RootBehavior'.", ex.Message);
2334
}
2435

36+
[Test]
37+
public void ShouldReplaceWithLastReplaceStepWhenMultipleReplaceStepsExistForTheSameStepId()
38+
{
39+
var builder = ConfigurePipelineModelBuilder.Setup()
40+
.Register(RegisterStep.Create("Root1", typeof(RootBehavior), "desc"))
41+
.Replace(new ReplaceStep("Root1", typeof(SomeBehaviorOfParentContext), "desc"))
42+
.Replace(new ReplaceStep("Root1", typeof(AnotherBehaviorOfParentContext), "desc"))
43+
.Build(typeof(IParentContext));
44+
45+
var model = builder.Build();
46+
47+
Assert.That(model, Has.Count.EqualTo(1));
48+
var overriddenBehavior = model.FirstOrDefault(x => x.StepId == "Root1");
49+
Assert.That(overriddenBehavior, Is.Not.Null);
50+
Assert.That(overriddenBehavior.BehaviorType, Is.EqualTo(typeof(AnotherBehaviorOfParentContext)));
51+
}
52+
53+
[Test]
54+
public void ShouldUseLastRegistrationWhenReplaceAndRegisterOrReplaceCallsAreMixed()
55+
{
56+
var builder = ConfigurePipelineModelBuilder.Setup()
57+
.Register(RegisterStep.Create("Root1", typeof(RootBehavior), "desc"))
58+
.Replace(new ReplaceStep("Root1", typeof(SomeBehaviorOfParentContext), "desc"))
59+
.RegisterOrReplace(RegisterOrReplaceStep.Create("Root1", typeof(AnotherBehaviorOfParentContext), "desc"))
60+
.Build(typeof(IParentContext));
61+
62+
var model = builder.Build();
63+
64+
Assert.That(model, Has.Count.EqualTo(1));
65+
var overriddenBehavior = model.FirstOrDefault(x => x.StepId == "Root1");
66+
Assert.That(overriddenBehavior, Is.Not.Null);
67+
Assert.That(overriddenBehavior.BehaviorType, Is.EqualTo(typeof(AnotherBehaviorOfParentContext)));
68+
}
69+
70+
2571
[Test]
2672
public void ShouldOnlyAllowReplacementOfExistingRegistrations()
2773
{
@@ -32,7 +78,24 @@ public void ShouldOnlyAllowReplacementOfExistingRegistrations()
3278

3379
var ex = Assert.Throws<Exception>(() => builder.Build());
3480

35-
Assert.AreEqual("Multiple replacements of the same pipeline behaviour is not supported. Make sure that you only register a single replacement for 'DoesNotExist'.", ex.Message);
81+
Assert.That(ex.Message, Is.EqualTo("'DoesNotExist' cannot be replaced because it does not exist. Make sure that you only register a replacement for existing pipeline behaviors."));
82+
}
83+
84+
[Test]
85+
public void ShouldReplaceExistingRegistrations()
86+
{
87+
var builder = ConfigurePipelineModelBuilder.Setup()
88+
.Register(RegisterStep.Create("Root1", typeof(RootBehavior), "desc"))
89+
.Register(RegisterStep.Create("SomeBehaviorOfParentContext", typeof(SomeBehaviorOfParentContext), "desc"))
90+
.Replace(new ReplaceStep("SomeBehaviorOfParentContext", typeof(AnotherBehaviorOfParentContext), "desc"))
91+
.Build(typeof(IParentContext));
92+
93+
var model = builder.Build();
94+
95+
Assert.That(model, Has.Count.EqualTo(2));
96+
var replacedBehavior = model.FirstOrDefault(x => x.StepId == "SomeBehaviorOfParentContext");
97+
Assert.That(replacedBehavior, Is.Not.Null);
98+
Assert.That(replacedBehavior.BehaviorType, Is.EqualTo(typeof(AnotherBehaviorOfParentContext)));
3699
}
37100

38101
[Test]
@@ -45,7 +108,7 @@ public void ShouldAddWhenAddingOrReplacingABehaviorThatDoesntExist()
45108

46109
var model = builder.Build();
47110

48-
Assert.That(model.Count, Is.EqualTo(2));
111+
Assert.That(model, Has.Count.EqualTo(2));
49112
var addedBehavior = model.FirstOrDefault(x => x.StepId == "SomeBehaviorOfParentContext");
50113
Assert.That(addedBehavior, Is.Not.Null);
51114
Assert.That(addedBehavior.BehaviorType, Is.EqualTo(typeof(SomeBehaviorOfParentContext)));
@@ -63,6 +126,24 @@ public void ShouldReplaceWhenAddingOrReplacingABehaviorThatDoesAlreadyExist()
63126
var model = builder.Build();
64127

65128
Assert.That(model.Count, Is.EqualTo(2));
129+
Assert.That(model, Has.Count.EqualTo(2));
130+
var overriddenBehavior = model.FirstOrDefault(x => x.StepId == "SomeBehaviorOfParentContext");
131+
Assert.That(overriddenBehavior, Is.Not.Null);
132+
Assert.That(overriddenBehavior.BehaviorType, Is.EqualTo(typeof(AnotherBehaviorOfParentContext)));
133+
}
134+
135+
[Test]
136+
public void ShouldReplaceWithLastRegisterReplaceStepWhenMultipleRegisterOrReplaceStepsExistForTheSameStepId()
137+
{
138+
var builder = ConfigurePipelineModelBuilder.Setup()
139+
.Register(RegisterStep.Create("Root1", typeof(RootBehavior), "desc"))
140+
.RegisterOrReplace(RegisterOrReplaceStep.Create("SomeBehaviorOfParentContext", typeof(SomeBehaviorOfParentContext), "desc"))
141+
.RegisterOrReplace(RegisterOrReplaceStep.Create("SomeBehaviorOfParentContext", typeof(AnotherBehaviorOfParentContext), "desc"))
142+
.Build(typeof(IParentContext));
143+
144+
var model = builder.Build();
145+
146+
Assert.That(model, Has.Count.EqualTo(2));
66147
var overriddenBehavior = model.FirstOrDefault(x => x.StepId == "SomeBehaviorOfParentContext");
67148
Assert.That(overriddenBehavior, Is.Not.Null);
68149
Assert.That(overriddenBehavior.BehaviorType, Is.EqualTo(typeof(AnotherBehaviorOfParentContext)));
@@ -94,6 +175,19 @@ public void ShouldDetectConflictingStageConnectors()
94175
Assert.AreEqual("Multiple stage connectors found for stage 'NServiceBus.Core.Tests.Pipeline.PipelineModelBuilderTests+IParentContext'. Remove one of: 'NServiceBus.Core.Tests.Pipeline.PipelineModelBuilderTests+ParentContextToChildContextConnector', 'NServiceBus.Core.Tests.Pipeline.PipelineModelBuilderTests+ParentContextToChildContextNotInheritedFromParentContextConnector'", ex.Message);
95176
}
96177

178+
[Test]
179+
public void ShouldDetectMissingStageConnectors()
180+
{
181+
var builder = ConfigurePipelineModelBuilder.Setup()
182+
.Register(RegisterStep.Create("Root1", typeof(RootBehavior), "desc"))
183+
.Register(RegisterStep.Create("ChildContextToChildContextNotInheritedFromParentContextConnector", typeof(ChildContextToChildContextNotInheritedFromParentContextConnector), "desc"))
184+
.Build(typeof(IParentContext));
185+
186+
var ex = Assert.Throws<Exception>(() => builder.Build());
187+
188+
Assert.That(ex.Message, Is.EqualTo("No stage connector found for stage 'NServiceBus.Core.Tests.Pipeline.PipelineModelBuilderTests+IParentContext'."));
189+
}
190+
97191
[Test]
98192
public void ShouldDetectNonExistingInsertAfterRegistrations()
99193
{
@@ -141,7 +235,7 @@ public void ShouldDetectRegistrationsWithContextsReachableFromTheRootContext()
141235

142236
var model = builder.Build();
143237

144-
Assert.AreEqual(3, model.Count);
238+
Assert.That(model, Has.Count.EqualTo(3));
145239
}
146240

147241
[Test]
@@ -155,7 +249,7 @@ public void ShouldDetectRegistrationsWithContextsNotReachableFromTheRootContext(
155249

156250
var model = builder.Build();
157251

158-
Assert.AreEqual(2, model.Count);
252+
Assert.That(model, Has.Count.EqualTo(2));
159253
}
160254

161255
[Test]
@@ -170,49 +264,43 @@ public void ShouldHandleTheTerminator()
170264

171265
var model = builder.Build();
172266

173-
Assert.AreEqual(3, model.Count);
267+
Assert.That(model, Has.Count.EqualTo(3));
174268
}
175269

176270
class ConfigurePipelineModelBuilder
177271
{
178-
List<RegisterStep> registrations = new List<RegisterStep>();
179-
List<RegisterOrReplaceStep> registerOrReplacements = new List<RegisterOrReplaceStep>();
180-
List<ReplaceStep> replacements = new List<ReplaceStep>();
272+
PipelineModifications pipelineModifications;
181273

182-
public static ConfigurePipelineModelBuilder Setup()
183-
{
184-
return new ConfigurePipelineModelBuilder();
185-
}
274+
ConfigurePipelineModelBuilder(PipelineModifications pipelineModifications) => this.pipelineModifications = pipelineModifications;
275+
276+
public static ConfigurePipelineModelBuilder Setup() => new(new PipelineModifications());
186277

187278
public ConfigurePipelineModelBuilder Register(RegisterStep registration)
188279
{
189-
registrations.Add(registration);
280+
pipelineModifications.AddAddition(registration);
190281
return this;
191282
}
192283

193284
public ConfigurePipelineModelBuilder Replace(ReplaceStep registration)
194285
{
195-
replacements.Add(registration);
286+
pipelineModifications.AddReplacement(registration);
196287
return this;
197288
}
198289

199290
public ConfigurePipelineModelBuilder RegisterOrReplace(RegisterOrReplaceStep registration)
200291
{
201-
registerOrReplacements.Add(registration);
292+
pipelineModifications.AddAdditionOrReplacement(registration);
202293
return this;
203294
}
204295

205-
public PipelineModelBuilder Build(Type parentContextType)
206-
{
207-
return new PipelineModelBuilder(parentContextType, registrations, replacements, registerOrReplacements);
208-
}
296+
public PipelineModelBuilder Build(Type parentContextType) => new(parentContextType, pipelineModifications.Additions, pipelineModifications.Replacements, pipelineModifications.AdditionsOrReplacements);
209297
}
210298

211299
interface IParentContext : IBehaviorContext { }
212300

213301
class ParentContext : BehaviorContext, IParentContext
214302
{
215-
public ParentContext(IBehaviorContext parentContext)
303+
protected ParentContext(IBehaviorContext parentContext)
216304
: base(parentContext)
217305
{
218306
}
@@ -240,66 +328,47 @@ public ChildContextNotInheritedFromParentContext(IBehaviorContext parentContext)
240328

241329
class ParentContextToChildContextConnector : StageConnector<IParentContext, IChildContext>
242330
{
243-
public override Task Invoke(IParentContext context, Func<IChildContext, Task> stage)
244-
{
245-
throw new NotImplementedException();
246-
}
331+
public override Task Invoke(IParentContext context, Func<IChildContext, Task> stage) => throw new NotImplementedException();
247332
}
248333

249334
class Terminator : PipelineTerminator<IChildContext>
250335
{
251-
protected override Task Terminate(IChildContext context)
252-
{
253-
throw new NotImplementedException();
254-
}
336+
protected override Task Terminate(IChildContext context) => throw new NotImplementedException();
255337
}
256338

257339
class ParentContextToChildContextNotInheritedFromParentContextConnector : StageConnector<IParentContext, IChildContextNotInheritedFromParentContext>
258340
{
259-
public override Task Invoke(IParentContext context, Func<IChildContextNotInheritedFromParentContext, Task> stage)
260-
{
261-
throw new NotImplementedException();
262-
}
341+
public override Task Invoke(IParentContext context, Func<IChildContextNotInheritedFromParentContext, Task> stage) => throw new NotImplementedException();
342+
}
343+
344+
class ChildContextToChildContextNotInheritedFromParentContextConnector : StageConnector<IChildContext, IChildContextNotInheritedFromParentContext>
345+
{
346+
public override Task Invoke(IChildContext context, Func<IChildContextNotInheritedFromParentContext, Task> stage) => throw new NotImplementedException();
263347
}
264348

265349
class SomeBehaviorOfParentContext : IBehavior<IParentContext, IParentContext>
266350
{
267-
public Task Invoke(IParentContext context, Func<IParentContext, Task> next)
268-
{
269-
throw new NotImplementedException();
270-
}
351+
public Task Invoke(IParentContext context, Func<IParentContext, Task> next) => throw new NotImplementedException();
271352
}
272353

273354
class AnotherBehaviorOfParentContext : IBehavior<IParentContext, IParentContext>
274355
{
275-
public Task Invoke(IParentContext context, Func<IParentContext, Task> next)
276-
{
277-
throw new NotImplementedException();
278-
}
356+
public Task Invoke(IParentContext context, Func<IParentContext, Task> next) => throw new NotImplementedException();
279357
}
280358

281359
class RootBehavior : IBehavior<IParentContext, IParentContext>
282360
{
283-
public Task Invoke(IParentContext context, Func<IParentContext, Task> next)
284-
{
285-
throw new NotImplementedException();
286-
}
361+
public Task Invoke(IParentContext context, Func<IParentContext, Task> next) => throw new NotImplementedException();
287362
}
288363

289364
class ChildBehaviorOfChildContext : IBehavior<IChildContext, IChildContext>
290365
{
291-
public Task Invoke(IChildContext context, Func<IChildContext, Task> next)
292-
{
293-
throw new NotImplementedException();
294-
}
366+
public Task Invoke(IChildContext context, Func<IChildContext, Task> next) => throw new NotImplementedException();
295367
}
296368

297369
class ChildBehaviorOfChildContextNotInheritedFromParentContext : IBehavior<IChildContextNotInheritedFromParentContext, IChildContextNotInheritedFromParentContext>
298370
{
299-
public Task Invoke(IChildContextNotInheritedFromParentContext context, Func<IChildContextNotInheritedFromParentContext, Task> next)
300-
{
301-
throw new NotImplementedException();
302-
}
371+
public Task Invoke(IChildContextNotInheritedFromParentContext context, Func<IChildContextNotInheritedFromParentContext, Task> next) => throw new NotImplementedException();
303372
}
304373
}
305374

src/NServiceBus.Core.Tests/Pipeline/PipelineTests.cs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ public async Task ShouldExecutePipeline()
2323
var stringWriter = new StringWriter();
2424

2525
var pipelineModifications = new PipelineModifications();
26-
pipelineModifications.Additions.Add(new Behavior1.Registration(stringWriter));
27-
pipelineModifications.Additions.Add(new Stage1.Registration(stringWriter));
28-
pipelineModifications.Additions.Add(new Behavior2.Registration(stringWriter));
29-
pipelineModifications.Additions.Add(new StageFork.Registration(stringWriter));
30-
pipelineModifications.Additions.Add(new Stage2.Registration(stringWriter));
31-
pipelineModifications.Additions.Add(new Terminator.Registration(stringWriter));
26+
pipelineModifications.AddAddition(new Behavior1.Registration(stringWriter));
27+
pipelineModifications.AddAddition(new Stage1.Registration(stringWriter));
28+
pipelineModifications.AddAddition(new Behavior2.Registration(stringWriter));
29+
pipelineModifications.AddAddition(new StageFork.Registration(stringWriter));
30+
pipelineModifications.AddAddition(new Stage2.Registration(stringWriter));
31+
pipelineModifications.AddAddition(new Terminator.Registration(stringWriter));
3232

3333
var pipeline = new Pipeline<ITransportReceiveContext>(new ServiceCollection().BuildServiceProvider(), pipelineModifications);
3434

@@ -46,12 +46,12 @@ public async Task ShouldNotCacheContext()
4646
var stringWriter = new StringWriter();
4747

4848
var pipelineModifications = new PipelineModifications();
49-
pipelineModifications.Additions.Add(new Behavior1.Registration(stringWriter));
50-
pipelineModifications.Additions.Add(new Stage1.Registration(stringWriter));
51-
pipelineModifications.Additions.Add(new Behavior2.Registration(stringWriter));
52-
pipelineModifications.Additions.Add(new StageFork.Registration(stringWriter));
53-
pipelineModifications.Additions.Add(new Stage2.Registration(stringWriter));
54-
pipelineModifications.Additions.Add(new Terminator.Registration(stringWriter));
49+
pipelineModifications.AddAddition(new Behavior1.Registration(stringWriter));
50+
pipelineModifications.AddAddition(new Stage1.Registration(stringWriter));
51+
pipelineModifications.AddAddition(new Behavior2.Registration(stringWriter));
52+
pipelineModifications.AddAddition(new StageFork.Registration(stringWriter));
53+
pipelineModifications.AddAddition(new Stage2.Registration(stringWriter));
54+
pipelineModifications.AddAddition(new Terminator.Registration(stringWriter));
5555

5656
var pipeline = new Pipeline<ITransportReceiveContext>(new ServiceCollection().BuildServiceProvider(), pipelineModifications);
5757

@@ -101,10 +101,10 @@ public async Task ShouldCacheExecutionFunc()
101101
var stringWriter = new StringWriter();
102102

103103
var pipelineModifications = new PipelineModifications();
104-
pipelineModifications.Additions.Add(new Behavior1.Registration(stringWriter));
105-
pipelineModifications.Additions.Add(new Stage1.Registration(stringWriter));
106-
pipelineModifications.Additions.Add(new Behavior2.Registration(stringWriter));
107-
pipelineModifications.Additions.Add(new StageFork.Registration(stringWriter));
104+
pipelineModifications.AddAddition(new Behavior1.Registration(stringWriter));
105+
pipelineModifications.AddAddition(new Stage1.Registration(stringWriter));
106+
pipelineModifications.AddAddition(new Behavior2.Registration(stringWriter));
107+
pipelineModifications.AddAddition(new StageFork.Registration(stringWriter));
108108

109109
var pipeline = new Pipeline<ITransportReceiveContext>(new ServiceCollection().BuildServiceProvider(), pipelineModifications);
110110

0 commit comments

Comments
 (0)