state machine unit test

This commit is contained in:
ialbert 2025-06-30 11:09:12 +03:00
parent a14ca74a69
commit 5462caf3f1
2 changed files with 178 additions and 166 deletions

View file

@ -8,8 +8,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.config.StateMachineFactory; import org.springframework.statemachine.config.StateMachineFactory;
import ru.spcex.clearing.session.teststate.config.Event; import ru.spcex.clearing.session.stage.TaskType;
import ru.spcex.clearing.session.teststate.config.State; import ru.spcex.clearing.session.state.SsnEvent;
import ru.spcex.clearing.session.teststate.config.TestStateMachineConfig; import ru.spcex.clearing.session.teststate.config.TestStateMachineConfig;
import ru.spcex.clearing.session.teststate.config.TestStateMachineExecutorsConfig; import ru.spcex.clearing.session.teststate.config.TestStateMachineExecutorsConfig;
@ -21,9 +21,9 @@ class StateMachineTest {
private final Logger log = LoggerFactory.getLogger(getClass()); private final Logger log = LoggerFactory.getLogger(getClass());
@Autowired @Autowired
private StateMachineFactory<State, Event> stateMachineFactory; private StateMachineFactory<TaskType, SsnEvent> stateMachineFactory;
private StateMachine<State, Event> stateMachine; private StateMachine<TaskType, SsnEvent> stateMachine;
@BeforeEach @BeforeEach
@ -35,7 +35,7 @@ class StateMachineTest {
@Test @Test
void test() { void test() {
log.debug("test???"); log.debug("test started");
// stateMachine // stateMachine
// .getStateMachineAccessor() // .getStateMachineAccessor()
// .doWithRegion(function -> function.addStateMachineInterceptor( // .doWithRegion(function -> function.addStateMachineInterceptor(
@ -84,20 +84,20 @@ class StateMachineTest {
// } // }
// })); // }));
stateMachine.sendEvent(Event.startSession); // stateMachine.sendEvent(SsnEvent.);
try { // try {
Thread.sleep(5000); // Thread.sleep(5000);
} catch (InterruptedException e) { // } catch (InterruptedException e) {
throw new RuntimeException(e); // throw new RuntimeException(e);
} // }
stateMachine.sendEvent(Event.SDF_01); // stateMachine.sendEvent(Event.SDF_01);
stateMachine.sendEvent(Event.SDF_04); // stateMachine.sendEvent(Event.SDF_04);
stateMachine.sendEvent(Event.SDF_57); // stateMachine.sendEvent(Event.SDF_57);
try { // try {
Thread.sleep(10000); // Thread.sleep(10000);
} catch (InterruptedException e) { // } catch (InterruptedException e) {
throw new RuntimeException(e); // throw new RuntimeException(e);
} // }
// stateMachine.sendEvent(Event.continueRevise); // stateMachine.sendEvent(Event.continueRevise);
// log.info("'ve send a continueRevise"); // log.info("'ve send a continueRevise");
// try { // try {
@ -109,7 +109,20 @@ class StateMachineTest {
// stateMachine.stop(); // stateMachine.stop();
// log.info("SECOND STOP"); // log.info("SECOND STOP");
// stateMachine.stop(); // stateMachine.stop();
sleepForNSec(5);
stateMachine.sendEvent(SsnEvent.SDF_57);
sleepForNSec(5);
stateMachine.sendEvent(SsnEvent.CONTINUE);
sleepForNSec(5);
log.info("So. We end with SM in state: {}", stateMachine.getState().getId());; log.info("So. We end with SM in state: {}", stateMachine.getState().getId());;
} }
public static void sleepForNSec(int i) {
try {
Thread.sleep((i * 1000L));
} catch (InterruptedException e) {
System.out.println("***** INTERRUPTED");
}
}
} }

View file

@ -1,15 +1,14 @@
package ru.spcex.clearing.session.teststate.config; package ru.spcex.clearing.session.teststate.config;
import java.util.ArrayList; import java.util.Arrays;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.HashSet;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor; import org.springframework.core.task.TaskExecutor;
import org.springframework.messaging.Message;
import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.action.Action; import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.config.EnableStateMachineFactory; import org.springframework.statemachine.config.EnableStateMachineFactory;
@ -19,17 +18,18 @@ import org.springframework.statemachine.config.builders.StateMachineStateConfigu
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.listener.StateMachineListenerAdapter; import org.springframework.statemachine.listener.StateMachineListenerAdapter;
import org.springframework.statemachine.transition.Transition; import org.springframework.statemachine.transition.Transition;
import ru.spcex.clearing.session.teststate.config.action.ContinueReviseTestAction; import ru.spcex.clearing.session.stage.TaskType;
import ru.spcex.clearing.session.teststate.config.action.InitialAction; import ru.spcex.clearing.session.state.SsnEvent;
import ru.spcex.clearing.session.teststate.config.action.SelfAction; import ru.spcex.clearing.session.state.action.SdfReceivedAction;
import ru.spcex.clearing.session.teststate.config.action.Send56TestAction; import ru.spcex.clearing.session.state.guard.PaymentsWereCreatedGuard;
import ru.spcex.clearing.session.teststate.config.guard.NoActiveSessionGuard; import ru.spcex.clearing.session.state.guard.SdfGuard;
import ru.spcex.clearing.util.StateMachineUtil; import ru.spcex.clearing.session.state.guard.SdfGuardExtractorAfterAssets;
import static ru.spcex.clearing.util.StateMachineUtil.chain;
import ru.spcex.platform.utils.log.ExceptionUtils; import ru.spcex.platform.utils.log.ExceptionUtils;
@Configuration @Configuration
@EnableStateMachineFactory @EnableStateMachineFactory
public class TestStateMachineConfig extends EnumStateMachineConfigurerAdapter<State, Event> { public class TestStateMachineConfig extends EnumStateMachineConfigurerAdapter<TaskType, SsnEvent> {
private final Logger log = LoggerFactory.getLogger(getClass()); private final Logger log = LoggerFactory.getLogger(getClass());
@Autowired @Autowired
@ -38,179 +38,178 @@ public class TestStateMachineConfig extends EnumStateMachineConfigurerAdapter<St
@Override @Override
public void configure(StateMachineStateConfigurer<State, Event> states) throws Exception { public void configure(StateMachineStateConfigurer<TaskType, SsnEvent> states) throws Exception {
states states
.withStates() .withStates()
.initial(State.initial, StateMachineUtil.chain(new InitialAction(), new SelfAction())) .initial(TaskType.StartRevise, chain(
.state(State.initial) context -> log.info("create session action"),
.state(State.startRevise) context -> log.info("send sdf56 action")))
.state(State.continueRevise, Event.triggerInternal) .states(new HashSet<>(Arrays.asList(
.state(State.stateWithInternalTransition) TaskType.StartRevise,
.state(State.stateAutoTransitionAfterInternalAutoTransition) TaskType.StartRevisePart1,
.state(State.stateWaitingForSdf) TaskType.DealsPrepare,
.state(State.stateAfterSdfReceived) TaskType.RequirementsAndObligationsCreate,
; TaskType.ObligationsAdmission,
TaskType.InclusionToPool,
TaskType.InspectionObligations,
TaskType.FormingRegistersOnOS,
TaskType.FormingPaymentInstruction,
TaskType.FinishingSession,
TaskType.EndStageNotification,
TaskType.PSEUDO_waitingForSdf,
TaskType.PSEUDO_waitingForObligationAdmission
)))
.end(TaskType.EndStageNotification);
} }
@Override @Override
public void configure(StateMachineTransitionConfigurer<State, Event> transitions) throws Exception { public void configure(StateMachineTransitionConfigurer<TaskType, SsnEvent> transitions) throws Exception {
transitions transitions
.withExternal() .withExternal()
.source(State.initial) .event(SsnEvent.SDF_57)
.target(State.startRevise) .source(TaskType.StartRevise).target(TaskType.StartRevisePart1)
.event(Event.startSession) .action(context -> log.info("reviseStage1Action"))
.guard(new NoActiveSessionGuard()) .and()
.action(new Send56TestAction())
.and()
.withExternal() .withExternal()
.source(State.startRevise) .source(TaskType.StartRevisePart1).target(TaskType.DealsPrepare)
.target(State.continueRevise) .action(context -> log.info("dealsPrepareAction"))
.action(new ContinueReviseTestAction()) .and()
//.and()
//.withExternal()
// .source(State.continueRevise)
// .target(State.newState)
// .action(context -> log.info("action without an event"))
.and()
.withExternal() .withExternal()
.source(State.continueRevise) .source(TaskType.DealsPrepare).target(TaskType.RequirementsAndObligationsCreate)
.target(State.stateWithInternalTransition) .action(context -> log.info("reqAndOblAction"))
.action(context -> { .and()
log.info("ACTION without an event");
// context.getStateMachine().sendEvent(Event.triggerInternal);
})
.and()
.withInternal()
.source(State.stateWithInternalTransition)
.action(context -> {
log.info("ACTION INTERNAL without an event");
})
.action(context -> {
log.info("ACTION INTERNAL second without an event");
})
.and()
.withExternal() .withExternal()
.source(State.stateWithInternalTransition) .source(TaskType.RequirementsAndObligationsCreate).target(TaskType.ObligationsAdmission)
.timerOnce(1) .action(context -> log.info("obligationAdmissionAction"))
.target(State.stateAutoTransitionAfterInternalAutoTransition) .and()
.action(ctx -> {
log.info("ACTION after internal transition");
List<Object> list = new ArrayList<>();
list.add("fasdfasdf");
ctx.getExtendedState().getVariables().put("payments", list);
})
.and()
.withExternal()
.source(State.stateAutoTransitionAfterInternalAutoTransition)
.target(State.stateAfterSdfReceived)
.action(ctx -> log.info("ACTION stateAutoTransitionAfterInternalAutoTransition to stateAfterSdfReceived without waiting"))
.guard(ctx -> {
List list = ctx.getExtendedState().get("payments", List.class);
return list != null && list.isEmpty();
})
.and()
.withExternal() .withExternal()
.source(State.stateAutoTransitionAfterInternalAutoTransition) .source(TaskType.ObligationsAdmission)
.target(State.stateWaitingForSdf) .target(TaskType.PSEUDO_waitingForObligationAdmission)
.action(ctx -> log.info("ACTION stateAutoTransitionAfterInternalAutoTransition to stateWaitingForSdf")) .guard(context -> true)
.guard(ctx -> { .and()
List list = ctx.getExtendedState().get("payments", List.class);
return list != null && !list.isEmpty();
})
.and()
.withExternal() .withExternal()
.source(State.stateWaitingForSdf) .source(TaskType.PSEUDO_waitingForObligationAdmission).target(TaskType.InclusionToPool)
.target(State.stateAfterSdfReceived) .action(chain(ctx -> log.info("obligationAdmissionContinueAction"), ctx -> log.info("inclusionToPoolAction")))
.guard(ctx -> { .event(SsnEvent.CONTINUE)
Boolean sdf01Received = ctx.getExtendedState().get(Event.SDF_01.name(), Boolean.class); .and()
Boolean sdf04Received = ctx.getExtendedState().get(Event.SDF_04.name(), Boolean.class); .withExternal()
Boolean sdf57Received = ctx.getExtendedState().get(Event.SDF_57.name(), Boolean.class); .source(TaskType.PSEUDO_waitingForObligationAdmission).target(TaskType.ObligationsAdmission)
log.info("GUARD received sdfs: sdf01={}, sdf04={}, sdf57={}", .action(chain(act("discardOblAdmStash"), act("obligationAdmissionAction")))
sdf01Received, sdf04Received, sdf57Received); .event(SsnEvent.REPEAT)
return Boolean.TRUE.equals(sdf01Received) .and()
&& Boolean.TRUE.equals(sdf04Received) .withExternal()
&& Boolean.TRUE.equals(sdf57Received); .source(TaskType.ObligationsAdmission).target(TaskType.InclusionToPool)
}); .guard(context -> false) //invert(oblAdmGuard)
.action(chain(act("obligationAdmissionContinueAction"), act("inclusionToPoolAction")))
for (var event : EnumSet.of(Event.SDF_01, Event.SDF_04, Event.SDF_57)) { .and()
.withExternal()
.source(TaskType.InclusionToPool).target(TaskType.InspectionObligations)
.action(act("inspOblDepositReturnAction"))
.action(act("inspectionObligationsV2Action"))
.and()
.withExternal()
.source(TaskType.InspectionObligations).target(TaskType.FormingPaymentInstruction)
.action(act("formingPaymentInstructionReturnMkrAction"))
.action(act("formingPaymentInstructionAssetsAction"))
.and()
//если не создалось paymentInstruction'ов
.withExternal()
.source(TaskType.FormingPaymentInstruction)
.target(TaskType.FinishingSession)
.guard(ctx -> true) //PaymentsWereNotCreatedGuard.instance
.action(act("finishingSessionAction"))
.and()
//если создались paymentInstruction, переходим в режим ожидания
.withExternal()
.source(TaskType.FormingPaymentInstruction)
.target(TaskType.PSEUDO_waitingForSdf)
.guard(PaymentsWereCreatedGuard.instance)
.and()
.withExternal()
.source(TaskType.PSEUDO_waitingForSdf)
.target(TaskType.FinishingSession)
.guard(new SdfGuard(SdfGuardExtractorAfterAssets.instance))
.action(act("finishingSessionAction"))
.and()
.withExternal()
.source(TaskType.FinishingSession)
.target(TaskType.EndStageNotification)
.action(act("endStageNotificationAction"));
for (var event: EnumSet.of(SsnEvent.SDF_01, SsnEvent.SDF_57, SsnEvent.SDF_04)) {
transitions transitions
.withInternal() .withInternal()
.source(State.stateWaitingForSdf) .source(TaskType.PSEUDO_waitingForSdf)
.event(event) .event(event)
.action(eventReceivedAction()); .action(SdfReceivedAction.instance);
} }
} }
private Action<TaskType, SsnEvent> act(String actionName) {
public Action<State, Event> eventReceivedAction() { return ctx -> log.info(actionName);
return ctx -> {
log.info("event {} flag saving received", ctx.getEvent().name());
ctx.getExtendedState().getVariables().put(ctx.getEvent().name(), true);
};
} }
@Override @Override
public void configure(StateMachineConfigurationConfigurer<State, Event> config) throws Exception { public void configure(StateMachineConfigurationConfigurer<TaskType, SsnEvent> config) throws Exception {
StateMachineListenerAdapter<State, Event> loggingChangeStateListener = new StateMachineListenerAdapter<>() { StateMachineListenerAdapter<TaskType, SsnEvent> loggingChangeStateListener = new StateMachineListenerAdapter<>() {
@Override // @Override
public void stateEntered(org.springframework.statemachine.state.State<State, Event> state) { // public void stateEntered(org.springframework.statemachine.state.State<TaskType, SsnEvent> state) {
State enteredState = state != null ? state.getId() : null; // TaskType enteredState = state != null ? state.getId() : null;
log.info(String.format("LISTENER stateEntered: %s", enteredState)); // log.info(String.format("LISTENER stateEntered: %s", enteredState));
} // }
// @Override
// public void eventNotAccepted(Message<SsnEvent> event) {
// SsnEvent payload = event != null ? event.getPayload() : null;
// log.info(String.format("LISTENER eventNotAccepted: %s", payload));
// }
@Override @Override
public void eventNotAccepted(Message<Event> event) { public void transition(Transition<TaskType, SsnEvent> transition) {
Event payload = event != null ? event.getPayload() : null; TaskType source = transition.getSource() != null ? transition.getSource().getId() : null;
log.info(String.format("LISTENER eventNotAccepted: %s", payload)); TaskType target = transition.getTarget().getId();
}
@Override
public void transition(Transition<State, Event> transition) {
State source = transition.getSource() != null ? transition.getSource().getId() : null;
State target = transition.getTarget().getId();
log.info("LISTENER transition: source {} target {}", log.info("LISTENER transition: source {} target {}",
source, target); source, target);
} }
@Override // @Override
public void stateChanged(org.springframework.statemachine.state.State<State, Event> from, org.springframework.statemachine.state.State<State, Event> to) { // public void stateChanged(org.springframework.statemachine.state.State<TaskType, SsnEvent> from, org.springframework.statemachine.state.State<TaskType, SsnEvent> to) {
State source = from != null ? from.getId() : null; // TaskType source = from != null ? from.getId() : null;
State target = to.getId(); // TaskType target = to.getId();
log.info("LISTENER stateChanged: source {} target {}", // log.info("LISTENER stateChanged: source {} target {}",
source, target); // source, target);
} // }
// @Override
// public void stateExited(org.springframework.statemachine.state.State<TaskType, SsnEvent> state) {
// TaskType whichOne = state != null ? state.getId() : null;
// log.info("LISTENER stateExited: {}", whichOne);
// }
// @Override
// public void transitionEnded(Transition<TaskType, SsnEvent> transition) {
// TaskType source = transition.getSource() != null ? transition.getSource().getId() : null;
// TaskType target = transition.getTarget().getId();
// log.info("LISTENER transitionEnded: source {} target {}",
// source, target);
// }
@Override @Override
public void stateExited(org.springframework.statemachine.state.State<State, Event> state) { public void stateMachineError(StateMachine<TaskType, SsnEvent> stateMachine, Exception exception) {
State whichOne = state != null ? state.getId() : null;
log.info("LISTENER stateExited: {}", whichOne);
}
@Override
public void transitionEnded(Transition<State, Event> transition) {
State source = transition.getSource() != null ? transition.getSource().getId() : null;
State target = transition.getTarget().getId();
log.info("LISTENER transitionEnded: source {} target {}",
source, target);
}
@Override
public void stateMachineError(StateMachine<State, Event> stateMachine, Exception exception) {
log.info("LISTENER stateMachineError: {}", (ExceptionUtils.getStackTrace(exception))); log.info("LISTENER stateMachineError: {}", (ExceptionUtils.getStackTrace(exception)));
} }
@Override // @Override
public void transitionStarted(Transition<State, Event> transition) { // public void transitionStarted(Transition<TaskType, SsnEvent> transition) {
State source = transition.getSource() != null ? transition.getSource().getId() : null; // TaskType source = transition.getSource() != null ? transition.getSource().getId() : null;
State target = transition.getTarget().getId(); // TaskType target = transition.getTarget().getId();
log.info("LISTENER transitionStarted: source {} target {}", // log.info("LISTENER transitionStarted: source {} target {}",
source, target); // source, target);
} // }
}; };
config config
.withConfiguration() .withConfiguration()
.machineId("test-machine")
.listener(loggingChangeStateListener) .listener(loggingChangeStateListener)
// .taskExecutor(taskExecutor) // .taskExecutor(taskExecutor)
; ;