Skip to content

Commit 7457df3

Browse files
authored
Merge pull request #87 from SuessLabs/feature/86-CommandState-MultiMessage
#86 - Command State Multi-Message Subscriptions
2 parents a6c573c + eee8ed6 commit 7457df3

File tree

11 files changed

+301
-76
lines changed

11 files changed

+301
-76
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright Xeno Innovations, Inc. 2025
2+
// See the LICENSE file in the project root for more information.
3+
4+
using System;
5+
using System.Threading.Tasks;
6+
using Lite.StateMachine.Tests.TestData;
7+
using Lite.StateMachine.Tests.TestData.Models;
8+
using Lite.StateMachine.Tests.TestData.Services;
9+
using Lite.StateMachine.Tests.TestData.States.CommandL3DiStates;
10+
using Microsoft.Extensions.DependencyInjection;
11+
using Microsoft.Extensions.Logging;
12+
13+
namespace Lite.StateMachine.Tests.StateTests;
14+
15+
[TestClass]
16+
public class CommandStateTests : TestBase
17+
{
18+
[TestMethod]
19+
public async Task BasicState_Override_Executes_SuccessAsync()
20+
{
21+
// Assemble with Dependency Injection
22+
var services = new ServiceCollection()
23+
.AddLogging(InlineTraceLogger(LogLevel.Trace))
24+
.AddSingleton<IMessageService, MessageService>()
25+
.AddSingleton<IEventAggregator, EventAggregator>()
26+
.BuildServiceProvider();
27+
28+
var msgService = services.GetRequiredService<IMessageService>();
29+
var events = services.GetRequiredService<IEventAggregator>();
30+
Func<Type, object?> factory = t => ActivatorUtilities.CreateInstance(services, t);
31+
32+
var ctxProperties = new PropertyBag()
33+
{
34+
{ ParameterType.Counter, 0 },
35+
};
36+
37+
var machine = new StateMachine<StateId>(factory, events)
38+
{
39+
// Make sure we don't get stuck.
40+
// And send some message after leaving Command state
41+
// to make sure we unsubscribed successfully.
42+
DefaultStateTimeoutMs = 3000,
43+
IsContextPersistent = true,
44+
};
45+
46+
machine
47+
.RegisterState<State1>(StateId.State1, StateId.State2)
48+
.RegisterComposite<State2>(StateId.State2, initialChildStateId: StateId.State2_Sub1, onSuccess: StateId.State3)
49+
.RegisterSubState<State2_Sub1>(StateId.State2_Sub1, parentStateId: StateId.State2, onSuccess: StateId.State2_Sub2)
50+
.RegisterSubComposite<State2_Sub2>(StateId.State2_Sub2, parentStateId: StateId.State2, initialChildStateId: StateId.State2_Sub2_Sub1, onSuccess: StateId.State2_Sub3)
51+
.RegisterSubState<State2_Sub2_Sub1>(StateId.State2_Sub2_Sub1, parentStateId: StateId.State2_Sub2, onSuccess: StateId.State2_Sub2_Sub2)
52+
.RegisterSubState<State2_Sub2_Sub2>(StateId.State2_Sub2_Sub2, parentStateId: StateId.State2_Sub2, onSuccess: StateId.State2_Sub2_Sub3)
53+
.RegisterSubState<State2_Sub2_Sub3>(StateId.State2_Sub2_Sub3, parentStateId: StateId.State2_Sub2, onSuccess: null)
54+
.RegisterSubState<State2_Sub3>(StateId.State2_Sub3, parentStateId: StateId.State2, onSuccess: null)
55+
.RegisterState<State3>(StateId.State3, onSuccess: null);
56+
57+
events.Subscribe(msg =>
58+
{
59+
if (msg is ICustomCommand)
60+
{
61+
if (msg is UnlockCommand cmd)
62+
{
63+
// +100 check so we don't trigger this a 2nd time.
64+
if (cmd.Counter > 100 && cmd.Counter < 200)
65+
return;
66+
67+
// NOTE:
68+
// First we purposely publish 'OpenCommand' to prove that our OnMessage
69+
// filters out the bad message, followed by publishing the REAL message.
70+
if (cmd.Counter < 200)
71+
events.Publish(new UnlockCommand { Counter = cmd.Counter + 100 });
72+
73+
events.Publish(new UnlockResponse { Counter = cmd.Counter + 100 });
74+
75+
// NOTE: This doesn't reach State2_Sub2_Sub2 because it already left (GOOD)
76+
events.Publish(new CloseResponse { Counter = cmd.Counter + 100 });
77+
}
78+
}
79+
});
80+
81+
// Act - Start your engine!
82+
await machine.RunAsync(StateId.State1, ctxProperties, null, TestContext.CancellationToken);
83+
84+
// Assert Results
85+
Assert.IsNotNull(machine);
86+
Assert.IsNull(machine.Context);
87+
88+
Assert.AreEqual(29, msgService.Counter1);
89+
Assert.AreEqual(13, msgService.Counter2, "State2 Context.Param Count");
90+
Assert.AreEqual(12, msgService.Counter3);
91+
Assert.AreEqual(2, msgService.Counter4);
92+
}
93+
}

source/Lite.StateMachine.Tests/StateTests/CompositeStateTest.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ public class CompositeStateTest : TestBase
1919
public const string ParameterSubStateEntered = "SubEntered";
2020
public const string SUCCESS = "success";
2121

22-
public TestContext TestContext { get; set; }
23-
2422
[TestMethod]
2523
public async Task Level1_Basic_RegisterHelpers_SuccessTestAsync()
2624
{

source/Lite.StateMachine.Tests/StateTests/CustomStateTests.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ namespace Lite.StateMachine.Tests.StateTests;
1414
[TestClass]
1515
public class CustomStateTests : TestBase
1616
{
17-
public TestContext TestContext { get; set; }
18-
1917
[TestMethod]
2018
[DataRow(false, DisplayName = "Don't skip State3")]
2119
[DataRow(true, DisplayName = "Skip State3")]

source/Lite.StateMachine.Tests/StateTests/TestBase.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ namespace Lite.StateMachine.Tests.StateTests;
99

1010
public class TestBase
1111
{
12+
public TestContext TestContext { get; set; }
13+
1214
/// <summary>ILogger Helper for generating clean in-line logs.</summary>
1315
/// <param name="logLevel">Log level (Default: Trace).</param>
1416
/// <returns><see cref="ILoggingBuilder"/>.</returns>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright Xeno Innovations, Inc. 2025
2+
// See the LICENSE file in the project root for more information.
3+
4+
namespace Lite.StateMachine.Tests.TestData.Models;
5+
6+
#pragma warning disable SA1649 // File name should match first type name
7+
#pragma warning disable SA1402 // File may only contain a single type
8+
9+
/// <summary>Signifies it's one of our event packets.</summary>
10+
public interface ICustomCommand;
11+
12+
/// <summary>Sample command sent by state machine.</summary>
13+
public class UnlockCommand : ICustomCommand
14+
{
15+
public int Counter { get; set; } = 0;
16+
}
17+
18+
/// <summary>Sample command response received by state machine.</summary>
19+
public class UnlockResponse : ICustomCommand
20+
{
21+
public int Counter { get; set; } = 0;
22+
}
23+
24+
/// <summary>Sample command response received by state machine.</summary>
25+
public class CloseResponse : ICustomCommand
26+
{
27+
public int Counter { get; set; } = 0;
28+
}
29+
30+
#pragma warning restore SA1402 // File may only contain a single type
31+
#pragma warning restore SA1649 // File name should match first type name

source/Lite.StateMachine.Tests/TestData/Services/MessageService.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ public interface IMessageService
2121
/// <summary>Gets or sets the user's custom counter.</summary>
2222
int Counter3 { get; set; }
2323

24+
/// <summary>Gets or sets the user's custom counter.</summary>
25+
int Counter4 { get; set; }
26+
2427
/// <summary>Gets a list of user's custom messages.</summary>
2528
List<string> Messages { get; }
2629

@@ -40,6 +43,9 @@ public class MessageService : IMessageService
4043
/// <inheritdoc/>
4144
public int Counter3 { get; set; }
4245

46+
/// <inheritdoc/>
47+
public int Counter4 { get; set; }
48+
4349
/// <inheritdoc/>
4450
public List<string> Messages { get; } = [];
4551

0 commit comments

Comments
 (0)