diff --git a/REstate.IoC.Ninject/NinjectComponentContainer.cs b/REstate.IoC.Ninject/NinjectComponentContainer.cs new file mode 100644 index 0000000..45fc15b --- /dev/null +++ b/REstate.IoC.Ninject/NinjectComponentContainer.cs @@ -0,0 +1,71 @@ +using System; +using System.Linq; +using Ninject; +using Ninject.Activation; + +namespace REstate.IoC.Ninject +{ + public class NinjectComponentContainer + : IComponentContainer + { + public NinjectComponentContainer(IKernel kernel) + { + Kernel = kernel; + } + + protected IKernel Kernel { get; } + + public void Register(T instance, string name = null) where T : class + { + var binding = Kernel.Bind().ToConstant(instance); + + if(name is null) + return; + + binding.Named(name); + } + + public void Register(Type registrationType, Type implementationType, string name = null) + { + var binding = Kernel.Bind(registrationType).To(implementationType); + + if(name is null) + return; + + binding.Named(name); + } + + public void Register(FactoryMethod resolver, string name = null) where T : class + { + T Resolve(IContext context) => resolver(this); + + var binding = Kernel.Bind().ToMethod(Resolve); + + if(name is null) + return; + + binding.Named(name); + } + + public void RegisterComponent(IComponent component) + { + component.Register(this); + } + + public T Resolve(string name = null) where T : class + => name is null ? Kernel.Get() : Kernel.Get(name); + + public T[] ResolveAll() where T : class => + Kernel.GetAll().ToArray(); + + void IRegistrar.Register(string name) + { + var binding = Kernel.Bind().To(); + + if(name is null) + return; + + binding.Named(name); + } + } +} diff --git a/REstate.IoC.Ninject/REstate.IoC.Ninject.csproj b/REstate.IoC.Ninject/REstate.IoC.Ninject.csproj new file mode 100644 index 0000000..5c0a61a --- /dev/null +++ b/REstate.IoC.Ninject/REstate.IoC.Ninject.csproj @@ -0,0 +1,29 @@ + + + + netstandard2.0;net45 + Ovan Crone + Psibernetic Solutions + Ninject adapter for REstate. + Ovan Crone 2016 + https://github.com/psibr/REstate/blob/v6.0/LICENSE + 3.0.0 + 3.0.0.0 + 3.0.0.0 + latest + https://github.com/psibr/REstate + https://github.com/psibr/REstate + git + REstate + https://github.com/psibr/REstate/blob/master/assets/icons/REstate-64.png?raw=true + + + + + + + + + + + diff --git a/REstate.sln b/REstate.sln index 40651aa..22c5d7b 100644 --- a/REstate.sln +++ b/REstate.sln @@ -51,6 +51,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Icons", "Icons", "{EE8E30B1 assets\icons\REstate.svg = assets\icons\REstate.svg EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "REstate.IoC.Ninject", "REstate.IoC.Ninject\REstate.IoC.Ninject.csproj", "{0E9D2031-6525-4280-9961-045137637FB3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "REstate.IoC.Ninject.Tests", "test\REstate.IoC.Ninject.Tests\REstate.IoC.Ninject.Tests.csproj", "{57415F9E-37A5-439E-ADF2-8CE40AB5B267}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -105,6 +109,14 @@ Global {D699BD4C-9005-4475-A958-3A0AC2C6F575}.Debug|Any CPU.Build.0 = Debug|Any CPU {D699BD4C-9005-4475-A958-3A0AC2C6F575}.Release|Any CPU.ActiveCfg = Release|Any CPU {D699BD4C-9005-4475-A958-3A0AC2C6F575}.Release|Any CPU.Build.0 = Release|Any CPU + {0E9D2031-6525-4280-9961-045137637FB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0E9D2031-6525-4280-9961-045137637FB3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E9D2031-6525-4280-9961-045137637FB3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0E9D2031-6525-4280-9961-045137637FB3}.Release|Any CPU.Build.0 = Release|Any CPU + {57415F9E-37A5-439E-ADF2-8CE40AB5B267}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {57415F9E-37A5-439E-ADF2-8CE40AB5B267}.Debug|Any CPU.Build.0 = Debug|Any CPU + {57415F9E-37A5-439E-ADF2-8CE40AB5B267}.Release|Any CPU.ActiveCfg = Release|Any CPU + {57415F9E-37A5-439E-ADF2-8CE40AB5B267}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -124,6 +136,8 @@ Global {5F424F7B-4840-414D-9FA3-7700D81AD898} = {88B4B54C-3DA1-40D2-8DCB-0181DD035955} {D699BD4C-9005-4475-A958-3A0AC2C6F575} = {90E8D167-048D-467B-81D5-1007247226A3} {EE8E30B1-F341-4924-8B26-401DA680D213} = {85E6545C-48B6-40EB-A6C7-827E16255AA6} + {0E9D2031-6525-4280-9961-045137637FB3} = {90E8D167-048D-467B-81D5-1007247226A3} + {57415F9E-37A5-439E-ADF2-8CE40AB5B267} = {88B4B54C-3DA1-40D2-8DCB-0181DD035955} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {0C35B35D-40C1-4521-BF0A-C5A6D7BFF4CD} diff --git a/src/Examples/Client-Server/Client/Program.cs b/src/Examples/Client-Server/Client/Program.cs index 1b3433a..05289b6 100644 --- a/src/Examples/Client-Server/Client/Program.cs +++ b/src/Examples/Client-Server/Client/Program.cs @@ -20,13 +20,13 @@ static async Task Main(string[] args) Log.Logger = logger; - REstateHost.Agent.Configuration - .RegisterComponent(new GrpcRemoteHostComponent( - new GrpcHostOptions - { - Channel = new Channel("localhost", 12345, ChannelCredentials.Insecure), - UseAsDefaultEngine = true - })); + //REstateHost.Agent.Configuration + // .RegisterComponent(new GrpcRemoteHostComponent( + // new GrpcHostOptions + // { + // Channel = new Channel("localhost", 12345, ChannelCredentials.Insecure), + // UseAsDefaultEngine = true + // })); var stateEngine = REstateHost.Agent .GetStateEngine(); diff --git a/src/REstate.Concurrency.Primitives/REstate.Concurrency.Primitives.csproj b/src/REstate.Concurrency.Primitives/REstate.Concurrency.Primitives.csproj index 6d1d8ef..611999a 100644 --- a/src/REstate.Concurrency.Primitives/REstate.Concurrency.Primitives.csproj +++ b/src/REstate.Concurrency.Primitives/REstate.Concurrency.Primitives.csproj @@ -6,13 +6,13 @@ Psibernetic Solutions Concurrency primitives such as Semaphores and mutexes build on REstate Machines. Ovan Crone 2016 - https://opensource.org/licenses/MIT + https://github.com/psibr/REstate/blob/v6.0/LICENSE 3.0.0 3.0.0.0 3.0.0.0 latest - https://github.com/psibr/REstate.Engine - https://github.com/psibr/REstate.Engine + https://github.com/psibr/REstate + https://github.com/psibr/REstate git REstate https://github.com/psibr/REstate/blob/master/assets/icons/REstate-64.png?raw=true diff --git a/src/REstate.Engine.Repositories.EntityFrameworkCore/REstate.Engine.Repositories.EntityFrameworkCore.csproj b/src/REstate.Engine.Repositories.EntityFrameworkCore/REstate.Engine.Repositories.EntityFrameworkCore.csproj index 72da50e..b5ef6b3 100644 --- a/src/REstate.Engine.Repositories.EntityFrameworkCore/REstate.Engine.Repositories.EntityFrameworkCore.csproj +++ b/src/REstate.Engine.Repositories.EntityFrameworkCore/REstate.Engine.Repositories.EntityFrameworkCore.csproj @@ -6,13 +6,13 @@ Psibernetic Solutions Entity Framework Core repository for REstate Machines and Schematics. Ovan Crone 2016 - https://opensource.org/licenses/MIT + https://github.com/psibr/REstate/blob/v6.0/LICENSE 3.0.0 3.0.0.0 3.0.0.0 latest - https://github.com/psibr/REstate.Engine - https://github.com/psibr/REstate.Engine + https://github.com/psibr/REstate + https://github.com/psibr/REstate git REstate 2.0.0 diff --git a/src/REstate.Engine.Repositories.Redis/REstate.Engine.Repositories.Redis.csproj b/src/REstate.Engine.Repositories.Redis/REstate.Engine.Repositories.Redis.csproj index a39ab29..1973f84 100644 --- a/src/REstate.Engine.Repositories.Redis/REstate.Engine.Repositories.Redis.csproj +++ b/src/REstate.Engine.Repositories.Redis/REstate.Engine.Repositories.Redis.csproj @@ -9,9 +9,9 @@ REstate Redis storage provider for REstate's engine. Ovan Crone 2016 - https://opensource.org/licenses/MIT - https://github.com/psibr/REstate.Engine - https://github.com/psibr/REstate.Engine + https://github.com/psibr/REstate/blob/v6.0/LICENSE + https://github.com/psibr/REstate + https://github.com/psibr/REstate git REstate True diff --git a/src/REstate.Remote/REstate.Remote.csproj b/src/REstate.Remote/REstate.Remote.csproj index 05fe021..e675afc 100644 --- a/src/REstate.Remote/REstate.Remote.csproj +++ b/src/REstate.Remote/REstate.Remote.csproj @@ -5,8 +5,8 @@ 3.0.0.0 3.0.0.0 3.0.0 - https://github.com/psibr/REstate.Engine - https://github.com/psibr/REstate.Engine + https://github.com/psibr/REstate + https://github.com/psibr/REstate git REstate Ovan Crone @@ -14,7 +14,7 @@ REstate Provides gRPC/HTTP2 interconnect support for REstate's engine in either client or server configuration. Ovan Crone 2016 - https://opensource.org/licenses/MIT + https://github.com/psibr/REstate/blob/v6.0/LICENSE latest https://github.com/psibr/REstate/blob/master/assets/icons/REstate-64.png?raw=true diff --git a/src/REstate/Engine/Connectors/ConnectorConfiguration.cs b/src/REstate/Engine/Connectors/ConnectorConfiguration.cs new file mode 100644 index 0000000..91d6231 --- /dev/null +++ b/src/REstate/Engine/Connectors/ConnectorConfiguration.cs @@ -0,0 +1,13 @@ +namespace REstate.Engine.Connectors +{ + public class ConnectorConfiguration + : IConnectorConfiguration + { + public ConnectorConfiguration(string identifier) + { + Identifier = identifier; + } + + public string Identifier { get; } + } +} \ No newline at end of file diff --git a/src/REstate/Engine/Connectors/Resolution/IConnectorResolver.cs b/src/REstate/Engine/Connectors/Resolution/IConnectorResolver.cs index 6a3e342..cb814fb 100644 --- a/src/REstate/Engine/Connectors/Resolution/IConnectorResolver.cs +++ b/src/REstate/Engine/Connectors/Resolution/IConnectorResolver.cs @@ -35,10 +35,15 @@ public DefaultConnectorResolver( var mappings = connectorTypeToIdentifierMappings.ToList(); var entryConnectorMappings = mappings - .Join(inner: entryConnectors, - outerKeySelector: mapping => mapping.ConnectorType, - innerKeySelector: connector => connector.GetType().GetGenericTypeDefinition(), - resultSelector: (mapping, connector) => (mapping.Identifier, Connector: connector)) + .Join(inner: entryConnectors.Where(connector => connector != null), + outerKeySelector: mapping => + mapping.ConnectorType, + innerKeySelector: connector => + connector.GetType().IsConstructedGenericType + ? connector.GetType().GetGenericTypeDefinition() + : connector.GetType(), + resultSelector: (mapping, connector) => + (mapping.Identifier, Connector: connector)) .ToList(); EntryConnectors = entryConnectorMappings @@ -54,9 +59,11 @@ public DefaultConnectorResolver( .ToLookup(tuple => tuple.Identifier, tuple => tuple.Connector, connectorStringComparer); GuardianConnectors = mappings - .Join(inner: guardianConnectors, + .Join(inner: guardianConnectors.Where(connector => connector != null), outerKeySelector: mapping => mapping.ConnectorType, - innerKeySelector: connector => connector.GetType().GetGenericTypeDefinition(), + innerKeySelector: connector => connector.GetType().IsConstructedGenericType + ? connector.GetType().GetGenericTypeDefinition() + : connector.GetType(), resultSelector: (mapping, connector) => (mapping.Identifier, Connector: connector)) .ToLookup(kvp => kvp.Identifier, kvp => kvp.Connector, connectorStringComparer); } diff --git a/src/REstate/Engine/REstateMachine.cs b/src/REstate/Engine/REstateMachine.cs index 60debbe..e7308c8 100644 --- a/src/REstate/Engine/REstateMachine.cs +++ b/src/REstate/Engine/REstateMachine.cs @@ -138,13 +138,19 @@ public async Task> SendAsync( var entryConnector = _connectorResolver.ResolveEntryConnector(schematicState.OnEntry.ConnectorKey); - await entryConnector.OnEntryAsync( - schematic: Schematic, - machine: this, - status: currentStatus, - inputParameters: new InputParameters(input, payload), - connectorSettings: schematicState.OnEntry.Settings, - cancellationToken: cancellationToken).ConfigureAwait(false); + InterceptorMachine interceptor; + using (interceptor = new InterceptorMachine(this, currentStatus)) + { + await entryConnector.OnEntryAsync( + schematic: Schematic, + machine: interceptor, + status: currentStatus, + inputParameters: new InputParameters(input, payload), + connectorSettings: schematicState.OnEntry.Settings, + cancellationToken: cancellationToken).ConfigureAwait(false); + } + + currentStatus = interceptor.CurrentStatus; } catch { @@ -218,5 +224,107 @@ protected async Task> GetCurrentStateAsync(CancellationToken canc public override string ToString() => $"{Schematic.SchematicName}/{MachineId}"; + + private class InterceptorMachine + : IStateMachine + , IDisposable + { + private bool _isDisposed; + private Status _currentStatus; + + public InterceptorMachine(IStateMachine machine, Status currentStatus) + { + _isDisposed = false; + Machine = machine; + CurrentStatus = currentStatus; + } + + public string MachineId => Machine.MachineId; + + private IStateMachine Machine { get; } + + public Status CurrentStatus + { + get => _currentStatus; + private set + { + lock (this) + { + if(value.CommitNumber > _currentStatus.CommitNumber) + _currentStatus = value; + } + } + } + + public Task> GetMetadataAsync( + CancellationToken cancellationToken = default) + => Machine.GetMetadataAsync(cancellationToken); + + public Task> GetSchematicAsync( + CancellationToken cancellationToken = default) + => Machine.GetSchematicAsync(cancellationToken); + + public async Task> SendAsync( + TInput input, + TPayload payload, + CancellationToken cancellationToken = default) + { + if(_isDisposed) throw new ObjectDisposedException("IStateMachine<,>", "Cannot send input at this point as final state has been captured."); + + var status = await Machine.SendAsync(input, payload, cancellationToken); + + CurrentStatus = status; + + return status; + } + + public async Task> SendAsync( + TInput input, + TPayload payload, + long lastCommitNumber, + CancellationToken cancellationToken = default) + { + if(_isDisposed) throw new ObjectDisposedException("IStateMachine<,>", "Cannot send input at this point as final state has been captured."); + + var status = await Machine.SendAsync(input, payload, lastCommitNumber, cancellationToken); + + CurrentStatus = status; + + return status; + } + + public async Task> SendAsync( + TInput input, + CancellationToken cancellationToken = default) + { + if(_isDisposed) throw new ObjectDisposedException("IStateMachine<,>", "Cannot send input at this point as final state has been captured."); + + var status = await Machine.SendAsync(input, cancellationToken); + + CurrentStatus = status; + + return status; + } + + public async Task> SendAsync( + TInput input, + long lastCommitNumber, + CancellationToken cancellationToken = default) + { + if(_isDisposed) throw new ObjectDisposedException("IStateMachine<,>", "Cannot send input at this point as final state has been captured."); + + var status = await Machine.SendAsync(input, lastCommitNumber, cancellationToken); + + CurrentStatus = status; + + return status; + } + + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + public void Dispose() + { + _isDisposed = true; + } + } } } \ No newline at end of file diff --git a/src/REstate/HostConfiguration.cs b/src/REstate/HostConfiguration.cs index d1f5774..b25b91b 100644 --- a/src/REstate/HostConfiguration.cs +++ b/src/REstate/HostConfiguration.cs @@ -12,6 +12,8 @@ namespace REstate public interface IHostConfiguration { void RegisterComponent(IComponent component); + + void RegisterComponent() where TComponent : IComponent, new(); } internal class HostConfiguration @@ -29,6 +31,12 @@ public HostConfiguration(IComponentContainer container) public void RegisterComponent(IComponent component) => Container.RegisterComponent(component); + public void RegisterComponent() + where TComponent : IComponent, new() + { + Container.RegisterComponent(new TComponent()); + } + /// /// Registers defaults and the REstate engine. /// diff --git a/src/REstate/IoC/HostConfigurationExtensions.cs b/src/REstate/IoC/HostConfigurationExtensions.cs new file mode 100644 index 0000000..9713545 --- /dev/null +++ b/src/REstate/IoC/HostConfigurationExtensions.cs @@ -0,0 +1,35 @@ +using REstate.Engine.Connectors; + +namespace REstate.IoC +{ + public static class HostConfigurationExtensions + { + public static void RegisterConnector( + this IHostConfiguration hostConfiguration, + IConnectorConfiguration connectorConfiguration = null, + string registrationName = null) where TConnector : IConnector + { + hostConfiguration.RegisterComponent(new SingleConnectorComponent(connectorConfiguration, registrationName)); + } + + private class SingleConnectorComponent + : IComponent + where TConnector : IConnector + { + private readonly IConnectorConfiguration _connectorConfiguration; + private readonly string _registrationName; + + public SingleConnectorComponent(IConnectorConfiguration connectorConfiguration, string registrationName = null) + { + _connectorConfiguration = connectorConfiguration; + _registrationName = registrationName; + } + + public void Register(IRegistrar registrar) + { + registrar.RegisterConnector(_connectorConfiguration ?? new ConnectorConfiguration(typeof(TConnector).FullName), _registrationName); + + } + } + } +} \ No newline at end of file diff --git a/src/REstate/IoC/RegistrarExtensions.cs b/src/REstate/IoC/RegistrarExtensions.cs index e368514..44b3df7 100644 --- a/src/REstate/IoC/RegistrarExtensions.cs +++ b/src/REstate/IoC/RegistrarExtensions.cs @@ -10,6 +10,21 @@ namespace REstate.IoC { public static class RegistrarExtensions { + public static IConnectorRegistration RegisterConnector(this IRegistrar registrar, string registrationName = null) + where TConnector : IConnector + { + return registrar.RegisterConnector(typeof(TConnector), registrationName); + } + + public static void RegisterConnector( + this IRegistrar registrar, + IConnectorConfiguration configuration, + string registrationName = null) + where TConnector : IConnector + { + registrar.RegisterConnector(registrationName).WithConfiguration(configuration); + } + /// /// Registers an or . /// @@ -36,7 +51,7 @@ public static class RegistrarExtensions /// public static IConnectorRegistration RegisterConnector(this IRegistrar registrar, Type connectorType, string registrationName = null) { - var registrationKey = registrationName ?? connectorType.AssemblyQualifiedName; + var registrationKey = registrationName ?? connectorType.FullName; var interfaces = connectorType .GetInterfaces() @@ -93,7 +108,7 @@ public IConnectorRegistration WithConfiguration(TConfiguration c public static void RegisterEventListener(this IRegistrar registrar, IEventListener listener) { - registrar.Register(listener, listener.GetType().AssemblyQualifiedName); + registrar.Register(listener, listener.GetType().FullName); } } } \ No newline at end of file diff --git a/src/REstate/REstate.csproj b/src/REstate/REstate.csproj index 7826431..6edde3f 100644 --- a/src/REstate/REstate.csproj +++ b/src/REstate/REstate.csproj @@ -6,13 +6,13 @@ Psibernetic Solutions Create state-machines for complicated flows, in a serializable and pluggable system. Ovan Crone 2016 - https://opensource.org/licenses/MIT + https://github.com/psibr/REstate/blob/master/LICENSE 3.0.0 3.0.0.0 3.0.0.0 latest - https://github.com/psibr/REstate.Engine - https://github.com/psibr/REstate.Engine + https://github.com/psibr/REstate + https://github.com/psibr/REstate git REstate https://github.com/psibr/REstate/blob/master/assets/icons/REstate-64.png?raw=true diff --git a/src/REstate/Schematics/Builder/IStateBuilder.cs b/src/REstate/Schematics/Builder/IStateBuilder.cs index 0106a47..e71573b 100644 --- a/src/REstate/Schematics/Builder/IStateBuilder.cs +++ b/src/REstate/Schematics/Builder/IStateBuilder.cs @@ -1,4 +1,5 @@ using System; +using REstate.Engine.Connectors; namespace REstate.Schematics.Builder { @@ -18,5 +19,7 @@ public interface IStateBuilder IStateBuilder WithReentrance(TInput input, Action> transition = null); IStateBuilder WithOnEntry(ConnectorKey connectorKey, Action> onEntry = null); + + IStateBuilder WithOnEntry(Action> onEntry = null) where TConnector : IEntryConnector; } } \ No newline at end of file diff --git a/src/REstate/Schematics/Builder/Implementation/StateBuilder.cs b/src/REstate/Schematics/Builder/Implementation/StateBuilder.cs index cf4481d..45af80c 100644 --- a/src/REstate/Schematics/Builder/Implementation/StateBuilder.cs +++ b/src/REstate/Schematics/Builder/Implementation/StateBuilder.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using REstate.Engine.Connectors; namespace REstate.Schematics.Builder.Implementation { @@ -99,5 +100,10 @@ public IStateBuilder WithOnEntry( return this; } + + public IStateBuilder WithOnEntry( + Action> onEntry = null) + where TConnector : IEntryConnector + => WithOnEntry(typeof(TConnector).FullName, onEntry); } } \ No newline at end of file diff --git a/test/REstate.Engine.Repositories.EntityFrameworkCore.Tests/Features/MachineCreation.cs b/test/REstate.Engine.Repositories.EntityFrameworkCore.Tests/Features/MachineCreation.cs index 68f1b91..0066201 100644 --- a/test/REstate.Engine.Repositories.EntityFrameworkCore.Tests/Features/MachineCreation.cs +++ b/test/REstate.Engine.Repositories.EntityFrameworkCore.Tests/Features/MachineCreation.cs @@ -25,6 +25,7 @@ protected override Task Given_host_configuration_is_applied() .DefineNew() .WithContext(Context) .AddAsyncSteps( + _ => _.Given_a_new_host(), _ => _.Given_EntityFrameworkCore_is_the_registered_repository()) .Build()); } diff --git a/test/REstate.Engine.Repositories.EntityFrameworkCore.Tests/Features/MachineDeletion.cs b/test/REstate.Engine.Repositories.EntityFrameworkCore.Tests/Features/MachineDeletion.cs index 6995e66..7924932 100644 --- a/test/REstate.Engine.Repositories.EntityFrameworkCore.Tests/Features/MachineDeletion.cs +++ b/test/REstate.Engine.Repositories.EntityFrameworkCore.Tests/Features/MachineDeletion.cs @@ -25,6 +25,7 @@ protected override Task Given_host_configuration_is_applied() .DefineNew() .WithContext(Context) .AddAsyncSteps( + _ => _.Given_a_new_host(), _ => _.Given_EntityFrameworkCore_is_the_registered_repository()) .Build()); } diff --git a/test/REstate.Engine.Repositories.EntityFrameworkCore.Tests/Features/MachineRetrieval.cs b/test/REstate.Engine.Repositories.EntityFrameworkCore.Tests/Features/MachineRetrieval.cs index cc563d2..b3cd2a3 100644 --- a/test/REstate.Engine.Repositories.EntityFrameworkCore.Tests/Features/MachineRetrieval.cs +++ b/test/REstate.Engine.Repositories.EntityFrameworkCore.Tests/Features/MachineRetrieval.cs @@ -25,6 +25,7 @@ protected override Task Given_host_configuration_is_applied() .DefineNew() .WithContext(Context) .AddAsyncSteps( + _ => _.Given_a_new_host(), _ => _.Given_EntityFrameworkCore_is_the_registered_repository()) .Build()); } diff --git a/test/REstate.IoC.Ninject.Tests/Features/MachineCreation.cs b/test/REstate.IoC.Ninject.Tests/Features/MachineCreation.cs new file mode 100644 index 0000000..63bc066 --- /dev/null +++ b/test/REstate.IoC.Ninject.Tests/Features/MachineCreation.cs @@ -0,0 +1,34 @@ +using System.Threading.Tasks; +using LightBDD.Framework; +using LightBDD.Framework.Scenarios.Contextual; +using LightBDD.Framework.Scenarios.Extended; +using Ninject; +using REstate.Tests.Features.Context; +using REstate.Tests.Features.Templates; + +// ReSharper disable InconsistentNaming + +namespace REstate.IoC.Ninject.Tests.Features +{ + [FeatureDescription(@" +In order to support IoC using my perferred DI container +As a developer +I want to create machines.")] + [ScenarioCategory("Machine Creation")] + [ScenarioCategory("IoC")] + [ScenarioCategory("Ninject")] + public class MachineCreation + : MachineCreationScenarios> + { + protected override Task Given_host_configuration_is_applied() + { + return Task.FromResult( + CompositeStep + .DefineNew() + .WithContext(Context) + .AddAsyncSteps( + _ => _.Given_a_new_host_with_custom_ComponentContainer(new NinjectComponentContainer(new StandardKernel()))) + .Build()); + } + } +} diff --git a/test/REstate.IoC.Ninject.Tests/Features/MachineDeletion.cs b/test/REstate.IoC.Ninject.Tests/Features/MachineDeletion.cs new file mode 100644 index 0000000..3c34677 --- /dev/null +++ b/test/REstate.IoC.Ninject.Tests/Features/MachineDeletion.cs @@ -0,0 +1,35 @@ +using System.Threading.Tasks; +using LightBDD.Framework; +using LightBDD.Framework.Scenarios.Contextual; +using LightBDD.Framework.Scenarios.Extended; +using Ninject; +using REstate.IoC.Ninject; +using REstate.Tests.Features.Context; +using REstate.Tests.Features.Templates; + +// ReSharper disable InconsistentNaming + +namespace REstate.IoC.Ninject.Tests.Features +{ + [FeatureDescription(@" +In order to support IoC using my perferred DI container +As a developer +I want to delete machines.")] + [ScenarioCategory("Machine Deletion")] + [ScenarioCategory("IoC")] + [ScenarioCategory("Ninject")] + public class MachineDeletion + : MachineDeletionScenarios> + { + protected override Task Given_host_configuration_is_applied() + { + return Task.FromResult( + CompositeStep + .DefineNew() + .WithContext(Context) + .AddAsyncSteps( + _ => _.Given_a_new_host_with_custom_ComponentContainer(new NinjectComponentContainer(new StandardKernel()))) + .Build()); + } + } +} diff --git a/test/REstate.IoC.Ninject.Tests/Features/MachineRetrieval.cs b/test/REstate.IoC.Ninject.Tests/Features/MachineRetrieval.cs new file mode 100644 index 0000000..696ab30 --- /dev/null +++ b/test/REstate.IoC.Ninject.Tests/Features/MachineRetrieval.cs @@ -0,0 +1,35 @@ +using System.Threading.Tasks; +using LightBDD.Framework; +using LightBDD.Framework.Scenarios.Contextual; +using LightBDD.Framework.Scenarios.Extended; +using Ninject; +using REstate.IoC.Ninject; +using REstate.Tests.Features.Context; +using REstate.Tests.Features.Templates; + +// ReSharper disable InconsistentNaming + +namespace REstate.IoC.Ninject.Tests.Features +{ + [FeatureDescription(@" +In order to support IoC using my perferred DI container +As a developer +I want to retrieve machines.")] + [ScenarioCategory("Machine Retrieval")] + [ScenarioCategory("IoC")] + [ScenarioCategory("Ninject")] + public class MachineRetrieval + : MachineRetrievalScenarios> + { + protected override Task Given_host_configuration_is_applied() + { + return Task.FromResult( + CompositeStep + .DefineNew() + .WithContext(Context) + .AddAsyncSteps( + _ => _.Given_a_new_host_with_custom_ComponentContainer(new NinjectComponentContainer(new StandardKernel()))) + .Build()); + } + } +} diff --git a/test/REstate.IoC.Ninject.Tests/Properties/AssemblyInfo.cs b/test/REstate.IoC.Ninject.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..86a9819 --- /dev/null +++ b/test/REstate.IoC.Ninject.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using LightBDD.XUnit2; + +[assembly:LightBddScope] diff --git a/test/REstate.IoC.Ninject.Tests/REstate.IoC.Ninject.Tests.csproj b/test/REstate.IoC.Ninject.Tests/REstate.IoC.Ninject.Tests.csproj new file mode 100644 index 0000000..a31f6c4 --- /dev/null +++ b/test/REstate.IoC.Ninject.Tests/REstate.IoC.Ninject.Tests.csproj @@ -0,0 +1,29 @@ + + + + netcoreapp2.0 + latest + false + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/REstate.IoC.Ninject.Tests/Units/NinjectComponentContainerTests.cs b/test/REstate.IoC.Ninject.Tests/Units/NinjectComponentContainerTests.cs new file mode 100644 index 0000000..53dbaac --- /dev/null +++ b/test/REstate.IoC.Ninject.Tests/Units/NinjectComponentContainerTests.cs @@ -0,0 +1,26 @@ +using Ninject; +using REstate.Engine.Connectors; +using Xunit; + +namespace REstate.IoC.Ninject.Tests.Units +{ + public class NinjectComponentContainerTests + { + [Fact] + public void CanResolveMultiple() + { + // ARRANGE + var container = new NinjectComponentContainer(new StandardKernel()); + + // ReSharper disable once ObjectCreationAsStatement + new REstateHost(container); + + // ACT + var connectors = container.ResolveAll>(); + + // ASSERT + Assert.NotNull(connectors); + Assert.NotEmpty(connectors); + } + } +} diff --git a/test/REstate.Remote.Tests/Features/MachineCreation.cs b/test/REstate.Remote.Tests/Features/MachineCreation.cs index 0427753..642e0e9 100644 --- a/test/REstate.Remote.Tests/Features/MachineCreation.cs +++ b/test/REstate.Remote.Tests/Features/MachineCreation.cs @@ -26,6 +26,7 @@ protected override Task Given_host_configuration_is_applied() .DefineNew() .WithContext(Context) .AddAsyncSteps( + _ => _.Given_a_new_host(), _ => _.Given_a_REstate_gRPC_Server_running(), _ => _.Given_the_default_agent_is_gRPC_remote()) .Build()); diff --git a/test/REstate.Remote.Tests/Features/MachineDeletion.cs b/test/REstate.Remote.Tests/Features/MachineDeletion.cs index e459451..6c1978f 100644 --- a/test/REstate.Remote.Tests/Features/MachineDeletion.cs +++ b/test/REstate.Remote.Tests/Features/MachineDeletion.cs @@ -26,6 +26,7 @@ protected override Task Given_host_configuration_is_applied() .DefineNew() .WithContext(Context) .AddAsyncSteps( + _ => _.Given_a_new_host(), _ => _.Given_a_REstate_gRPC_Server_running(), _ => _.Given_the_default_agent_is_gRPC_remote()) .Build()); diff --git a/test/REstate.Remote.Tests/Features/MachineRetrieval.cs b/test/REstate.Remote.Tests/Features/MachineRetrieval.cs index 18013c7..8bb09dc 100644 --- a/test/REstate.Remote.Tests/Features/MachineRetrieval.cs +++ b/test/REstate.Remote.Tests/Features/MachineRetrieval.cs @@ -26,6 +26,7 @@ protected override Task Given_host_configuration_is_applied() .DefineNew() .WithContext(Context) .AddAsyncSteps( + _ => _.Given_a_new_host(), _ => _.Given_a_REstate_gRPC_Server_running(), _ => _.Given_the_default_agent_is_gRPC_remote()) .Build()); diff --git a/test/REstate.Tests/Features/Templates/MachineCreationScenarios.cs b/test/REstate.Tests/Features/Templates/MachineCreationScenarios.cs index d1fb7e0..97f0b19 100644 --- a/test/REstate.Tests/Features/Templates/MachineCreationScenarios.cs +++ b/test/REstate.Tests/Features/Templates/MachineCreationScenarios.cs @@ -27,7 +27,6 @@ public async Task A_Machine_can_be_created_from_a_Schematic() var schematicName = uniqueId; await Runner.WithContext(Context).RunScenarioAsync( - _ => _.Given_a_new_host(), _ => Given_host_configuration_is_applied(), _ => _.Given_a_Schematic_with_an_initial_state_INITIALSTATE(schematicName, "Initial"), _ => _.When_a_Machine_is_created_from_a_Schematic(_.CurrentSchematic), @@ -43,7 +42,6 @@ public async Task A_Machine_can_be_created_from_a_SchematicName() var schematicName = uniqueId; await Runner.WithContext(Context).RunScenarioAsync( - _ => _.Given_a_new_host(), _ => Given_host_configuration_is_applied(), _ => _.Given_a_Schematic_with_an_initial_state_INITIALSTATE(schematicName, "Initial"), _ => _.Given_a_Schematic_is_stored(_.CurrentSchematic), @@ -61,7 +59,6 @@ public async Task A_Machine_can_be_created_from_a_Schematic_with_a_predefined_Ma var machineId = uniqueId; await Runner.WithContext(Context).RunScenarioAsync( - _ => _.Given_a_new_host(), _ => Given_host_configuration_is_applied(), _ => _.Given_a_Schematic_with_an_initial_state_INITIALSTATE(schematicName, "Initial"), _ => _.When_a_Machine_is_created_from_a_Schematic_with_a_predefined_MachineId(_.CurrentSchematic, machineId), @@ -79,7 +76,6 @@ public async Task A_Machine_can_be_created_from_a_SchematicName_with_a_predefine var machineId = uniqueId; await Runner.WithContext(Context).RunScenarioAsync( - _ => _.Given_a_new_host(), _ => Given_host_configuration_is_applied(), _ => _.Given_a_Schematic_with_an_initial_state_INITIALSTATE(schematicName, "Initial"), _ => _.Given_a_Schematic_is_stored(_.CurrentSchematic), @@ -97,7 +93,6 @@ public async Task Machines_can_be_bulk_created_from_a_SchematicName() var schematicName = uniqueId; await Runner.WithContext(Context).RunScenarioAsync( - _ => _.Given_a_new_host(), _ => Given_host_configuration_is_applied(), _ => _.Given_a_Schematic_with_an_initial_state_INITIALSTATE(schematicName, "Initial"), _ => _.Given_a_Schematic_is_stored(_.CurrentSchematic), @@ -115,7 +110,6 @@ public async Task Machines_can_be_bulk_created_from_a_Schematic() var schematicName = uniqueId; await Runner.WithContext(Context).RunScenarioAsync( - _ => _.Given_a_new_host(), _ => Given_host_configuration_is_applied(), _ => _.Given_a_Schematic_with_an_initial_state_INITIALSTATE(schematicName, "Initial"), _ => _.When_MACHINECOUNT_Machines_are_bulk_created_from_a_Schematic(_.CurrentSchematic, 5), diff --git a/test/REstate.Tests/Features/Templates/MachineDeletionScenarios.cs b/test/REstate.Tests/Features/Templates/MachineDeletionScenarios.cs index 27d4612..88d5bbf 100644 --- a/test/REstate.Tests/Features/Templates/MachineDeletionScenarios.cs +++ b/test/REstate.Tests/Features/Templates/MachineDeletionScenarios.cs @@ -11,46 +11,14 @@ namespace REstate.Tests.Features.Templates { [FeatureDescription(@" -In order to utilize REstate's functionality +In order to remove previous executions or stop machines As a developer -I want to retrieve previously created machines")] - [ScenarioCategory("Machine Retrieval")] - public abstract class MachineRetrievalScenarios +I want to delete previously created machines")] + [ScenarioCategory("Machine Deletion")] + public abstract class MachineDeletionScenarios : REstateFeature where TContext : REstateContext, new() { - [Scenario] - public async Task A_Machine_can_be_retrieved() - { - var uniqueId = Guid.NewGuid().ToString(); - - var schematicName = uniqueId; - var machineId = uniqueId; - - await Runner.WithContext(Context).RunScenarioAsync( - _ => _.Given_a_new_host(), - _ => Given_host_configuration_is_applied(), - _ => _.Given_a_Schematic_with_an_initial_state_INITIALSTATE(schematicName, "Initial"), - _ => _.Given_a_Machine_exists_with_MachineId_MACHINEID(_.CurrentSchematic, machineId), - _ => _.When_a_Machine_is_retrieved_with_MachineId_MACHINEID(machineId), - _ => _.Then_no_exception_was_thrown(), - _ => _.Then_the_Machine_is_valid(_.CurrentMachine)); - } - - [Scenario] - public async Task A_NonExistant_Machine_cannot_be_retrieved() - { - var uniqueId = Guid.NewGuid().ToString(); - - var machineId = uniqueId; - - await Runner.WithContext(Context).RunScenarioAsync( - _ => _.Given_a_new_host(), - _ => Given_host_configuration_is_applied(), - _ => _.When_a_Machine_is_retrieved_with_MachineId_MACHINEID(machineId), - _ => _.Then_MachineDoesNotExistException_is_thrown()); - } - [Scenario] public async Task A_Machine_that_is_deleted_no_longer_exists() { @@ -60,7 +28,6 @@ public async Task A_Machine_that_is_deleted_no_longer_exists() var machineId = uniqueId; await Runner.WithContext(Context).RunScenarioAsync( - _ => _.Given_a_new_host(), _ => Given_host_configuration_is_applied(), _ => _.Given_a_Schematic_with_an_initial_state_INITIALSTATE(schematicName, "Initial"), _ => _.Given_a_Machine_exists_with_MachineId_MACHINEID(_.CurrentSchematic, machineId), diff --git a/test/REstate.Tests/Features/Templates/MachineRetrievalScenarios.cs b/test/REstate.Tests/Features/Templates/MachineRetrievalScenarios.cs index 0246612..d2203dd 100644 --- a/test/REstate.Tests/Features/Templates/MachineRetrievalScenarios.cs +++ b/test/REstate.Tests/Features/Templates/MachineRetrievalScenarios.cs @@ -11,14 +11,44 @@ namespace REstate.Tests.Features.Templates { [FeatureDescription(@" -In order to remove previous executions or stop machines +In order to utilize REstate's functionality As a developer -I want to delete previously created machines")] - [ScenarioCategory("Machine Deletion")] - public abstract class MachineDeletionScenarios +I want to retrieve previously created machines")] + [ScenarioCategory("Machine Retrieval")] + public abstract class MachineRetrievalScenarios : REstateFeature where TContext : REstateContext, new() { + [Scenario] + public async Task A_Machine_can_be_retrieved() + { + var uniqueId = Guid.NewGuid().ToString(); + + var schematicName = uniqueId; + var machineId = uniqueId; + + await Runner.WithContext(Context).RunScenarioAsync( + _ => Given_host_configuration_is_applied(), + _ => _.Given_a_Schematic_with_an_initial_state_INITIALSTATE(schematicName, "Initial"), + _ => _.Given_a_Machine_exists_with_MachineId_MACHINEID(_.CurrentSchematic, machineId), + _ => _.When_a_Machine_is_retrieved_with_MachineId_MACHINEID(machineId), + _ => _.Then_no_exception_was_thrown(), + _ => _.Then_the_Machine_is_valid(_.CurrentMachine)); + } + + [Scenario] + public async Task A_NonExistant_Machine_cannot_be_retrieved() + { + var uniqueId = Guid.NewGuid().ToString(); + + var machineId = uniqueId; + + await Runner.WithContext(Context).RunScenarioAsync( + _ => Given_host_configuration_is_applied(), + _ => _.When_a_Machine_is_retrieved_with_MachineId_MACHINEID(machineId), + _ => _.Then_MachineDoesNotExistException_is_thrown()); + } + [Scenario] public async Task A_Machine_that_is_deleted_no_longer_exists() { @@ -28,7 +58,6 @@ public async Task A_Machine_that_is_deleted_no_longer_exists() var machineId = uniqueId; await Runner.WithContext(Context).RunScenarioAsync( - _ => _.Given_a_new_host(), _ => Given_host_configuration_is_applied(), _ => _.Given_a_Schematic_with_an_initial_state_INITIALSTATE(schematicName, "Initial"), _ => _.Given_a_Machine_exists_with_MachineId_MACHINEID(_.CurrentSchematic, machineId), diff --git a/test/REstate.Tests/Features/Templates/REstateFeature.cs b/test/REstate.Tests/Features/Templates/REstateFeature.cs index 8235fd6..0501ead 100644 --- a/test/REstate.Tests/Features/Templates/REstateFeature.cs +++ b/test/REstate.Tests/Features/Templates/REstateFeature.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using LightBDD.Framework; using LightBDD.Framework.Scenarios.Contextual; +using LightBDD.Framework.Scenarios.Extended; using LightBDD.XUnit2; using REstate.Tests.Features.Context; @@ -23,6 +24,7 @@ protected virtual Task Given_host_configuration_is_applied() CompositeStep .DefineNew() .WithContext(Context) + .AddAsyncSteps(context => context.Given_a_new_host()) .Build()); } diff --git a/test/REstate.Tests/Units/MachineTests.cs b/test/REstate.Tests/Units/MachineTests.cs index 6a245fe..b13dee2 100644 --- a/test/REstate.Tests/Units/MachineTests.cs +++ b/test/REstate.Tests/Units/MachineTests.cs @@ -1,6 +1,12 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; +using System.Threading; +using System.Threading.Tasks; +using REstate.Engine; +using REstate.Engine.Connectors; +using REstate.IoC; using REstate.Schematics; using Xunit; @@ -27,5 +33,61 @@ public void Machine_has_correct_ToString_functionality() Assert.StartsWith($"{schematic.SchematicName}/", machine.ToString(), StringComparison.Ordinal); Assert.EndsWith(machine.MachineId, machine.ToString(), StringComparison.Ordinal); } + + [Fact] + public async Task Entry_connector_with_tail_return_final_state() + { + // Arrange + var host = new REstateHost(); + + host.Agent().Configuration + .RegisterConnector(); + + var schematic = host.Agent() + .CreateSchematic( + schematicName: nameof(Entry_connector_with_tail_return_final_state)) + .WithState("Idle", idle => idle + .AsInitialState()) + .WithState("Processing", processing => processing + .WithTransitionFrom("Idle", "Process") + .WithOnEntry()) + .WithState("StillProcessing", stillProcessing => stillProcessing + .WithTransitionFrom("Processing", "Continue")) + .WithState("Complete", complete => complete + .WithTransitionFrom("StillProcessing", "Complete")); + + var engine = host.Agent().GetStateEngine(); + var machine = await engine.CreateMachineAsync(schematic); + + // Act + var status = await machine.SendAsync("Process"); + + // Assert + Assert.Equal("Complete", status.State); + Assert.Equal(3, status.CommitNumber); + } + + /// + /// An entry that tails 2 actions before returning. + /// + public class ProcessorEntry + : IEntryConnector + { + public async Task OnEntryAsync( + ISchematic schematic, + IStateMachine machine, + Status status, + InputParameters inputParameters, + IReadOnlyDictionary connectorSettings, + CancellationToken cancellationToken = default) + { + // Processing + + // Move next + await Task.WhenAll( + machine.SendAsync("Continue", cancellationToken), + machine.SendAsync("Complete", cancellationToken)); + } + } } }