move files into modules
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
package click.kamil.springstatemachineexporter;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class MainTest {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package click.kamil.springstatemachineexporter;
|
||||
|
||||
import click.kamil.springstatemachineexporter.ast.out.Dot;
|
||||
import click.kamil.springstatemachineexporter.ast.out.PlantUml;
|
||||
import click.kamil.springstatemachineexporter.ast.out.Scxml;
|
||||
import click.kamil.springstatemachineexporter.ast.out.StateMachineExporter;
|
||||
import click.kamil.springstatemachineexporter.utils.TestScenario;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class RegressionTest {
|
||||
|
||||
static List<TestScenario> provideTestScenarios() {
|
||||
return List.of(
|
||||
new TestScenario(
|
||||
"Complex State Machine",
|
||||
Path.of("../state_machines/complex_state_machine"),
|
||||
Path.of("src/test/resources/golden/ComplexStateMachineConfig"),
|
||||
"ComplexStateMachineConfig"
|
||||
),
|
||||
new TestScenario(
|
||||
"Fork Join State Machine",
|
||||
Path.of("../state_machines/forkjoin_state_machine"),
|
||||
Path.of("src/test/resources/golden/ForkJoinStateMachineConfig"),
|
||||
"ForkJoinStateMachineConfig"
|
||||
),
|
||||
new TestScenario(
|
||||
"Kamil Enum State Machine",
|
||||
Path.of("../state_machines/enumstate_state_machine"),
|
||||
Path.of("src/test/resources/golden/KamilEnumStateMachineConfiguration"),
|
||||
"KamilEnumStateMachineConfiguration"
|
||||
),
|
||||
new TestScenario(
|
||||
"Simple Enum Join State Machine",
|
||||
Path.of("../state_machines/simple_state_machine"),
|
||||
Path.of("src/test/resources/golden/SimpleEnumStateMachineConfiguration"),
|
||||
"SimpleEnumStateMachineConfiguration"
|
||||
)
|
||||
);
|
||||
}
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@MethodSource("provideTestScenarios")
|
||||
void testOutputMatchesGolden(TestScenario scenario, @TempDir Path tempDir) throws Exception {
|
||||
Main.runExporter(scenario.inputPath(), tempDir);
|
||||
|
||||
List<StateMachineExporter> exporters = List.of(
|
||||
new PlantUml(),
|
||||
new Dot(),
|
||||
new Scxml()
|
||||
);
|
||||
|
||||
String baseName = scenario.baseName();
|
||||
|
||||
for (StateMachineExporter exporter : exporters) {
|
||||
String fileName = baseName + exporter.getFileExtension();
|
||||
|
||||
Path actualFile = tempDir.resolve(baseName).resolve(fileName);
|
||||
Path goldenFile = scenario.goldenPath().resolve(fileName);
|
||||
|
||||
assertThat(actualFile)
|
||||
.as("File %s should exist in output", fileName)
|
||||
.exists();
|
||||
|
||||
assertThat(actualFile)
|
||||
.as("Content mismatch for %s", fileName)
|
||||
.hasSameTextualContentAs(goldenFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,486 @@
|
||||
package click.kamil.springstatemachineexporter.ast.app;
|
||||
|
||||
import click.kamil.springstatemachineexporter.ast.app.domain.Transition;
|
||||
import org.eclipse.jdt.core.dom.*;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class AstTransitionParserTest {
|
||||
|
||||
@Test
|
||||
void shouldParseSimpleWithExternalTransition() {
|
||||
String source = """
|
||||
public class TestClass {
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions.withExternal().source("CREATED").target("PAID").event("PAY");
|
||||
}
|
||||
}
|
||||
""";
|
||||
MethodDeclaration method = createMethodDeclaration(source);
|
||||
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method);
|
||||
|
||||
assertThat(transitions).hasSize(1);
|
||||
Transition transition = transitions.getFirst();
|
||||
assertThat(transition)
|
||||
.extracting(Transition::getType)
|
||||
.isEqualTo("withExternal");
|
||||
assertThat(transition.getSourceStates()).containsExactly("CREATED");
|
||||
assertThat(transition.getTargetStates()).containsExactly("PAID");
|
||||
assertThat(transition)
|
||||
.extracting(Transition::getEvent)
|
||||
.isEqualTo("PAY");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseTransitionWithGuard() {
|
||||
String source = """
|
||||
public class TestClass {
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions.withExternal().source("CREATED").target("PAID").event("PAY").guard("amount > 0");
|
||||
}
|
||||
}
|
||||
""";
|
||||
MethodDeclaration method = createMethodDeclaration(source);
|
||||
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method);
|
||||
|
||||
assertThat(transitions).hasSize(1);
|
||||
Transition transition = transitions.getFirst();
|
||||
assertThat(transition)
|
||||
.extracting(Transition::getGuard)
|
||||
.isEqualTo("amount > 0");
|
||||
assertThat(transition.isLambdaGuard()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseTransitionWithLambdaGuard() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
""";
|
||||
MethodDeclaration method = createMethodDeclaration(source);
|
||||
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method);
|
||||
|
||||
assertThat(transitions).hasSize(1);
|
||||
Transition transition = transitions.getFirst();
|
||||
assertThat(transition.getGuard()).contains("context -> context.getExtendedState().getVariables().get(\"amount\") > 0");
|
||||
assertThat(transition.isLambdaGuard()).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()).containsExactly("processPayment()");
|
||||
assertThat(transition.getIsLambdaActions()).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()).hasSize(1);
|
||||
assertThat(transition.getActions().getFirst()).contains("context -> System.out.println(\"Payment processed\")");
|
||||
assertThat(transition.getIsLambdaActions()).containsExactly(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseMultipleTransitionsWithAnd() {
|
||||
String source = """
|
||||
public class TestClass {
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions.withExternal().source("CREATED").target("PAID").event("PAY").and().withExternal().source("PAID").target("SHIPPED").event("SHIP");
|
||||
}
|
||||
}
|
||||
""";
|
||||
MethodDeclaration method = createMethodDeclaration(source);
|
||||
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method);
|
||||
|
||||
assertThat(transitions).hasSize(2);
|
||||
|
||||
Transition first = transitions.getFirst();
|
||||
assertThat(first)
|
||||
.extracting(Transition::getType)
|
||||
.isEqualTo("withExternal");
|
||||
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("withExternal");
|
||||
assertThat(second.getSourceStates()).containsExactly("PAID");
|
||||
assertThat(second.getTargetStates()).containsExactly("SHIPPED");
|
||||
assertThat(second)
|
||||
.extracting(Transition::getEvent)
|
||||
.isEqualTo("SHIP");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseWithChoiceTransition() {
|
||||
String source = """
|
||||
public class TestClass {
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions.withChoice().source("PAID").first("SHIPPED", "isExpress()").then("CANCELLED", "isCancelled()").last("PENDING");
|
||||
}
|
||||
}
|
||||
""";
|
||||
MethodDeclaration method = createMethodDeclaration(source);
|
||||
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method);
|
||||
|
||||
assertThat(transitions).hasSize(3);
|
||||
|
||||
Transition first = transitions.getFirst();
|
||||
assertThat(first)
|
||||
.extracting(Transition::getType)
|
||||
.isEqualTo("withChoice");
|
||||
assertThat(first.getSourceStates()).containsExactly("PAID");
|
||||
assertThat(first.getTargetStates()).containsExactly("SHIPPED");
|
||||
assertThat(first)
|
||||
.extracting(Transition::getGuard)
|
||||
.isEqualTo("isExpress()");
|
||||
assertThat(first)
|
||||
.extracting(Transition::getOrder)
|
||||
.isEqualTo(0);
|
||||
|
||||
Transition second = transitions.get(1);
|
||||
assertThat(second)
|
||||
.extracting(Transition::getType)
|
||||
.isEqualTo("withChoice");
|
||||
assertThat(second.getSourceStates()).containsExactly("PAID");
|
||||
assertThat(second.getTargetStates()).containsExactly("CANCELLED");
|
||||
assertThat(second)
|
||||
.extracting(Transition::getGuard)
|
||||
.isEqualTo("isCancelled()");
|
||||
assertThat(second)
|
||||
.extracting(Transition::getOrder)
|
||||
.isEqualTo(1);
|
||||
|
||||
Transition third = transitions.get(2);
|
||||
assertThat(third)
|
||||
.extracting(Transition::getType)
|
||||
.isEqualTo("withChoice");
|
||||
assertThat(third.getSourceStates()).containsExactly("PAID");
|
||||
assertThat(third.getTargetStates()).containsExactly("PENDING");
|
||||
assertThat(third)
|
||||
.extracting(Transition::getOrder)
|
||||
.isEqualTo(2);
|
||||
}
|
||||
|
||||
@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("withJunction");
|
||||
assertThat(transitions.get(1))
|
||||
.extracting(Transition::getType)
|
||||
.isEqualTo("withJunction");
|
||||
assertThat(transitions.get(2))
|
||||
.extracting(Transition::getType)
|
||||
.isEqualTo("withJunction");
|
||||
}
|
||||
|
||||
@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("withInternal");
|
||||
assertThat(transition.getSourceStates()).containsExactly("PAID");
|
||||
assertThat(transition)
|
||||
.extracting(Transition::getEvent)
|
||||
.isEqualTo("UPDATE");
|
||||
assertThat(transition.getActions()).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("withLocal");
|
||||
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("withFork");
|
||||
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("withJoin");
|
||||
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()).containsExactly("shipExpress()");
|
||||
assertThat(transitions.get(1).getActions()).containsExactly("cancelOrder()");
|
||||
assertThat(transitions.get(2).getActions()).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.isLambdaGuard()).isTrue();
|
||||
assertThat(transition.getIsLambdaActions()).containsExactly(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDetectAnonymousClassExpressions() {
|
||||
String source = """
|
||||
public class TestClass {
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source("CREATED")
|
||||
.target("PAID")
|
||||
.event("PAY")
|
||||
.action(new Action<String, String>() {
|
||||
@Override
|
||||
public void execute(StateContext<String, String> 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.getIsLambdaActions()).containsExactly(true);
|
||||
}
|
||||
|
||||
private MethodDeclaration createMethodDeclaration(String source) {
|
||||
ASTParser parser = ASTParser.newParser(AST.getJLSLatest());
|
||||
parser.setKind(ASTParser.K_COMPILATION_UNIT);
|
||||
parser.setSource(source.toCharArray());
|
||||
CompilationUnit cu = (CompilationUnit) parser.createAST(null);
|
||||
if (cu.types().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
TypeDeclaration typeDecl = (TypeDeclaration) cu.types().getFirst();
|
||||
MethodDeclaration[] methods = typeDecl.getMethods();
|
||||
if (methods.length > 0) {
|
||||
return methods[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
package click.kamil.springstatemachineexporter.ast.app;
|
||||
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class StateMachineStateConfigurationMethodFinderTest {
|
||||
|
||||
@Test
|
||||
void shouldParseInitialStates() {
|
||||
String source = """
|
||||
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(OrderStates.CREATED)
|
||||
.end(OrderStates.COMPLETED);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
CompilationUnit cu = StateMachineStateConfigurationMethodFinder.parse(source);
|
||||
StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor visitor =
|
||||
new StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor();
|
||||
cu.accept(visitor);
|
||||
|
||||
assertThat(visitor.getInitialStates()).containsExactly("OrderStates.CREATED");
|
||||
assertThat(visitor.getEndStates()).containsExactly("OrderStates.COMPLETED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseMultipleInitialStates() {
|
||||
String source = """
|
||||
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(OrderStates.CREATED)
|
||||
.initial(OrderStates.PENDING)
|
||||
.end(OrderStates.COMPLETED)
|
||||
.end(OrderStates.CANCELLED);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
CompilationUnit cu = StateMachineStateConfigurationMethodFinder.parse(source);
|
||||
StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor visitor =
|
||||
new StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor();
|
||||
cu.accept(visitor);
|
||||
|
||||
assertThat(visitor.getInitialStates()).containsExactlyInAnyOrder("OrderStates.CREATED", "OrderStates.PENDING");
|
||||
assertThat(visitor.getEndStates()).containsExactlyInAnyOrder("OrderStates.COMPLETED", "OrderStates.CANCELLED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldIgnoreStatesInsideFork() {
|
||||
String source = """
|
||||
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(OrderStates.CREATED)
|
||||
.fork(OrderStates.PROCESSING)
|
||||
.and()
|
||||
.withStates()
|
||||
.parent(OrderStates.PROCESSING)
|
||||
.initial(OrderStates.SHIPPING)
|
||||
.initial(OrderStates.BILLING)
|
||||
.end(OrderStates.COMPLETED);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
CompilationUnit cu = StateMachineStateConfigurationMethodFinder.parse(source);
|
||||
StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor visitor =
|
||||
new StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor();
|
||||
cu.accept(visitor);
|
||||
|
||||
assertThat(visitor.getInitialStates()).containsExactly("OrderStates.CREATED");
|
||||
assertThat(visitor.getEndStates()).containsExactly("OrderStates.COMPLETED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldIgnoreStatesInsideRegion() {
|
||||
String source = """
|
||||
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(OrderStates.CREATED)
|
||||
.region(OrderStates.PROCESSING)
|
||||
.and()
|
||||
.withStates()
|
||||
.parent(OrderStates.PROCESSING)
|
||||
.initial(OrderStates.SHIPPING)
|
||||
.initial(OrderStates.BILLING)
|
||||
.end(OrderStates.COMPLETED);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
CompilationUnit cu = StateMachineStateConfigurationMethodFinder.parse(source);
|
||||
StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor visitor =
|
||||
new StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor();
|
||||
cu.accept(visitor);
|
||||
|
||||
assertThat(visitor.getInitialStates()).containsExactly("OrderStates.CREATED");
|
||||
assertThat(visitor.getEndStates()).containsExactly("OrderStates.COMPLETED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseStatesWithStringLiterals() {
|
||||
String source = """
|
||||
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<String, String> {
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial("CREATED")
|
||||
.end("COMPLETED");
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
CompilationUnit cu = StateMachineStateConfigurationMethodFinder.parse(source);
|
||||
StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor visitor =
|
||||
new StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor();
|
||||
cu.accept(visitor);
|
||||
|
||||
assertThat(visitor.getInitialStates()).containsExactly("CREATED");
|
||||
assertThat(visitor.getEndStates()).containsExactly("COMPLETED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseStatesWithQuotedStrings() {
|
||||
String source = """
|
||||
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<String, String> {
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial("CREATED")
|
||||
.end("COMPLETED");
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
CompilationUnit cu = StateMachineStateConfigurationMethodFinder.parse(source);
|
||||
StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor visitor =
|
||||
new StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor();
|
||||
cu.accept(visitor);
|
||||
|
||||
assertThat(visitor.getInitialStates()).containsExactly("CREATED");
|
||||
assertThat(visitor.getEndStates()).containsExactly("COMPLETED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldIgnoreNonConfigureMethods() {
|
||||
String source = """
|
||||
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {
|
||||
public void someOtherMethod() {
|
||||
states
|
||||
.withStates()
|
||||
.initial(OrderStates.CREATED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(OrderStates.PENDING)
|
||||
.end(OrderStates.COMPLETED);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
CompilationUnit cu = StateMachineStateConfigurationMethodFinder.parse(source);
|
||||
StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor visitor =
|
||||
new StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor();
|
||||
cu.accept(visitor);
|
||||
|
||||
assertThat(visitor.getInitialStates()).containsExactly("OrderStates.PENDING");
|
||||
assertThat(visitor.getEndStates()).containsExactly("OrderStates.COMPLETED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleEmptyConfigureMethod() {
|
||||
String source = """
|
||||
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) throws Exception {
|
||||
// Empty method
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
CompilationUnit cu = StateMachineStateConfigurationMethodFinder.parse(source);
|
||||
StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor visitor =
|
||||
new StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor();
|
||||
cu.accept(visitor);
|
||||
|
||||
assertThat(visitor.getInitialStates()).isEmpty();
|
||||
assertThat(visitor.getEndStates()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleConfigureMethodWithoutWithStates() {
|
||||
String source = """
|
||||
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) throws Exception {
|
||||
System.out.println("No state configuration here");
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
CompilationUnit cu = StateMachineStateConfigurationMethodFinder.parse(source);
|
||||
StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor visitor =
|
||||
new StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor();
|
||||
cu.accept(visitor);
|
||||
|
||||
assertThat(visitor.getInitialStates()).isEmpty();
|
||||
assertThat(visitor.getEndStates()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseComplexStateConfiguration() {
|
||||
String source = """
|
||||
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(OrderStates.CREATED)
|
||||
.state(OrderStates.PAID)
|
||||
.state(OrderStates.SHIPPED)
|
||||
.end(OrderStates.COMPLETED)
|
||||
.end(OrderStates.CANCELLED)
|
||||
.and()
|
||||
.withStates()
|
||||
.parent(OrderStates.PAID)
|
||||
.initial(OrderStates.PROCESSING)
|
||||
.end(OrderStates.READY);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
CompilationUnit cu = StateMachineStateConfigurationMethodFinder.parse(source);
|
||||
StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor visitor =
|
||||
new StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor();
|
||||
cu.accept(visitor);
|
||||
|
||||
assertThat(visitor.getInitialStates()).containsExactlyInAnyOrder("OrderStates.CREATED", "OrderStates.PROCESSING");
|
||||
assertThat(visitor.getEndStates()).containsExactlyInAnyOrder("OrderStates.COMPLETED", "OrderStates.CANCELLED", "OrderStates.READY");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseStatesWithMethodCalls() {
|
||||
String source = """
|
||||
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(getInitialState())
|
||||
.end(getEndState());
|
||||
}
|
||||
|
||||
private OrderStates getInitialState() {
|
||||
return OrderStates.CREATED;
|
||||
}
|
||||
|
||||
private OrderStates getEndState() {
|
||||
return OrderStates.COMPLETED;
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
CompilationUnit cu = StateMachineStateConfigurationMethodFinder.parse(source);
|
||||
StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor visitor =
|
||||
new StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor();
|
||||
cu.accept(visitor);
|
||||
|
||||
assertThat(visitor.getInitialStates()).containsExactly("getInitialState()");
|
||||
assertThat(visitor.getEndStates()).containsExactly("getEndState()");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleMultipleConfigureMethods() {
|
||||
String source = """
|
||||
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(OrderStates.CREATED)
|
||||
.end(OrderStates.COMPLETED);
|
||||
}
|
||||
|
||||
public void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions) throws Exception {
|
||||
// This should be ignored
|
||||
states
|
||||
.withStates()
|
||||
.initial(OrderStates.PENDING);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
CompilationUnit cu = StateMachineStateConfigurationMethodFinder.parse(source);
|
||||
StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor visitor =
|
||||
new StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor();
|
||||
cu.accept(visitor);
|
||||
|
||||
assertThat(visitor.getInitialStates()).containsExactlyInAnyOrder("OrderStates.CREATED", "OrderStates.PENDING");
|
||||
assertThat(visitor.getEndStates()).containsExactly("OrderStates.COMPLETED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseStatesWithEnumValues() {
|
||||
String source = """
|
||||
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(OrderStates.CREATED)
|
||||
.state(OrderStates.PAID)
|
||||
.state(OrderStates.SHIPPED)
|
||||
.end(OrderStates.COMPLETED);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
CompilationUnit cu = StateMachineStateConfigurationMethodFinder.parse(source);
|
||||
StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor visitor =
|
||||
new StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor();
|
||||
cu.accept(visitor);
|
||||
|
||||
assertThat(visitor.getInitialStates()).containsExactly("OrderStates.CREATED");
|
||||
assertThat(visitor.getEndStates()).containsExactly("OrderStates.COMPLETED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleNestedForkAndRegion() {
|
||||
String source = """
|
||||
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(OrderStates.CREATED)
|
||||
.fork(OrderStates.PROCESSING)
|
||||
.and()
|
||||
.withStates()
|
||||
.parent(OrderStates.PROCESSING)
|
||||
.region(OrderStates.SHIPPING)
|
||||
.and()
|
||||
.withStates()
|
||||
.parent(OrderStates.SHIPPING)
|
||||
.initial(OrderStates.PREPARING)
|
||||
.end(OrderStates.SHIPPED)
|
||||
.and()
|
||||
.withStates()
|
||||
.parent(OrderStates.PROCESSING)
|
||||
.region(OrderStates.BILLING)
|
||||
.and()
|
||||
.withStates()
|
||||
.parent(OrderStates.BILLING)
|
||||
.initial(OrderStates.CALCULATING)
|
||||
.end(OrderStates.BILLED)
|
||||
.and()
|
||||
.withStates()
|
||||
.parent(OrderStates.PROCESSING)
|
||||
.end(OrderStates.COMPLETED);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
CompilationUnit cu = StateMachineStateConfigurationMethodFinder.parse(source);
|
||||
StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor visitor =
|
||||
new StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor();
|
||||
cu.accept(visitor);
|
||||
|
||||
assertThat(visitor.getInitialStates()).containsExactly("OrderStates.CREATED");
|
||||
assertThat(visitor.getEndStates()).containsExactlyInAnyOrder("OrderStates.COMPLETED", "OrderStates.SHIPPED", "OrderStates.BILLED");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
package click.kamil.springstatemachineexporter.ast.app;
|
||||
|
||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
import org.eclipse.jdt.core.dom.SimpleName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class StateMachineTransitionConfigurationMethodFinderTest {
|
||||
|
||||
@Test
|
||||
void shouldFindConfigureMethodInEnumStateMachineConfigurerAdapter() {
|
||||
String source = """
|
||||
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(OrderStates.CREATED)
|
||||
.target(OrderStates.PAID)
|
||||
.event(OrderEvents.PAY);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
StateMachineTransitionConfigurationMethodFinder finder = new StateMachineTransitionConfigurationMethodFinder(source);
|
||||
MethodDeclaration method = finder.findConfigureMethod();
|
||||
|
||||
assertThat(method)
|
||||
.extracting(MethodDeclaration::getName)
|
||||
.extracting(SimpleName::getIdentifier)
|
||||
.isEqualTo("configure");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindConfigureMethodInStateMachineConfigurerAdapter() {
|
||||
String source = """
|
||||
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<String, String> {
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source("CREATED")
|
||||
.target("PAID")
|
||||
.event("PAY");
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
StateMachineTransitionConfigurationMethodFinder finder = new StateMachineTransitionConfigurationMethodFinder(source);
|
||||
MethodDeclaration method = finder.findConfigureMethod();
|
||||
|
||||
assertThat(method)
|
||||
.extracting(MethodDeclaration::getName)
|
||||
.extracting(SimpleName::getIdentifier)
|
||||
.isEqualTo("configure");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnNull_whenClassDoesNotExtendStateMachineConfigurer() {
|
||||
String source = """
|
||||
public class OrderStateMachineConfig {
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
// This should not be found
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
StateMachineTransitionConfigurationMethodFinder finder = new StateMachineTransitionConfigurationMethodFinder(source);
|
||||
MethodDeclaration method = finder.findConfigureMethod();
|
||||
|
||||
assertThat(method).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnNull_whenClassIsInterface() {
|
||||
String source = """
|
||||
public interface OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {
|
||||
void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions) throws Exception;
|
||||
}
|
||||
""";
|
||||
|
||||
StateMachineTransitionConfigurationMethodFinder finder = new StateMachineTransitionConfigurationMethodFinder(source);
|
||||
MethodDeclaration method = finder.findConfigureMethod();
|
||||
|
||||
assertThat(method).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnNull_whenConfigureMethodIsNotPublic() {
|
||||
String source = """
|
||||
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {
|
||||
@Override
|
||||
protected void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions) throws Exception {
|
||||
// This should not be found
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
StateMachineTransitionConfigurationMethodFinder finder = new StateMachineTransitionConfigurationMethodFinder(source);
|
||||
MethodDeclaration method = finder.findConfigureMethod();
|
||||
|
||||
assertThat(method).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnNull_whenConfigureMethodDoesNotReturnVoid() {
|
||||
String source = """
|
||||
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {
|
||||
@Override
|
||||
public String configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions) throws Exception {
|
||||
// This should not be found
|
||||
return "result";
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
StateMachineTransitionConfigurationMethodFinder finder = new StateMachineTransitionConfigurationMethodFinder(source);
|
||||
MethodDeclaration method = finder.findConfigureMethod();
|
||||
|
||||
assertThat(method).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnNull_whenConfigureMethodHasWrongNumberOfParameters() {
|
||||
String source = """
|
||||
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {
|
||||
@Override
|
||||
public void configure() throws Exception {
|
||||
// This should not be found
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
StateMachineTransitionConfigurationMethodFinder finder = new StateMachineTransitionConfigurationMethodFinder(source);
|
||||
MethodDeclaration method = finder.findConfigureMethod();
|
||||
|
||||
assertThat(method).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnNull_whenConfigureMethodParameterIsNotStateMachineTransitionConfigurer() {
|
||||
String source = """
|
||||
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) throws Exception {
|
||||
// This should not be found
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
StateMachineTransitionConfigurationMethodFinder finder = new StateMachineTransitionConfigurationMethodFinder(source);
|
||||
MethodDeclaration method = finder.findConfigureMethod();
|
||||
|
||||
assertThat(method).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnNull_whenConfigureMethodDoesNotThrowException() {
|
||||
String source = """
|
||||
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions) {
|
||||
// This should not be found
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
StateMachineTransitionConfigurationMethodFinder finder = new StateMachineTransitionConfigurationMethodFinder(source);
|
||||
MethodDeclaration method = finder.findConfigureMethod();
|
||||
|
||||
assertThat(method).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnNull_whenConfigureMethodThrowsDifferentException() {
|
||||
String source = """
|
||||
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions) throws RuntimeException {
|
||||
// This should not be found
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
StateMachineTransitionConfigurationMethodFinder finder = new StateMachineTransitionConfigurationMethodFinder(source);
|
||||
MethodDeclaration method = finder.findConfigureMethod();
|
||||
|
||||
assertThat(method).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindConfigureMethodWithMultipleExceptions() {
|
||||
String source = """
|
||||
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions) throws Exception, RuntimeException {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(OrderStates.CREATED)
|
||||
.target(OrderStates.PAID)
|
||||
.event(OrderEvents.PAY);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
StateMachineTransitionConfigurationMethodFinder finder = new StateMachineTransitionConfigurationMethodFinder(source);
|
||||
MethodDeclaration method = finder.findConfigureMethod();
|
||||
|
||||
assertThat(method)
|
||||
.extracting(MethodDeclaration::getName)
|
||||
.extracting(SimpleName::getIdentifier)
|
||||
.isEqualTo("configure");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindConfigureMethodWithParameterizedType() {
|
||||
String source = """
|
||||
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(OrderStates.CREATED)
|
||||
.target(OrderStates.PAID)
|
||||
.event(OrderEvents.PAY);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
StateMachineTransitionConfigurationMethodFinder finder = new StateMachineTransitionConfigurationMethodFinder(source);
|
||||
MethodDeclaration method = finder.findConfigureMethod();
|
||||
|
||||
assertThat(method)
|
||||
.extracting(MethodDeclaration::getName)
|
||||
.extracting(SimpleName::getIdentifier)
|
||||
.isEqualTo("configure");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnNull_whenMultipleConfigureMethodsExist() {
|
||||
String source = """
|
||||
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) throws Exception {
|
||||
// State configuration
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions) throws Exception {
|
||||
// Transition configuration
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
StateMachineTransitionConfigurationMethodFinder finder = new StateMachineTransitionConfigurationMethodFinder(source);
|
||||
MethodDeclaration method = finder.findConfigureMethod();
|
||||
|
||||
assertThat(method)
|
||||
.extracting(MethodDeclaration::getName)
|
||||
.extracting(SimpleName::getIdentifier)
|
||||
.isEqualTo("configure");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleClassWithNoMethods() {
|
||||
String source = """
|
||||
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {
|
||||
// No methods
|
||||
}
|
||||
""";
|
||||
|
||||
StateMachineTransitionConfigurationMethodFinder finder = new StateMachineTransitionConfigurationMethodFinder(source);
|
||||
MethodDeclaration method = finder.findConfigureMethod();
|
||||
|
||||
assertThat(method).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleClassWithOtherMethods() {
|
||||
String source = """
|
||||
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {
|
||||
public void someOtherMethod() {
|
||||
// This should be ignored
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions) throws Exception {
|
||||
// This should be found
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
StateMachineTransitionConfigurationMethodFinder finder = new StateMachineTransitionConfigurationMethodFinder(source);
|
||||
MethodDeclaration method = finder.findConfigureMethod();
|
||||
|
||||
assertThat(method)
|
||||
.extracting(MethodDeclaration::getName)
|
||||
.extracting(SimpleName::getIdentifier)
|
||||
.isEqualTo("configure");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleClassWithGenericParameters() {
|
||||
String source = """
|
||||
public class OrderStateMachineConfig<S, E> extends EnumStateMachineConfigurerAdapter<S, E> {
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<S, E> transitions) throws Exception {
|
||||
// Generic configuration
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
StateMachineTransitionConfigurationMethodFinder finder = new StateMachineTransitionConfigurationMethodFinder(source);
|
||||
MethodDeclaration method = finder.findConfigureMethod();
|
||||
|
||||
assertThat(method)
|
||||
.extracting(MethodDeclaration::getName)
|
||||
.extracting(SimpleName::getIdentifier)
|
||||
.isEqualTo("configure");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleClassWithAnnotations() {
|
||||
String source = """
|
||||
@Configuration
|
||||
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {
|
||||
@Override
|
||||
@Bean
|
||||
public void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions) throws Exception {
|
||||
// Configuration with annotations
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
StateMachineTransitionConfigurationMethodFinder finder = new StateMachineTransitionConfigurationMethodFinder(source);
|
||||
MethodDeclaration method = finder.findConfigureMethod();
|
||||
|
||||
assertThat(method)
|
||||
.extracting(MethodDeclaration::getName)
|
||||
.extracting(SimpleName::getIdentifier)
|
||||
.isEqualTo("configure");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
package click.kamil.springstatemachineexporter.ast.app;
|
||||
|
||||
import click.kamil.springstatemachineexporter.ast.app.domain.Transition;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class TransitionStateUtilsTest {
|
||||
|
||||
@Test
|
||||
void shouldFindStartStates_whenStatesHaveNoIncomingTransitions() {
|
||||
List<Transition> transitions = List.of(
|
||||
createTransition("CREATED", "PAID"),
|
||||
createTransition("PAID", "SHIPPED"),
|
||||
createTransition("SHIPPED", "COMPLETED")
|
||||
);
|
||||
|
||||
Set<String> startStates = TransitionStateUtils.findStartStates(transitions);
|
||||
|
||||
assertThat(startStates).containsExactly("CREATED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindStartStates_whenMultipleStatesHaveNoIncomingTransitions() {
|
||||
List<Transition> transitions = List.of(
|
||||
createTransition("CREATED", "PAID"),
|
||||
createTransition("PENDING", "PAID"),
|
||||
createTransition("PAID", "SHIPPED")
|
||||
);
|
||||
|
||||
Set<String> startStates = TransitionStateUtils.findStartStates(transitions);
|
||||
|
||||
assertThat(startStates).containsExactlyInAnyOrder("CREATED", "PENDING");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindEndStates_whenStatesHaveNoOutgoingTransitions() {
|
||||
List<Transition> transitions = List.of(
|
||||
createTransition("CREATED", "PAID"),
|
||||
createTransition("PAID", "SHIPPED"),
|
||||
createTransition("PAID", "CANCELLED")
|
||||
);
|
||||
|
||||
Set<String> endStates = TransitionStateUtils.findEndStates(transitions);
|
||||
|
||||
assertThat(endStates).containsExactlyInAnyOrder("SHIPPED", "CANCELLED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindEndStates_whenMultipleStatesHaveNoOutgoingTransitions() {
|
||||
List<Transition> transitions = List.of(
|
||||
createTransition("CREATED", "PAID"),
|
||||
createTransition("PAID", "SHIPPED"),
|
||||
createTransition("PAID", "CANCELLED"),
|
||||
createTransition("PAID", "REFUNDED")
|
||||
);
|
||||
|
||||
Set<String> endStates = TransitionStateUtils.findEndStates(transitions);
|
||||
|
||||
assertThat(endStates).containsExactlyInAnyOrder("SHIPPED", "CANCELLED", "REFUNDED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleEmptyTransitionsList() {
|
||||
Set<String> startStates = TransitionStateUtils.findStartStates(List.of());
|
||||
Set<String> endStates = TransitionStateUtils.findEndStates(List.of());
|
||||
|
||||
assertThat(startStates).isEmpty();
|
||||
assertThat(endStates).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleTransitionsWithQuotedStates() {
|
||||
List<Transition> transitions = List.of(
|
||||
createTransition("\"CREATED\"", "\"PAID\""),
|
||||
createTransition("\"PAID\"", "\"SHIPPED\"")
|
||||
);
|
||||
|
||||
Set<String> startStates = TransitionStateUtils.findStartStates(transitions);
|
||||
Set<String> endStates = TransitionStateUtils.findEndStates(transitions);
|
||||
|
||||
assertThat(startStates).containsExactly("CREATED");
|
||||
assertThat(endStates).containsExactly("SHIPPED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleTransitionsWithMixedQuotedAndUnquotedStates() {
|
||||
List<Transition> transitions = List.of(
|
||||
createTransition("CREATED", "\"PAID\""),
|
||||
createTransition("\"PAID\"", "SHIPPED")
|
||||
);
|
||||
|
||||
Set<String> startStates = TransitionStateUtils.findStartStates(transitions);
|
||||
Set<String> endStates = TransitionStateUtils.findEndStates(transitions);
|
||||
|
||||
assertThat(startStates).containsExactly("CREATED");
|
||||
assertThat(endStates).containsExactly("SHIPPED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleTransitionsWithNullStates() {
|
||||
List<Transition> transitions = List.of(
|
||||
createTransition(null, "PAID"),
|
||||
createTransition("PAID", null)
|
||||
);
|
||||
|
||||
Set<String> startStates = TransitionStateUtils.findStartStates(transitions);
|
||||
Set<String> endStates = TransitionStateUtils.findEndStates(transitions);
|
||||
|
||||
assertThat(startStates).isEmpty();
|
||||
assertThat(endStates).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleTransitionsWithEmptyStates() {
|
||||
List<Transition> transitions = List.of(
|
||||
createTransition("", "PAID"),
|
||||
createTransition("PAID", "")
|
||||
);
|
||||
|
||||
Set<String> startStates = TransitionStateUtils.findStartStates(transitions);
|
||||
Set<String> endStates = TransitionStateUtils.findEndStates(transitions);
|
||||
|
||||
assertThat(startStates).isEmpty();
|
||||
assertThat(endStates).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleTransitionsWithMultipleSourceStates() {
|
||||
Transition transition1 = createTransition(List.of("CREATED", "PENDING"), List.of("PAID"));
|
||||
Transition transition2 = createTransition(List.of("PAID"), List.of("SHIPPED"));
|
||||
List<Transition> transitions = List.of(transition1, transition2);
|
||||
|
||||
Set<String> startStates = TransitionStateUtils.findStartStates(transitions);
|
||||
Set<String> endStates = TransitionStateUtils.findEndStates(transitions);
|
||||
|
||||
assertThat(startStates).containsExactlyInAnyOrder("CREATED", "PENDING");
|
||||
assertThat(endStates).containsExactly("SHIPPED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleTransitionsWithMultipleTargetStates() {
|
||||
Transition transition1 = createTransition(
|
||||
List.of("PAID"),
|
||||
List.of("SHIPPED", "CANCELLED")
|
||||
);
|
||||
List<Transition> transitions = List.of(transition1);
|
||||
|
||||
Set<String> startStates = TransitionStateUtils.findStartStates(transitions);
|
||||
Set<String> endStates = TransitionStateUtils.findEndStates(transitions);
|
||||
|
||||
assertThat(startStates).containsExactly("PAID");
|
||||
assertThat(endStates).containsExactlyInAnyOrder("SHIPPED", "CANCELLED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleCircularTransitions() {
|
||||
List<Transition> transitions = List.of(
|
||||
createTransition("A", "B"),
|
||||
createTransition("B", "C"),
|
||||
createTransition("C", "A")
|
||||
);
|
||||
|
||||
Set<String> startStates = TransitionStateUtils.findStartStates(transitions);
|
||||
Set<String> endStates = TransitionStateUtils.findEndStates(transitions);
|
||||
|
||||
assertThat(startStates).isEmpty();
|
||||
assertThat(endStates).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleSelfLoopingTransitions() {
|
||||
List<Transition> transitions = List.of(
|
||||
createTransition("A", "A"),
|
||||
createTransition("A", "B")
|
||||
);
|
||||
|
||||
Set<String> startStates = TransitionStateUtils.findStartStates(transitions);
|
||||
Set<String> endStates = TransitionStateUtils.findEndStates(transitions);
|
||||
|
||||
assertThat(startStates).isEmpty(); // A is both source and target, so not a start state
|
||||
assertThat(endStates).containsExactly("B"); // Only B is a pure end state
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleTransitionsWithWhitespaceInStates() {
|
||||
List<Transition> transitions = List.of(
|
||||
createTransition(" CREATED ", " PAID "),
|
||||
createTransition(" PAID ", " SHIPPED ")
|
||||
);
|
||||
|
||||
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()");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleComplexStateMachine() {
|
||||
List<Transition> transitions = List.of(
|
||||
createTransition("CREATED", "PAID"),
|
||||
createTransition("PAID", "SHIPPED"),
|
||||
createTransition("PAID", "CANCELLED"),
|
||||
createTransition("SHIPPED", "DELIVERED"),
|
||||
createTransition("CANCELLED", "REFUNDED")
|
||||
);
|
||||
|
||||
Set<String> startStates = TransitionStateUtils.findStartStates(transitions);
|
||||
Set<String> endStates = TransitionStateUtils.findEndStates(transitions);
|
||||
|
||||
assertThat(startStates).containsExactly("CREATED");
|
||||
assertThat(endStates).containsExactlyInAnyOrder("DELIVERED", "REFUNDED");
|
||||
}
|
||||
|
||||
private Transition createTransition(String source, String target) {
|
||||
Transition transition = new Transition();
|
||||
if (source != null) {
|
||||
transition.getSourceStates().add(source);
|
||||
}
|
||||
if (target != null) {
|
||||
transition.getTargetStates().add(target);
|
||||
}
|
||||
return transition;
|
||||
}
|
||||
|
||||
private Transition createTransition(List<String> sources, List<String> targets) {
|
||||
Transition transition = new Transition();
|
||||
if (sources != null) {
|
||||
transition.getSourceStates().addAll(sources);
|
||||
}
|
||||
if (targets != null) {
|
||||
transition.getTargetStates().addAll(targets);
|
||||
}
|
||||
return transition;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
package click.kamil.springstatemachineexporter.ast.in;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
|
||||
|
||||
class AstFileFinderTest {
|
||||
|
||||
private static final String CLASS_WITH_ANNOTATION = """
|
||||
@Service
|
||||
public class TestService {
|
||||
public void doSomething() {
|
||||
// implementation
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private static final String CLASS_WITH_MULTIPLE_ANNOTATIONS = """
|
||||
@Service
|
||||
@Component
|
||||
public class TestService {
|
||||
public void doSomething() {
|
||||
// implementation
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private static final String INTERFACE_WITH_ANNOTATION = """
|
||||
@FunctionalInterface
|
||||
public interface TestInterface {
|
||||
void doSomething();
|
||||
}
|
||||
""";
|
||||
|
||||
private static final String CLASS_WITHOUT_ANNOTATION = """
|
||||
public class TestService {
|
||||
public void doSomething() {
|
||||
// implementation
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private static final String CLASS_WITH_DIFFERENT_ANNOTATION = """
|
||||
@Repository
|
||||
public class TestRepository {
|
||||
public void doSomething() {
|
||||
// implementation
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private static final String CLASS_WITH_FULLY_QUALIFIED_ANNOTATION = """
|
||||
@org.springframework.stereotype.Service
|
||||
public class TestService {
|
||||
public void doSomething() {
|
||||
// implementation
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private static final String MULTIPLE_CLASSES = """
|
||||
@Service
|
||||
public class TestService {
|
||||
public void doSomething() {
|
||||
// implementation
|
||||
}
|
||||
}
|
||||
|
||||
public class AnotherClass {
|
||||
// no annotation
|
||||
}
|
||||
""";
|
||||
|
||||
private static final String CLASS_WITH_ANNOTATION_AND_FIELDS = """
|
||||
@Service
|
||||
public class TestService {
|
||||
private String name;
|
||||
|
||||
public void doSomething() {
|
||||
// implementation
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private static final String EMPTY_SOURCE = "";
|
||||
|
||||
private static final String INVALID_JAVA_SYNTAX = """
|
||||
@Service
|
||||
public class TestService {
|
||||
// missing closing brace
|
||||
""";
|
||||
|
||||
@Test
|
||||
void shouldReturnTrue_whenClassHasMatchingAnnotation() {
|
||||
AstFileFinder finder = new AstFileFinder();
|
||||
List<String> annotationNames = List.of("Service");
|
||||
|
||||
boolean result = finder.hasClassWithAnnotation(CLASS_WITH_ANNOTATION, annotationNames);
|
||||
|
||||
assertThat(result).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnTrue_whenClassHasMultipleMatchingAnnotations() {
|
||||
AstFileFinder finder = new AstFileFinder();
|
||||
List<String> annotationNames = List.of("Service", "Component");
|
||||
|
||||
boolean result = finder.hasClassWithAnnotation(CLASS_WITH_MULTIPLE_ANNOTATIONS, annotationNames);
|
||||
|
||||
assertThat(result).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnFalse_whenClassHasNoAnnotation() {
|
||||
AstFileFinder finder = new AstFileFinder();
|
||||
List<String> annotationNames = List.of("Service");
|
||||
|
||||
boolean result = finder.hasClassWithAnnotation(CLASS_WITHOUT_ANNOTATION, annotationNames);
|
||||
|
||||
assertThat(result).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnFalse_whenClassHasDifferentAnnotation() {
|
||||
AstFileFinder finder = new AstFileFinder();
|
||||
List<String> annotationNames = List.of("Service");
|
||||
|
||||
boolean result = finder.hasClassWithAnnotation(CLASS_WITH_DIFFERENT_ANNOTATION, annotationNames);
|
||||
|
||||
assertThat(result).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnTrue_whenClassHasFullyQualifiedAnnotation() {
|
||||
AstFileFinder finder = new AstFileFinder();
|
||||
List<String> annotationNames = List.of("org.springframework.stereotype.Service");
|
||||
|
||||
boolean result = finder.hasClassWithAnnotation(CLASS_WITH_FULLY_QUALIFIED_ANNOTATION, annotationNames);
|
||||
|
||||
assertThat(result).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnTrue_whenMultipleClassesAndOneHasAnnotation() {
|
||||
AstFileFinder finder = new AstFileFinder();
|
||||
List<String> annotationNames = List.of("Service");
|
||||
|
||||
boolean result = finder.hasClassWithAnnotation(MULTIPLE_CLASSES, annotationNames);
|
||||
|
||||
assertThat(result).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnFalse_whenInterfaceHasAnnotation() {
|
||||
AstFileFinder finder = new AstFileFinder();
|
||||
List<String> annotationNames = List.of("FunctionalInterface");
|
||||
|
||||
boolean result = finder.hasClassWithAnnotation(INTERFACE_WITH_ANNOTATION, annotationNames);
|
||||
|
||||
assertThat(result).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnFalse_whenEmptyAnnotationList() {
|
||||
AstFileFinder finder = new AstFileFinder();
|
||||
List<String> annotationNames = Collections.emptyList();
|
||||
|
||||
boolean result = finder.hasClassWithAnnotation(CLASS_WITH_ANNOTATION, annotationNames);
|
||||
|
||||
assertThat(result).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnFalse_whenEmptySource() {
|
||||
AstFileFinder finder = new AstFileFinder();
|
||||
List<String> annotationNames = List.of("Service");
|
||||
|
||||
boolean result = finder.hasClassWithAnnotation(EMPTY_SOURCE, annotationNames);
|
||||
|
||||
assertThat(result).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowException_whenNullAnnotationList() {
|
||||
AstFileFinder finder = new AstFileFinder();
|
||||
|
||||
// when & then
|
||||
assertThatThrownBy(() ->
|
||||
finder.hasClassWithAnnotation(CLASS_WITH_ANNOTATION, null)
|
||||
).isInstanceOf(NullPointerException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnTrue_whenClassHasAnnotationAndFields() {
|
||||
AstFileFinder finder = new AstFileFinder();
|
||||
List<String> annotationNames = List.of("Service");
|
||||
|
||||
boolean result = finder.hasClassWithAnnotation(CLASS_WITH_ANNOTATION_AND_FIELDS, annotationNames);
|
||||
|
||||
assertThat(result).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleInvalidJavaSyntax() {
|
||||
AstFileFinder finder = new AstFileFinder();
|
||||
List<String> annotationNames = List.of("Service");
|
||||
|
||||
boolean result = finder.hasClassWithAnnotation(INVALID_JAVA_SYNTAX, annotationNames);
|
||||
|
||||
// AST parser might still create a valid AST even with syntax errors
|
||||
// The result depends on how the parser handles the invalid syntax
|
||||
assertThat(result).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnTrue_whenAnnotationNameMatchesCaseInsensitive() {
|
||||
AstFileFinder finder = new AstFileFinder();
|
||||
List<String> annotationNames = List.of("service"); // lowercase
|
||||
|
||||
boolean result = finder.hasClassWithAnnotation(CLASS_WITH_ANNOTATION, annotationNames);
|
||||
|
||||
assertThat(result).isFalse(); // AST parser is case-sensitive
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnTrue_whenMultipleAnnotationNamesProvided() {
|
||||
AstFileFinder finder = new AstFileFinder();
|
||||
List<String> annotationNames = List.of("Component", "Service", "Repository");
|
||||
|
||||
boolean result = finder.hasClassWithAnnotation(CLASS_WITH_ANNOTATION, annotationNames);
|
||||
|
||||
assertThat(result).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnFalse_whenNoMatchingAnnotationNames() {
|
||||
AstFileFinder finder = new AstFileFinder();
|
||||
List<String> annotationNames = List.of("Component", "Repository", "Controller");
|
||||
|
||||
boolean result = finder.hasClassWithAnnotation(CLASS_WITH_ANNOTATION, annotationNames);
|
||||
|
||||
assertThat(result).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnFalse_whenSourceContainsOnlyComments() {
|
||||
String sourceWithComments = """
|
||||
// This is a comment
|
||||
/* Another comment */
|
||||
/** JavaDoc comment */
|
||||
""";
|
||||
AstFileFinder finder = new AstFileFinder();
|
||||
List<String> annotationNames = List.of("Service");
|
||||
|
||||
boolean result = finder.hasClassWithAnnotation(sourceWithComments, annotationNames);
|
||||
|
||||
assertThat(result).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnFalse_whenSourceContainsOnlyPackageDeclaration() {
|
||||
String sourceWithPackage = "package com.example;";
|
||||
AstFileFinder finder = new AstFileFinder();
|
||||
List<String> annotationNames = List.of("Service");
|
||||
|
||||
boolean result = finder.hasClassWithAnnotation(sourceWithPackage, annotationNames);
|
||||
|
||||
assertThat(result).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnTrue_whenClassHasAnnotationWithParameters() {
|
||||
String classWithAnnotatedParams = """
|
||||
@Service("testService")
|
||||
public class TestService {
|
||||
public void doSomething() {
|
||||
// implementation
|
||||
}
|
||||
}
|
||||
""";
|
||||
AstFileFinder finder = new AstFileFinder();
|
||||
List<String> annotationNames = List.of("Service");
|
||||
|
||||
boolean result = finder.hasClassWithAnnotation(classWithAnnotatedParams, annotationNames);
|
||||
|
||||
assertThat(result).isTrue();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package click.kamil.springstatemachineexporter.utils;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public record TestScenario(
|
||||
String name,
|
||||
Path inputPath,
|
||||
Path goldenPath,
|
||||
String baseName
|
||||
) {}
|
||||
Reference in New Issue
Block a user