public async Task ExecuteExitActionAsync_does_not_execute_if_cancelled() { var sale = new Sale(saleID: 96) { State = SaleState.ChangeDue }; var transitionResult = new StateTransitionResult <SaleState, SaleEvent>(SaleEvent.Pay , SaleState.Open , SaleState.Open , SaleState.ChangeDue , "lastTransitionName"); var stateMachine = new StateMachineAsync <Sale, SaleState, SaleEvent>(sale1 => sale1.State, (sale1, newState) => sale1.State = newState); var openState = new StateConfigurationAsync <Sale, SaleState, SaleEvent>(SaleState.Open, stateMachine); var openExitActionFired = false; openState.AddExitAction((sale1, _) => { openExitActionFired = true; return(Task.CompletedTask); }); using (var cancelSource = new CancellationTokenSource()) { var parameters = new ExecutionParameters <Sale, SaleEvent>(SaleEvent.Pay, sale, cancelSource.Token); cancelSource.Cancel(); await openState.ExecuteExitActionAsync(parameters, transitionResult); } Assert.False(openExitActionFired); }
public async Task FireTriggerAsync_can_cancel_trigger_event() { var sut = new StateMachineAsync <Sale, SaleState, SaleEvent>( stateAccessor: sale => sale.State , stateMutator: (sale, newState) => sale.State = newState); sut.AddTriggerAction(SaleEvent.AddItem, (_, cancelToken) => { Task.Delay(millisecondsDelay: 999999, cancellationToken: cancelToken).Wait(); return(Task.CompletedTask); }); var wasCancelled = false; using (var cancelSource = new CancellationTokenSource()) { try { cancelSource.Cancel(throwOnFirstException: true); await Task.Run(async() => await sut.FireTriggerAsync(new Sale(saleID: 45), SaleEvent.AddItem, cancelSource.Token)); cancelSource.Cancel(throwOnFirstException: true); } catch (AggregateException ex) { wasCancelled = ex.InnerExceptions.OfType <OperationCanceledException>().Any(); } } Assert.True(wasCancelled); }
public void AddAutoForwardTransitionWRequest_throws_ArgumentNullException_if_Condition_null() { var stateMachine = new StateMachineAsync <Sale, SaleState, SaleEvent>(sale => sale.State, (sale, newState) => sale.State = newState); var sut = new StateConfigurationAsync <Sale, SaleState, SaleEvent>(SaleState.ChangeDue, stateMachine); Assert.Throws <ArgumentNullException>(() => sut.AddAutoForwardTransition <string>(SaleEvent.Pay, SaleState.Complete, condition: null)); }
public async Task FireTriggerAsyncWRequest_withReferenceTypeInstance_triggerExecutes() { var testRequest = new Request(value: 123.45); var sut = new StateMachineAsync <Sale, SaleState, SaleEvent>( stateAccessor: sale2 => sale2.State , stateMutator: (sale3, newState) => sale3.State = newState); sut.ConfigureState(SaleState.Open) .AddTriggerAction <Request>(SaleEvent.Pay, async(saleInstance, request, token) => { saleInstance.Balance = request.Value; await Task.Delay(millisecondsDelay: 100); }) .AddTransition(SaleEvent.Pay, SaleState.Complete); var sale = new Sale(saleID: 45) { State = SaleState.Open }; var stateTransitionResult = await sut.FireTriggerAsync(sale, SaleEvent.Pay, testRequest); Assert.NotNull(stateTransitionResult); Assert.Equal(SaleState.Complete, stateTransitionResult.CurrentState); Assert.Equal(SaleState.Complete, sale.State); Assert.Equal(sale.Balance, testRequest.Value); }
public async Task FireTriggerAsync_executes_superState_if_currentState_not_successful() { var sale = new Sale(saleID: 96) { State = SaleState.ChangeDue }; var stateMachine = new StateMachineAsync <Sale, SaleState, SaleEvent>(sale1 => sale1.State, (sale1, newState) => sale1.State = newState); var openState = new StateConfigurationAsync <Sale, SaleState, SaleEvent>(SaleState.Open, stateMachine); var changeDueState = new StateConfigurationAsync <Sale, SaleState, SaleEvent>(SaleState.ChangeDue, stateMachine); var openStatePayTriggerFired = false; openState .AddTriggerAction(SaleEvent.Pay, (sale1, _) => { openStatePayTriggerFired = true; return(Task.CompletedTask); }) .AddTransition(SaleEvent.Pay, SaleState.Complete, name: "openStatePay"); changeDueState.AddSuperstate(openState); var parameters = new ExecutionParameters <Sale, SaleEvent>(SaleEvent.Pay, sale); var result = await changeDueState.FireTriggerAsync(parameters); Assert.True(openStatePayTriggerFired); Assert.True(result.WasTransitioned); Assert.Equal(SaleState.Complete, sale.State); Assert.Equal(SaleState.Complete, result.CurrentState); Assert.Equal(SaleState.ChangeDue, result.PreviousState); Assert.Equal(SaleState.ChangeDue, result.StartingState); Assert.Equal("openStatePay", result.LastTransitionName); }
public async Task FireTriggerAsync_executes_autoTransition() { var sale = new Sale(saleID: 45) { State = SaleState.Open }; var sut = new StateMachineAsync <Sale, SaleState, SaleEvent>( stateAccessor: sale2 => sale2.State , stateMutator: (sale3, newState) => sale3.State = newState); sut.ConfigureState(SaleState.Open) .AddTransition(SaleEvent.Pay, SaleState.ChangeDue, conditionAsync: null, name: "toChangeDue"); sut.ConfigureState(SaleState.ChangeDue) .AddAutoForwardTransition(SaleEvent.Pay, SaleState.Complete, (sale1, _) => Task.FromResult(result: true), name: "toComplete"); var result = await sut.FireTriggerAsync(sale, SaleEvent.Pay); Assert.Equal(SaleState.Complete, sale.State); Assert.True(result.WasTransitioned); Assert.Equal(SaleState.Complete, result.CurrentState); Assert.Equal(SaleState.ChangeDue, result.PreviousState); Assert.Equal(SaleState.Open, result.StartingState); Assert.Equal("toComplete", result.LastTransitionName); }
public async Task FireTriggerAsyncWRequest_executes_entryExitActions_for_autoTransition() { var sale = new Sale(saleID: 45) { State = SaleState.Open }; var sut = new StateMachineAsync <Sale, SaleState, SaleEvent>( stateAccessor: sale2 => sale2.State , stateMutator: (sale3, newState) => sale3.State = newState); sut.ConfigureState(SaleState.Open) .AddTransition(SaleEvent.Pay, SaleState.ChangeDue, conditionAsync: null, name: "toChangeDue"); sut.ConfigureState(SaleState.ChangeDue) .AddAutoForwardTransition(SaleEvent.Pay, SaleState.Complete, (sale1, _) => Task.FromResult(result: true), name: "toComplete"); var completeEntryActionFired = false; sut.ConfigureState(SaleState.Complete) .AddEntryAction((sale1, _) => { completeEntryActionFired = true; return(Task.CompletedTask); }); var result = await sut.FireTriggerAsync(sale, SaleEvent.Pay, "stringParam"); Assert.True(completeEntryActionFired); Assert.Equal(SaleState.Complete, sale.State); Assert.True(result.WasTransitioned); Assert.Equal(SaleState.Complete, result.CurrentState); Assert.Equal(SaleState.ChangeDue, result.PreviousState); Assert.Equal(SaleState.Open, result.StartingState); Assert.Equal("toComplete", result.LastTransitionName); }
public async Task ExecuteReentryActionAsync_executes_ReentryAction() { var sale = new Sale(saleID: 96) { State = SaleState.Open }; var transitionResult = new StateTransitionResult <SaleState, SaleEvent>(SaleEvent.AddItem , SaleState.Open , SaleState.Open , SaleState.Open , "lastTransitionName"); var stateMachine = new StateMachineAsync <Sale, SaleState, SaleEvent>(sale1 => sale1.State, (sale1, newState) => sale1.State = newState); var openState = new StateConfigurationAsync <Sale, SaleState, SaleEvent>(SaleState.Open, stateMachine); var changeDueState = new StateConfigurationAsync <Sale, SaleState, SaleEvent>(SaleState.ChangeDue, stateMachine); changeDueState.AddSuperstate(openState); var changeDueEntryActionFromOpenFired = false; changeDueState.AddReentryAction((sale1, _) => { changeDueEntryActionFromOpenFired = true; return(Task.CompletedTask); }); var parameters = new ExecutionParameters <Sale, SaleEvent>(SaleEvent.Pay, sale); await changeDueState.ExecuteReentryActionAsync(parameters, transitionResult); Assert.True(changeDueEntryActionFromOpenFired); }
public async Task FireTriggerAsyncWRequest_addTransitionSignatureWRequest_transitionExecutesWithRequestInstance() { var testRequest = new Request(value: 123.45); var sut = new StateMachineAsync <Sale, SaleState, SaleEvent>( stateAccessor: sale2 => sale2.State , stateMutator: (sale3, newState) => sale3.State = newState); var result = default(double); sut.ConfigureState(SaleState.Open) .AddTransition <Request>(SaleEvent.Pay, SaleState.Complete, (saleInstance, request, token) => { result = request.Value; return(Task.FromResult(result: true)); }); var sale = new Sale(saleID: 45) { State = SaleState.Open }; var stateTransitionResult = await sut.FireTriggerAsync(sale, SaleEvent.Pay, testRequest); Assert.NotNull(stateTransitionResult); Assert.Equal(SaleState.Complete, stateTransitionResult.CurrentState); Assert.Equal(SaleState.Complete, sale.State); Assert.Equal(result, testRequest.Value); }
public async Task ExecuteAutoTransitionAsync_executes_AutoTransition_for_superState() { var sale = new Sale(saleID: 96) { State = SaleState.ChangeDue }; var transitionResult = new StateTransitionResult <SaleState, SaleEvent>(SaleEvent.Pay , SaleState.Open , SaleState.Open , SaleState.ChangeDue , "lastTransitionName"); var stateMachine = new StateMachineAsync <Sale, SaleState, SaleEvent>(sale1 => sale1.State, (sale1, newState) => sale1.State = newState); IStateConfigurationAsyncInternal <Sale, SaleState, SaleEvent> openState = stateMachine.ConfigureState(SaleState.Open) as IStateConfigurationAsyncInternal <Sale, SaleState, SaleEvent>; IStateConfigurationAsyncInternal <Sale, SaleState, SaleEvent> changeDueState = stateMachine.ConfigureState(SaleState.ChangeDue) as IStateConfigurationAsyncInternal <Sale, SaleState, SaleEvent>; changeDueState.AddSuperstate(openState); openState.AddAutoForwardTransition(SaleEvent.ChangeGiven, SaleState.Complete, (sale1, _) => Task.FromResult(result: true)); var parameters = new ExecutionParameters <Sale, SaleEvent>(SaleEvent.ChangeGiven, sale); var autoTransitionResult = await changeDueState.ExecuteAutoTransitionAsync(parameters, transitionResult); Assert.True(autoTransitionResult.WasTransitioned); Assert.Equal(SaleState.Complete, sale.State); Assert.Equal(SaleState.Complete, autoTransitionResult.CurrentState); Assert.Equal(SaleState.ChangeDue, autoTransitionResult.PreviousState); Assert.Equal(SaleState.Open, autoTransitionResult.StartingState); }
public void AddEntryAction_throws_ArgumentNullException_if_Action_null() { var stateMachine = new StateMachineAsync <Sale, SaleState, SaleEvent>(sale => sale.State, (sale, newState) => sale.State = newState); var sut = new StateConfigurationAsync <Sale, SaleState, SaleEvent>(SaleState.ChangeDue, stateMachine); Assert.Throws <ArgumentNullException>(() => sut.AddEntryAction(action: null)); }
public void AddDynamicTransitionWRequest_throws_ArgumentNullException_if_Function_null() { var stateMachine = new StateMachineAsync <Sale, SaleState, SaleEvent>(sale => sale.State, (sale, newState) => sale.State = newState); var sut = new StateConfigurationAsync <Sale, SaleState, SaleEvent>(SaleState.ChangeDue, stateMachine); Assert.Throws <ArgumentNullException>(() => sut.AddDynamicTransition <string>(SaleEvent.AddItem, stateFunction: null)); }
public async Task ExecuteEntryActionAsync_executes_for_super_state() { var entryActionCalled = false; var stateMachine = new StateMachineAsync <Sale, SaleState, SaleEvent>(sale1 => sale1.State, (sale1, newState) => sale1.State = newState); var openConfig = new StateConfigurationAsync <Sale, SaleState, SaleEvent>(SaleState.Open, stateMachine); openConfig.AddEntryAction((sale1, _) => { entryActionCalled = true; return(Task.CompletedTask); }); var changeDueConfig = new StateConfigurationAsync <Sale, SaleState, SaleEvent>(SaleState.ChangeDue, stateMachine); changeDueConfig.MakeSubStateOf(openConfig); var sale = new Sale(saleID: 96) { State = SaleState.ChangeDue }; var parameters = new ExecutionParameters <Sale, SaleEvent>(SaleEvent.Pay, sale); var transitionResult = new StateTransitionResult <SaleState, SaleEvent>(SaleEvent.Pay , SaleState.Open , SaleState.Complete , SaleState.ChangeDue , "lastTransitionName"); await changeDueConfig.ExecuteEntryActionAsync(parameters, transitionResult); Assert.True(entryActionCalled); }
public void IsSubstateOf_returns_False_if_in_given_state() { var stateMachine = new StateMachineAsync <Sale, SaleState, SaleEvent>(sale1 => sale1.State, (sale1, newState) => sale1.State = newState); var openState = new StateConfigurationAsync <Sale, SaleState, SaleEvent>(SaleState.Open, stateMachine); Assert.False(openState.IsSubstateOf(SaleState.Open)); Assert.False(openState.IsSubstateOf(SaleState.Complete)); }
public void AddAutoFallbackTransitionWRequest_adds_transition() { var stateMachine = new StateMachineAsync <Sale, SaleState, SaleEvent>(sale => sale.State, (sale, newState) => sale.State = newState); var sut = new StateConfigurationAsync <Sale, SaleState, SaleEvent>(SaleState.ChangeDue, stateMachine); sut.AddAutoFallbackTransition <string>(SaleEvent.Pay, SaleState.ChangeDue, condition: (sale, s, _) => Task.FromResult(true)); Assert.Single((stateMachine.ConfigureState(SaleState.ChangeDue) as StateConfigurationAsync <Sale, SaleState, SaleEvent>).AutoTransitions); }
public void AddReentryAction_throws_InvalidOperationException_if_already_set() { var stateMachine = new StateMachineAsync <Sale, SaleState, SaleEvent>(sale => sale.State, (sale, newState) => sale.State = newState); var sut = new StateConfigurationAsync <Sale, SaleState, SaleEvent>(SaleState.ChangeDue, stateMachine); sut.AddReentryAction((sale, _) => Task.CompletedTask); Assert.Throws <InvalidOperationException>(() => sut.AddReentryAction((sale, _) => Task.CompletedTask)); }
public void ExecuteAutoTransitionAsync_can_be_cancelled() { var stopwatch = new Stopwatch(); var sale = new Sale(saleID: 96) { State = SaleState.ChangeDue }; var transitionResult = new StateTransitionResult <SaleState, SaleEvent>(SaleEvent.Pay , SaleState.ChangeDue , SaleState.ChangeDue , SaleState.ChangeDue , "lastTransitionName"); var stateMachine = new StateMachineAsync <Sale, SaleState, SaleEvent>(sale1 => sale1.State, (sale1, newState) => sale1.State = newState); var sut = new StateConfigurationAsync <Sale, SaleState, SaleEvent>(SaleState.ChangeDue, stateMachine); sut.AddAutoForwardTransition(SaleEvent.ChangeGiven, SaleState.Complete, (sale1, cancelToken) => { do { Task.Delay(millisecondsDelay: 99).Wait(); } while (!cancelToken.IsCancellationRequested); return(Task.FromResult(result: !cancelToken.IsCancellationRequested)); }); using (var mutex = new Mutex(initiallyOwned: false)) using (var cancelSource = new CancellationTokenSource()) { var parameters = new ExecutionParameters <Sale, SaleEvent>(SaleEvent.ChangeGiven, sale, cancelSource.Token); StateTransitionResult <SaleState, SaleEvent> autoTransitionResult = null; Task.Factory.StartNew(async() => { mutex.WaitOne(); autoTransitionResult = await sut.ExecuteAutoTransitionAsync(parameters, transitionResult); mutex.ReleaseMutex(); }, TaskCreationOptions.LongRunning); try { stopwatch.Start(); Task.Delay(millisecondsDelay: 2345).Wait(); cancelSource.Cancel(); mutex.WaitOne(); } catch (Exception ex) { cancelSource.Cancel(); } Assert.True(autoTransitionResult.WasCancelled); Assert.False(autoTransitionResult.ConditionMet); Assert.True(autoTransitionResult.TransitionDefined); Assert.Equal(SaleState.ChangeDue, sale.State); Assert.Equal(SaleState.ChangeDue, autoTransitionResult.CurrentState); } }
public void AddTriggerActionWRequest_throws_InvalidOperationException_if_already_set() { var stateMachine = new StateMachineAsync <Sale, SaleState, SaleEvent>(sale => sale.State, (sale, newState) => sale.State = newState); var sut = new StateConfigurationAsync <Sale, SaleState, SaleEvent>(SaleState.ChangeDue, stateMachine); sut.AddTriggerAction <string>(SaleEvent.Pay, (sale, stringParam, _) => Task.CompletedTask); Assert.Throws <InvalidOperationException>(() => sut.AddTriggerAction <string>(SaleEvent.Pay, (sale, stringParam, _) => Task.CompletedTask)); }
public void AddTransitionWRequest_add_transition() { var stateMachine = new StateMachineAsync <Sale, SaleState, SaleEvent>(sale => sale.State, (sale, newState) => sale.State = newState); var sut = new StateConfigurationAsync <Sale, SaleState, SaleEvent>(SaleState.ChangeDue, stateMachine); sut.AddTransition <string>(SaleEvent.AddItem, SaleState.Complete, (sale, stringParam, cancelToken) => Task.FromResult(true)); Assert.Single(sut.Transitions); }
public void AddDynamicTransitionWRequest_adds_transition() { var stateMachine = new StateMachineAsync <Sale, SaleState, SaleEvent>(sale => sale.State, (sale, newState) => sale.State = newState); var sut = new StateConfigurationAsync <Sale, SaleState, SaleEvent>(SaleState.ChangeDue, stateMachine); sut.AddDynamicTransition <string>(SaleEvent.AddItem, stateFunction: (sale, request) => SaleState.Complete); Assert.Single(sut.Transitions); }
public void AddAutoForwardTransition_throws_InvalidOperationException_if_AutoTransition_already_set() { var stateMachine = new StateMachineAsync <Sale, SaleState, SaleEvent>(sale => sale.State, (sale, newState) => sale.State = newState); var sut = new StateConfigurationAsync <Sale, SaleState, SaleEvent>(SaleState.ChangeDue, stateMachine); sut.AddAutoForwardTransition(SaleEvent.Pay, SaleState.Complete, (sale, _) => Task.FromResult(result: true)); Assert.Throws <InvalidOperationException>(() => sut.AddAutoForwardTransition(SaleEvent.Pay, SaleState.Complete, (sale, _) => Task.FromResult(result: true))); }
public void AddSuperState_throws_ArgumentOutOfRangeException_if_configuredState_already_superState() { var stateMachine = new StateMachineAsync <Sale, SaleState, SaleEvent>(sale => sale.State, (sale, newState) => sale.State = newState); var changeDueConfig = new StateConfigurationAsync <Sale, SaleState, SaleEvent>(SaleState.ChangeDue, stateMachine); var openStateConfig = new StateConfigurationAsync <Sale, SaleState, SaleEvent>(SaleState.Open, stateMachine); changeDueConfig.AddSuperstate(openStateConfig); Assert.Throws <ArgumentOutOfRangeException>(() => openStateConfig.AddSuperstate(changeDueConfig)); }
public void IsSubstateOf_returns_True_if_in_given_state_is_subState() { var stateMachine = new StateMachineAsync <Sale, SaleState, SaleEvent>(sale1 => sale1.State, (sale1, newState) => sale1.State = newState); var openState = new StateConfigurationAsync <Sale, SaleState, SaleEvent>(SaleState.Open, stateMachine); var changeDueState = new StateConfigurationAsync <Sale, SaleState, SaleEvent>(SaleState.ChangeDue, stateMachine); changeDueState.AddSuperstate(openState); Assert.True(changeDueState.IsSubstateOf(SaleState.Open)); Assert.False(openState.IsSubstateOf(SaleState.ChangeDue)); }
public void GetStateTransitionWithAsyncCondition_returns_StateTransitionAsync() { var stateMachine = new StateMachineAsync <Sale, SaleState, SaleEvent>(sale => sale.State, (sale, newState) => sale.State = newState); var result = StateTransitionFactory <Sale, SaleState, SaleEvent> .GetStateTransition(stateMachine , toState : SaleState.Complete , conditionAsync : (sale, cancelToken) => Task.FromResult(true) , name : "test" , priority : 1); Assert.IsType <StateTransitionAsync <Sale, SaleState, SaleEvent> >(result); }
public void ConfigureState_returns_existing_state_if_already_added() { var sut = new StateMachineAsync <Sale, SaleState, SaleEvent>( stateAccessor: sale => sale.State , stateMutator: (sale, newState) => sale.State = newState); var firstState = sut.ConfigureState(SaleState.Complete); var secondState = sut.ConfigureState(SaleState.Complete); Assert.Same(firstState, secondState); }
public void AddSuperState_throws_ArgumentOutOfRangeException_if_substate_is_already_superstate() { var stateMachine = new StateMachineAsync <Sale, SaleState, SaleEvent>(sale => sale.State, (sale, newState) => sale.State = newState); var sut = new StateConfigurationAsync <Sale, SaleState, SaleEvent>(SaleState.ChangeDue, stateMachine); var openStateConfig = new StateConfigurationAsync <Sale, SaleState, SaleEvent>(SaleState.Open, stateMachine); //First time works fine openStateConfig.AddSuperstate(sut); //Second time blows up Assert.Throws <ArgumentOutOfRangeException>(() => openStateConfig.AddSuperstate(sut)); }
public void GetStateTransactionWTRequestAsyncStateMachineStartStateAndTriggerState_returns_StateTransitionAutoDynamicAsync() { var stateMachine = new StateMachineAsync <Sale, SaleState, SaleEvent>(sale => sale.State, (sale, newState) => sale.State = newState); var result = StateTransitionFactory <Sale, SaleState, SaleEvent> .GetStateTransition <string>(stateMachine , startState : SaleState.Open , stateFunction : (sale, stringParam) => SaleState.Complete , triggerState : SaleState.ChangeDue , name : "test" , priority : 1); Assert.IsType <StateTransitionAutoDynamicParameterizedAsync <Sale, SaleState, SaleEvent, string> >(result); }
public void AddTriggerAction_throws_InvalidOperationException_if_action_already_added_for_trigger() { var sut = new StateMachineAsync <Sale, SaleState, SaleEvent>( stateAccessor: sale => sale.State , stateMutator: (sale, newState) => sale.State = newState); //1st one works fine sut.AddTriggerAction(SaleEvent.AddItem, (sale, _) => Task.Delay(millisecondsDelay: 1, cancellationToken: _)); //Adding the same thing 2nd time throws exception Assert.Throws <InvalidOperationException>(() => sut.AddTriggerAction(SaleEvent.AddItem, (sale, _) => Task.Delay(millisecondsDelay: 1, cancellationToken: _))); }
private StateMachineAsync <Sale, SaleState, SaleEvent> getTestStateMachine() { var stateMachine = new StateMachineAsync <Sale, SaleState, SaleEvent>( stateAccessor: sale => sale.State , stateMutator: (sale, newState) => sale.State = newState); stateMachine.ConfigureState(SaleState.Open) .AddTransition(SaleEvent.Pay, SaleState.Complete, (sale1, _) => Task.FromResult(Math.Abs(sale1.Balance) < 0.00001)) .AddTransition(SaleEvent.Pay, SaleState.ChangeDue, (sale2, _) => Task.FromResult(sale2.Balance < 0)); stateMachine.ConfigureState(SaleState.ChangeDue) .AddTransition(SaleEvent.ChangeGiven, SaleState.Complete); return(stateMachine); }
public async Task FireTriggerAsync_executes_trigger_event_when_defined() { var sut = new StateMachineAsync <Sale, SaleState, SaleEvent>( stateAccessor: sale => sale.State , stateMutator: (sale, newState) => sale.State = newState); var triggerActionExecuted = false; sut.AddTriggerAction(SaleEvent.AddItem, (sale, _) => Task.Run(() => triggerActionExecuted = true, _)); await sut.FireTriggerAsync(new Sale(saleID : 45), SaleEvent.AddItem, default(CancellationToken)); Assert.True(triggerActionExecuted); }