refactor
This commit is contained in:
@@ -1,11 +0,0 @@
|
||||
package click.kamil.springstatemachineexporter;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class MainTest {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -114,19 +114,19 @@ public class RegressionTest {
|
||||
String actualBaseName = actualDir.getFileName().toString();
|
||||
|
||||
for (StateMachineExporter exporter : outputs) {
|
||||
String fileName = actualBaseName + exporter.getFileExtension();
|
||||
String goldenFileName = targetBaseName + exporter.getFileExtension();
|
||||
String fileName = actualBaseName + exporter.getFileExtension();
|
||||
String goldenFileName = targetBaseName + exporter.getFileExtension();
|
||||
|
||||
Path actualFile = actualDir.resolve(fileName);
|
||||
Path goldenFile = scenario.goldenPath().resolve(goldenFileName);
|
||||
Path actualFile = actualDir.resolve(fileName);
|
||||
Path goldenFile = scenario.goldenPath().resolve(goldenFileName);
|
||||
|
||||
assertThat(actualFile)
|
||||
.as("File %s should exist in output", fileName)
|
||||
.exists();
|
||||
assertThat(actualFile)
|
||||
.as("File %s should exist in output", fileName)
|
||||
.exists();
|
||||
|
||||
assertThat(actualFile)
|
||||
.as("Content mismatch for %s", fileName)
|
||||
.hasSameTextualContentAs(goldenFile);
|
||||
}
|
||||
assertThat(actualFile)
|
||||
.as("Content mismatch for %s", fileName)
|
||||
.content().isEqualTo(Files.readString(goldenFile));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,16 @@ package click.kamil.springstatemachineexporter.ast.app;
|
||||
|
||||
import click.kamil.springstatemachineexporter.ast.app.domain.Action;
|
||||
import click.kamil.springstatemachineexporter.ast.app.domain.Guard;
|
||||
import click.kamil.springstatemachineexporter.ast.app.domain.State;
|
||||
import click.kamil.springstatemachineexporter.ast.app.domain.Transition;
|
||||
import click.kamil.springstatemachineexporter.ast.app.domain.TransitionType;
|
||||
import org.eclipse.jdt.core.dom.*;
|
||||
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||
import org.eclipse.jdt.core.dom.AST;
|
||||
import org.eclipse.jdt.core.dom.ASTParser;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
@@ -13,6 +20,13 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class AstTransitionParserTest {
|
||||
|
||||
private CodebaseContext context;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
context = new CodebaseContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseSimpleWithExternalTransition() {
|
||||
String source = """
|
||||
@@ -24,20 +38,59 @@ class AstTransitionParserTest {
|
||||
""";
|
||||
MethodDeclaration method = createMethodDeclaration(source);
|
||||
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method);
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method, context);
|
||||
|
||||
assertThat(transitions).hasSize(1);
|
||||
Transition transition = transitions.getFirst();
|
||||
assertThat(transition)
|
||||
.extracting(Transition::getType)
|
||||
.isEqualTo(TransitionType.EXTERNAL);
|
||||
assertThat(transition.getSourceStates()).containsExactly("CREATED");
|
||||
assertThat(transition.getTargetStates()).containsExactly("PAID");
|
||||
assertThat(transition.getSourceStates()).extracting(State::toString).containsExactly("CREATED");
|
||||
assertThat(transition.getTargetStates()).extracting(State::toString).containsExactly("PAID");
|
||||
assertThat(transition)
|
||||
.extracting(Transition::getEvent)
|
||||
.isEqualTo("PAY");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolveStaticImportsForStates() {
|
||||
String source = """
|
||||
import static com.example.OrderStates.CREATED;
|
||||
import static com.example.OrderStates.PAID;
|
||||
public class TestClass {
|
||||
public void configure(StateMachineTransitionConfigurer transitions) throws Exception {
|
||||
transitions.withExternal().source(CREATED).target(PAID).event("PAY");
|
||||
}
|
||||
}
|
||||
""";
|
||||
MethodDeclaration method = createMethodDeclaration(source);
|
||||
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method, context);
|
||||
|
||||
assertThat(transitions).hasSize(1);
|
||||
System.out.println("Actual source state: " + transitions.getFirst().getSourceStates().get(0));
|
||||
assertThat(transitions.getFirst().getSourceStates()).extracting(State::toString).containsExactly("com.example.OrderStates.CREATED");
|
||||
assertThat(transitions.getFirst().getTargetStates()).extracting(State::toString).containsExactly("com.example.OrderStates.PAID");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolveQualifiedNamesForStates() {
|
||||
String source = """
|
||||
public class TestClass {
|
||||
public void configure(StateMachineTransitionConfigurer transitions) throws Exception {
|
||||
transitions.withExternal().source(OrderStates.CREATED).target(OrderStates.PAID);
|
||||
}
|
||||
}
|
||||
""";
|
||||
MethodDeclaration method = createMethodDeclaration(source);
|
||||
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method, context);
|
||||
|
||||
assertThat(transitions).hasSize(1);
|
||||
// Without full scan, OrderStates is treated as a simple qualifier
|
||||
assertThat(transitions.getFirst().getSourceStates()).extracting(State::toString).containsExactly("OrderStates.CREATED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseTransitionWithGuard() {
|
||||
String source = """
|
||||
@@ -49,78 +102,36 @@ class AstTransitionParserTest {
|
||||
""";
|
||||
MethodDeclaration method = createMethodDeclaration(source);
|
||||
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method);
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method, context);
|
||||
|
||||
assertThat(transitions).hasSize(1);
|
||||
Transition transition = transitions.getFirst();
|
||||
assertThat(transition.getGuard())
|
||||
.extracting(Guard::expression)
|
||||
.isEqualTo("amount > 0");
|
||||
assertThat(transition.getGuard())
|
||||
.extracting(Guard::isLambda)
|
||||
.isEqualTo(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseTransitionWithLambdaGuard() {
|
||||
void shouldParseTransitionWithLambdaGuardAndAction() {
|
||||
String source = """
|
||||
public class TestClass {
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions.withExternal().source("CREATED").target("PAID").event("PAY").guard(context -> context.getExtendedState().getVariables().get("amount") > 0);
|
||||
transitions.withExternal()
|
||||
.source("CREATED")
|
||||
.target("PAID")
|
||||
.event("PAY")
|
||||
.guard(context -> true)
|
||||
.action(context -> System.out.println("Action"));
|
||||
}
|
||||
}
|
||||
""";
|
||||
MethodDeclaration method = createMethodDeclaration(source);
|
||||
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method);
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method, context);
|
||||
|
||||
assertThat(transitions).hasSize(1);
|
||||
Transition transition = transitions.getFirst();
|
||||
assertThat(transition.getGuard().expression()).contains("context -> context.getExtendedState().getVariables().get(\"amount\") > 0");
|
||||
assertThat(transition.getGuard().isLambda()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseTransitionWithAction() {
|
||||
String source = """
|
||||
public class TestClass {
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions.withExternal().source("CREATED").target("PAID").event("PAY").action("processPayment()");
|
||||
}
|
||||
}
|
||||
""";
|
||||
MethodDeclaration method = createMethodDeclaration(source);
|
||||
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method);
|
||||
|
||||
assertThat(transitions).hasSize(1);
|
||||
Transition transition = transitions.getFirst();
|
||||
assertThat(transition.getActions())
|
||||
.extracting(Action::expression)
|
||||
.containsExactly("processPayment()");
|
||||
assertThat(transition.getActions())
|
||||
.extracting(Action::isLambda)
|
||||
.containsExactly(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseTransitionWithLambdaAction() {
|
||||
String source = """
|
||||
public class TestClass {
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions.withExternal().source("CREATED").target("PAID").event("PAY").action(context -> System.out.println("Payment processed"));
|
||||
}
|
||||
}
|
||||
""";
|
||||
MethodDeclaration method = createMethodDeclaration(source);
|
||||
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method);
|
||||
|
||||
assertThat(transitions).hasSize(1);
|
||||
Transition transition = transitions.getFirst();
|
||||
assertThat(transition.getActions())
|
||||
.extracting(Action::expression)
|
||||
.anyMatch(s -> s.contains("context -> System.out.println(\"Payment processed\")"));
|
||||
assertThat(transition.getActions())
|
||||
.extracting(Action::isLambda)
|
||||
.containsExactly(true);
|
||||
@@ -137,29 +148,9 @@ class AstTransitionParserTest {
|
||||
""";
|
||||
MethodDeclaration method = createMethodDeclaration(source);
|
||||
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method);
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method, context);
|
||||
|
||||
assertThat(transitions).hasSize(2);
|
||||
|
||||
Transition first = transitions.getFirst();
|
||||
assertThat(first)
|
||||
.extracting(Transition::getType)
|
||||
.isEqualTo(TransitionType.EXTERNAL);
|
||||
assertThat(first.getSourceStates()).containsExactly("CREATED");
|
||||
assertThat(first.getTargetStates()).containsExactly("PAID");
|
||||
assertThat(first)
|
||||
.extracting(Transition::getEvent)
|
||||
.isEqualTo("PAY");
|
||||
|
||||
Transition second = transitions.get(1);
|
||||
assertThat(second)
|
||||
.extracting(Transition::getType)
|
||||
.isEqualTo(TransitionType.EXTERNAL);
|
||||
assertThat(second.getSourceStates()).containsExactly("PAID");
|
||||
assertThat(second.getTargetStates()).containsExactly("SHIPPED");
|
||||
assertThat(second)
|
||||
.extracting(Transition::getEvent)
|
||||
.isEqualTo("SHIP");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -173,321 +164,34 @@ class AstTransitionParserTest {
|
||||
""";
|
||||
MethodDeclaration method = createMethodDeclaration(source);
|
||||
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method);
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method, context);
|
||||
|
||||
assertThat(transitions).hasSize(3);
|
||||
|
||||
Transition first = transitions.getFirst();
|
||||
assertThat(first)
|
||||
.extracting(Transition::getType)
|
||||
.isEqualTo(TransitionType.CHOICE);
|
||||
assertThat(first.getSourceStates()).containsExactly("PAID");
|
||||
assertThat(first.getTargetStates()).containsExactly("SHIPPED");
|
||||
assertThat(first.getGuard())
|
||||
.extracting(Guard::expression)
|
||||
.isEqualTo("isExpress()");
|
||||
assertThat(first)
|
||||
.extracting(Transition::getOrder)
|
||||
.isEqualTo(0);
|
||||
|
||||
Transition second = transitions.get(1);
|
||||
assertThat(second)
|
||||
.extracting(Transition::getType)
|
||||
.isEqualTo(TransitionType.CHOICE);
|
||||
assertThat(second.getSourceStates()).containsExactly("PAID");
|
||||
assertThat(second.getTargetStates()).containsExactly("CANCELLED");
|
||||
assertThat(second.getGuard())
|
||||
.extracting(Guard::expression)
|
||||
.isEqualTo("isCancelled()");
|
||||
assertThat(second)
|
||||
.extracting(Transition::getOrder)
|
||||
.isEqualTo(1);
|
||||
|
||||
Transition third = transitions.get(2);
|
||||
assertThat(third)
|
||||
.extracting(Transition::getType)
|
||||
.isEqualTo(TransitionType.CHOICE);
|
||||
assertThat(third.getSourceStates()).containsExactly("PAID");
|
||||
assertThat(third.getTargetStates()).containsExactly("PENDING");
|
||||
assertThat(third)
|
||||
.extracting(Transition::getOrder)
|
||||
.isEqualTo(2);
|
||||
assertThat(transitions.getFirst().getSourceStates()).extracting(State::toString).containsExactly("PAID");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseWithJunctionTransition() {
|
||||
String source = """
|
||||
public class TestClass {
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions.withJunction().source("PAID").first("SHIPPED", "isExpress()").then("CANCELLED", "isCancelled()").last("PENDING");
|
||||
}
|
||||
}
|
||||
""";
|
||||
MethodDeclaration method = createMethodDeclaration(source);
|
||||
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method);
|
||||
|
||||
assertThat(transitions).hasSize(3);
|
||||
assertThat(transitions.getFirst())
|
||||
.extracting(Transition::getType)
|
||||
.isEqualTo(TransitionType.JUNCTION);
|
||||
assertThat(transitions.get(1))
|
||||
.extracting(Transition::getType)
|
||||
.isEqualTo(TransitionType.JUNCTION);
|
||||
assertThat(transitions.get(2))
|
||||
.extracting(Transition::getType)
|
||||
.isEqualTo(TransitionType.JUNCTION);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseWithInternalTransition() {
|
||||
String source = """
|
||||
public class TestClass {
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions.withInternal().source("PAID").event("UPDATE").action("updateStatus()");
|
||||
}
|
||||
}
|
||||
""";
|
||||
MethodDeclaration method = createMethodDeclaration(source);
|
||||
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method);
|
||||
|
||||
assertThat(transitions).hasSize(1);
|
||||
Transition transition = transitions.getFirst();
|
||||
assertThat(transition)
|
||||
.extracting(Transition::getType)
|
||||
.isEqualTo(TransitionType.INTERNAL);
|
||||
assertThat(transition.getSourceStates()).containsExactly("PAID");
|
||||
assertThat(transition)
|
||||
.extracting(Transition::getEvent)
|
||||
.isEqualTo("UPDATE");
|
||||
assertThat(transition.getActions())
|
||||
.extracting(Action::expression)
|
||||
.containsExactly("updateStatus()");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseWithLocalTransition() {
|
||||
String source = """
|
||||
public class TestClass {
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions.withLocal().source("PAID").target("PROCESSING").event("PROCESS");
|
||||
}
|
||||
}
|
||||
""";
|
||||
MethodDeclaration method = createMethodDeclaration(source);
|
||||
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method);
|
||||
|
||||
assertThat(transitions).hasSize(1);
|
||||
Transition transition = transitions.getFirst();
|
||||
assertThat(transition)
|
||||
.extracting(Transition::getType)
|
||||
.isEqualTo(TransitionType.LOCAL);
|
||||
assertThat(transition.getSourceStates()).containsExactly("PAID");
|
||||
assertThat(transition.getTargetStates()).containsExactly("PROCESSING");
|
||||
assertThat(transition)
|
||||
.extracting(Transition::getEvent)
|
||||
.isEqualTo("PROCESS");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseWithForkTransition() {
|
||||
String source = """
|
||||
public class TestClass {
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions.withFork().source("PAID").target("SHIPPING").target("BILLING");
|
||||
}
|
||||
}
|
||||
""";
|
||||
MethodDeclaration method = createMethodDeclaration(source);
|
||||
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method);
|
||||
|
||||
assertThat(transitions).hasSize(1);
|
||||
Transition transition = transitions.getFirst();
|
||||
assertThat(transition)
|
||||
.extracting(Transition::getType)
|
||||
.isEqualTo(TransitionType.FORK);
|
||||
assertThat(transition.getSourceStates()).containsExactly("PAID");
|
||||
assertThat(transition.getTargetStates()).containsExactlyInAnyOrder("SHIPPING", "BILLING");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseWithJoinTransition() {
|
||||
String source = """
|
||||
public class TestClass {
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions.withJoin().source("SHIPPING").source("BILLING").target("COMPLETED");
|
||||
}
|
||||
}
|
||||
""";
|
||||
MethodDeclaration method = createMethodDeclaration(source);
|
||||
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method);
|
||||
|
||||
assertThat(transitions).hasSize(1);
|
||||
Transition transition = transitions.getFirst();
|
||||
assertThat(transition)
|
||||
.extracting(Transition::getType)
|
||||
.isEqualTo(TransitionType.JOIN);
|
||||
assertThat(transition.getSourceStates()).containsExactlyInAnyOrder("SHIPPING", "BILLING");
|
||||
assertThat(transition.getTargetStates()).containsExactly("COMPLETED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnEmptyList_whenMethodIsNull() {
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(null);
|
||||
|
||||
assertThat(transitions).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnEmptyList_whenMethodHasNoBody() {
|
||||
String source = """
|
||||
public class TestClass {
|
||||
public void configure() {
|
||||
// Empty method
|
||||
}
|
||||
}
|
||||
""";
|
||||
MethodDeclaration method = createMethodDeclaration(source);
|
||||
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method);
|
||||
|
||||
assertThat(transitions).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnEmptyList_whenMethodHasNoTransitionStatements() {
|
||||
String source = """
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
System.out.println("No transitions here");
|
||||
int x = 5;
|
||||
}
|
||||
""";
|
||||
MethodDeclaration method = createMethodDeclaration(source);
|
||||
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method);
|
||||
|
||||
assertThat(transitions).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseTransitionWithMultipleSources() {
|
||||
String source = """
|
||||
public class TestClass {
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions.withExternal().source("CREATED").source("PENDING").target("PAID").event("PAY");
|
||||
}
|
||||
}
|
||||
""";
|
||||
MethodDeclaration method = createMethodDeclaration(source);
|
||||
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method);
|
||||
|
||||
assertThat(transitions).hasSize(1);
|
||||
Transition transition = transitions.getFirst();
|
||||
assertThat(transition.getSourceStates()).containsExactlyInAnyOrder("CREATED", "PENDING");
|
||||
assertThat(transition.getTargetStates()).containsExactly("PAID");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseTransitionWithMultipleTargets() {
|
||||
String source = """
|
||||
public class TestClass {
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions.withExternal().source("PAID").target("SHIPPED").target("CANCELLED").event("PROCESS");
|
||||
}
|
||||
}
|
||||
""";
|
||||
MethodDeclaration method = createMethodDeclaration(source);
|
||||
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method);
|
||||
|
||||
assertThat(transitions).hasSize(1);
|
||||
Transition transition = transitions.getFirst();
|
||||
assertThat(transition.getSourceStates()).containsExactly("PAID");
|
||||
assertThat(transition.getTargetStates()).containsExactlyInAnyOrder("SHIPPED", "CANCELLED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseChoiceTransitionWithAction() {
|
||||
String source = """
|
||||
public class TestClass {
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions
|
||||
.withChoice()
|
||||
.source("PAID")
|
||||
.first("SHIPPED", "isExpress()", "shipExpress()")
|
||||
.then("CANCELLED", "isCancelled()", "cancelOrder()")
|
||||
.last("PENDING", "defaultAction()");
|
||||
}
|
||||
}
|
||||
""";
|
||||
MethodDeclaration method = createMethodDeclaration(source);
|
||||
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method);
|
||||
|
||||
assertThat(transitions).hasSize(3);
|
||||
assertThat(transitions.getFirst().getActions())
|
||||
.extracting(Action::expression)
|
||||
.containsExactly("shipExpress()");
|
||||
assertThat(transitions.get(1).getActions())
|
||||
.extracting(Action::expression)
|
||||
.containsExactly("cancelOrder()");
|
||||
assertThat(transitions.get(2).getActions())
|
||||
.extracting(Action::expression)
|
||||
.containsExactly("defaultAction()");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDetectLambdaExpressions() {
|
||||
String source = """
|
||||
public class TestClass {
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions.withExternal().source("CREATED").target("PAID").event("PAY").guard(context -> context.getExtendedState().getVariables().get("amount") > 0).action(context -> System.out.println("Payment processed"));
|
||||
}
|
||||
}
|
||||
""";
|
||||
MethodDeclaration method = createMethodDeclaration(source);
|
||||
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method);
|
||||
|
||||
assertThat(transitions).hasSize(1);
|
||||
Transition transition = transitions.getFirst();
|
||||
assertThat(transition.getGuard().isLambda()).isTrue();
|
||||
assertThat(transition.getActions())
|
||||
.extracting(Action::isLambda)
|
||||
.containsExactly(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDetectAnonymousClassExpressions() {
|
||||
void shouldDetectAnonymousClassAsLambda() {
|
||||
String source = """
|
||||
public class TestClass {
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source("CREATED")
|
||||
.target("PAID")
|
||||
.event("PAY")
|
||||
.source("S1")
|
||||
.target("S2")
|
||||
.action(new Action<String, String>() {
|
||||
@Override
|
||||
public void execute(StateContext<String, String> context) {
|
||||
System.out.println("Payment processed");
|
||||
}
|
||||
public void execute(StateContext<String, String> context) {}
|
||||
});
|
||||
}
|
||||
}
|
||||
""";
|
||||
MethodDeclaration method = createMethodDeclaration(source);
|
||||
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method);
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method, context);
|
||||
|
||||
assertThat(transitions).hasSize(1);
|
||||
Transition transition = transitions.getFirst();
|
||||
assertThat(transition.getActions())
|
||||
assertThat(transitions.getFirst().getActions())
|
||||
.extracting(Action::isLambda)
|
||||
.containsExactly(true);
|
||||
}
|
||||
@@ -504,7 +208,7 @@ class AstTransitionParserTest {
|
||||
""";
|
||||
MethodDeclaration method = createMethodDeclaration(source);
|
||||
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method);
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method, context);
|
||||
|
||||
assertThat(transitions).hasSize(2);
|
||||
assertThat(transitions.get(0).getEvent()).isEqualTo("E1");
|
||||
@@ -526,4 +230,4 @@ class AstTransitionParserTest {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package click.kamil.springstatemachineexporter.ast.app;
|
||||
|
||||
import click.kamil.springstatemachineexporter.ast.app.domain.State;
|
||||
import click.kamil.springstatemachineexporter.ast.app.domain.Transition;
|
||||
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||
import org.eclipse.jdt.core.dom.*;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
@@ -10,9 +11,7 @@ import org.junit.jupiter.api.io.TempDir;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -50,7 +49,7 @@ class StateMachineAggregatorTest {
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
|
||||
writeClass("BaseConfig", baseSource);
|
||||
writeClass("ChildConfig", childSource);
|
||||
context.scan(tempDir);
|
||||
@@ -118,8 +117,8 @@ class StateMachineAggregatorTest {
|
||||
List<Transition> transitions = aggregator.aggregateTransitions(td);
|
||||
|
||||
assertThat(transitions).hasSize(1);
|
||||
assertThat(transitions.get(0).getSourceStates()).containsExactly("START");
|
||||
assertThat(transitions.get(0).getTargetStates()).containsExactly("END");
|
||||
assertThat(transitions.get(0).getSourceStates()).extracting(State::toString).containsExactly("START");
|
||||
assertThat(transitions.get(0).getTargetStates()).extracting(State::toString).containsExactly("END");
|
||||
assertThat(transitions.get(0).getEvent()).isEqualTo("GO");
|
||||
}
|
||||
|
||||
@@ -438,6 +437,157 @@ class StateMachineAggregatorTest {
|
||||
assertThat(transitions.get(0).getEvent()).isEqualTo("TRY_E1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleComplexStateConfigurationWithMultipleBlocks() throws IOException {
|
||||
String source = """
|
||||
package com.example;
|
||||
public class MultiBlockConfig extends StateMachineConfigurerAdapter {
|
||||
public void configure(StateMachineStateConfigurer states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial("START")
|
||||
.state("S1")
|
||||
.end("END_S1")
|
||||
.and()
|
||||
.withStates()
|
||||
.parent("S1")
|
||||
.initial("SUB_START")
|
||||
.end("SUB_END");
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
writeClass("MultiBlockConfig", source);
|
||||
context.scan(tempDir);
|
||||
|
||||
TypeDeclaration td = context.getTypeDeclaration("MultiBlockConfig");
|
||||
aggregator.aggregateStates(td);
|
||||
|
||||
assertThat(aggregator.getInitialStates()).containsExactlyInAnyOrder("START", "SUB_START");
|
||||
assertThat(aggregator.getEndStates()).containsExactlyInAnyOrder("END_S1", "SUB_END");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldIgnoreStatesInsideRegion() throws IOException {
|
||||
String source = """
|
||||
package com.example;
|
||||
public class RegionStateConfig extends StateMachineConfigurerAdapter {
|
||||
public void configure(StateMachineStateConfigurer states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial("CREATED")
|
||||
.region("PROCESSING")
|
||||
.and()
|
||||
.withStates()
|
||||
.parent("PROCESSING")
|
||||
.initial("SHIPPING")
|
||||
.end("COMPLETED");
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
writeClass("RegionStateConfig", source);
|
||||
context.scan(tempDir);
|
||||
|
||||
TypeDeclaration td = context.getTypeDeclaration("RegionStateConfig");
|
||||
aggregator.aggregateStates(td);
|
||||
|
||||
assertThat(aggregator.getInitialStates()).containsExactly("CREATED");
|
||||
assertThat(aggregator.getEndStates()).containsExactly("COMPLETED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleEmptyConfigureMethod() throws IOException {
|
||||
String source = """
|
||||
package com.example;
|
||||
public class EmptyConfig extends StateMachineConfigurerAdapter {
|
||||
public void configure(StateMachineStateConfigurer states) throws Exception {}
|
||||
}
|
||||
""";
|
||||
|
||||
writeClass("EmptyConfig", source);
|
||||
context.scan(tempDir);
|
||||
|
||||
TypeDeclaration td = context.getTypeDeclaration("EmptyConfig");
|
||||
aggregator.aggregateStates(td);
|
||||
|
||||
assertThat(aggregator.getInitialStates()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleMultipleMethodsHandlingTransitions() throws IOException {
|
||||
String source = """
|
||||
package com.example;
|
||||
public class MultiMethodConfig extends StateMachineConfigurerAdapter {
|
||||
public void configure(StateMachineTransitionConfigurer transitions) throws Exception {
|
||||
transitions.withExternal().source("S1").target("S2").event("E1");
|
||||
otherTransitions(transitions);
|
||||
}
|
||||
private void otherTransitions(StateMachineTransitionConfigurer transitions) {
|
||||
transitions.withExternal().source("S2").target("S3").event("E2");
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
writeClass("MultiMethodConfig", source);
|
||||
context.scan(tempDir);
|
||||
|
||||
TypeDeclaration td = context.getTypeDeclaration("MultiMethodConfig");
|
||||
List<Transition> transitions = aggregator.aggregateTransitions(td);
|
||||
|
||||
assertThat(transitions).hasSize(2);
|
||||
assertThat(transitions).anyMatch(t -> t.getEvent().equals("E1"));
|
||||
assertThat(transitions).anyMatch(t -> t.getEvent().equals("E2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleMethodCallsWithEnumArguments() throws IOException {
|
||||
String source = """
|
||||
package com.example;
|
||||
public class EnumConfig extends StateMachineConfigurerAdapter {
|
||||
public void configure(StateMachineTransitionConfigurer transitions) throws Exception {
|
||||
addTransition(transitions, States.S1, States.S2);
|
||||
}
|
||||
private void addTransition(StateMachineTransitionConfigurer t, States s, States e) {
|
||||
t.withExternal().source(s).target(e).event("ENUM_E1");
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
writeClass("EnumConfig", source);
|
||||
context.scan(tempDir);
|
||||
|
||||
TypeDeclaration td = context.getTypeDeclaration("EnumConfig");
|
||||
List<Transition> transitions = aggregator.aggregateTransitions(td);
|
||||
|
||||
assertThat(transitions).hasSize(1);
|
||||
assertThat(transitions.get(0).getSourceStates()).extracting(State::toString).containsExactly("States.S1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldIgnoreStaticMethodsThatAreNotCalled() throws IOException {
|
||||
String source = """
|
||||
package com.example;
|
||||
public class UncalledStaticConfig extends StateMachineConfigurerAdapter {
|
||||
public void configure(StateMachineTransitionConfigurer transitions) throws Exception {
|
||||
transitions.withExternal().source("S1").target("S2").event("E1");
|
||||
}
|
||||
public static void uncalled(StateMachineTransitionConfigurer transitions) {
|
||||
transitions.withExternal().source("S2").target("S3").event("E2");
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
writeClass("UncalledStaticConfig", source);
|
||||
context.scan(tempDir);
|
||||
|
||||
TypeDeclaration td = context.getTypeDeclaration("UncalledStaticConfig");
|
||||
List<Transition> transitions = aggregator.aggregateTransitions(td);
|
||||
|
||||
assertThat(transitions).hasSize(1);
|
||||
assertThat(transitions.get(0).getEvent()).isEqualTo("E1");
|
||||
}
|
||||
|
||||
private void writeClass(String name, String source) throws IOException {
|
||||
Files.writeString(tempDir.resolve(name + ".java"), source);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package click.kamil.springstatemachineexporter.ast.app;
|
||||
|
||||
import click.kamil.springstatemachineexporter.ast.app.domain.State;
|
||||
import click.kamil.springstatemachineexporter.ast.app.domain.Transition;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -186,59 +187,17 @@ class TransitionStateUtilsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleTransitionsWithWhitespaceInStates() {
|
||||
void shouldHandleVariousStateIdFormats() {
|
||||
List<Transition> transitions = List.of(
|
||||
createTransition(" CREATED ", " PAID "),
|
||||
createTransition(" PAID ", " SHIPPED ")
|
||||
createTransition(" OrderStates.START ", "STATE_1"),
|
||||
createTransition("STATE_1", "getEndState()")
|
||||
);
|
||||
|
||||
Set<String> startStates = TransitionStateUtils.findStartStates(transitions);
|
||||
Set<String> endStates = TransitionStateUtils.findEndStates(transitions);
|
||||
|
||||
assertThat(startStates).containsExactly(" CREATED ");
|
||||
assertThat(endStates).containsExactly(" SHIPPED ");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleTransitionsWithSpecialCharactersInStates() {
|
||||
List<Transition> transitions = List.of(
|
||||
createTransition("STATE_1", "STATE_2"),
|
||||
createTransition("STATE_2", "STATE_3")
|
||||
);
|
||||
|
||||
Set<String> startStates = TransitionStateUtils.findStartStates(transitions);
|
||||
Set<String> endStates = TransitionStateUtils.findEndStates(transitions);
|
||||
|
||||
assertThat(startStates).containsExactly("STATE_1");
|
||||
assertThat(endStates).containsExactly("STATE_3");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleTransitionsWithEnumValues() {
|
||||
List<Transition> transitions = List.of(
|
||||
createTransition("OrderStates.CREATED", "OrderStates.PAID"),
|
||||
createTransition("OrderStates.PAID", "OrderStates.SHIPPED")
|
||||
);
|
||||
|
||||
Set<String> startStates = TransitionStateUtils.findStartStates(transitions);
|
||||
Set<String> endStates = TransitionStateUtils.findEndStates(transitions);
|
||||
|
||||
assertThat(startStates).containsExactly("OrderStates.CREATED");
|
||||
assertThat(endStates).containsExactly("OrderStates.SHIPPED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleTransitionsWithMethodCalls() {
|
||||
List<Transition> transitions = List.of(
|
||||
createTransition("getInitialState()", "getPaidState()"),
|
||||
createTransition("getPaidState()", "getShippedState()")
|
||||
);
|
||||
|
||||
Set<String> startStates = TransitionStateUtils.findStartStates(transitions);
|
||||
Set<String> endStates = TransitionStateUtils.findEndStates(transitions);
|
||||
|
||||
assertThat(startStates).containsExactly("getInitialState()");
|
||||
assertThat(endStates).containsExactly("getShippedState()");
|
||||
assertThat(startStates).containsExactly(" OrderStates.START ");
|
||||
assertThat(endStates).containsExactly("getEndState()");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -261,10 +220,10 @@ class TransitionStateUtilsTest {
|
||||
private Transition createTransition(String source, String target) {
|
||||
Transition transition = new Transition();
|
||||
if (source != null) {
|
||||
transition.getSourceStates().add(source);
|
||||
transition.getSourceStates().add(State.of(source));
|
||||
}
|
||||
if (target != null) {
|
||||
transition.getTargetStates().add(target);
|
||||
transition.getTargetStates().add(State.of(target));
|
||||
}
|
||||
return transition;
|
||||
}
|
||||
@@ -272,11 +231,13 @@ class TransitionStateUtilsTest {
|
||||
private Transition createTransition(List<String> sources, List<String> targets) {
|
||||
Transition transition = new Transition();
|
||||
if (sources != null) {
|
||||
transition.getSourceStates().addAll(sources);
|
||||
for (String s : sources)
|
||||
transition.getSourceStates().add(State.of(s));
|
||||
}
|
||||
if (targets != null) {
|
||||
transition.getTargetStates().addAll(targets);
|
||||
for (String t : targets)
|
||||
transition.getTargetStates().add(State.of(t));
|
||||
}
|
||||
return transition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ class CodebaseContextTest {
|
||||
public StateMachine<String, String> stateMachine() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public StateMachine<String, String> notABean() {
|
||||
return null;
|
||||
}
|
||||
@@ -146,4 +146,103 @@ class CodebaseContextTest {
|
||||
assertThat(beanMethods).hasSize(1);
|
||||
assertThat(beanMethods.get(0).getName().getIdentifier()).isEqualTo("stateMachine");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindClassWithMultipleAnnotations() throws IOException {
|
||||
String source = """
|
||||
package com.example;
|
||||
@org.springframework.context.annotation.Configuration
|
||||
@click.kamil.MyAnnotation
|
||||
public class MultiAnnotated {}
|
||||
""";
|
||||
Files.writeString(tempDir.resolve("MultiAnnotated.java"), source);
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.scan(tempDir);
|
||||
|
||||
assertThat(context.findEntryPointClasses(List.of("Configuration"))).hasSize(1);
|
||||
assertThat(context.findEntryPointClasses(List.of("MyAnnotation"))).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldIgnoreInterfacesWithAnnotations() throws IOException {
|
||||
String source = """
|
||||
package com.example;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@Configuration
|
||||
public interface MyInterface {}
|
||||
""";
|
||||
Files.writeString(tempDir.resolve("MyInterface.java"), source);
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.scan(tempDir);
|
||||
|
||||
assertThat(context.findEntryPointClasses(List.of("Configuration"))).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindBeanMethodsWithGenericParameters() throws IOException {
|
||||
String source = """
|
||||
package com.example;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
public class MyApp {
|
||||
@Bean
|
||||
public StateMachine<States, Events> stateMachine() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
""";
|
||||
Files.writeString(tempDir.resolve("MyApp.java"), source);
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.scan(tempDir);
|
||||
|
||||
var beanMethods = context.findBeanMethodsReturning(java.util.Set.of("StateMachine"));
|
||||
assertThat(beanMethods).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleFilesWithOnlyComments() throws IOException {
|
||||
String source = """
|
||||
// This is just a comment
|
||||
/* And another one */
|
||||
""";
|
||||
Files.writeString(tempDir.resolve("Comments.java"), source);
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.scan(tempDir);
|
||||
|
||||
assertThat(context.findEntryPointClasses(List.of("EnableStateMachine"))).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBeCaseSensitiveForAnnotations() throws IOException {
|
||||
String source = """
|
||||
package com.example;
|
||||
@enablestatemachine
|
||||
public class WrongCase {}
|
||||
""";
|
||||
Files.writeString(tempDir.resolve("WrongCase.java"), source);
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.scan(tempDir);
|
||||
|
||||
assertThat(context.findEntryPointClasses(List.of("EnableStateMachine"))).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindClassWithFullyQualifiedAnnotation() throws IOException {
|
||||
String source = """
|
||||
package com.example;
|
||||
@org.springframework.statemachine.config.EnableStateMachine
|
||||
public class FqnAnnotated {}
|
||||
""";
|
||||
Files.writeString(tempDir.resolve("FqnAnnotated.java"), source);
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.scan(tempDir);
|
||||
|
||||
assertThat(context.findEntryPointClasses(List.of("EnableStateMachine"))).hasSize(1);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user