From c6db825807a2e0071d660c1ea21ce9be333fe56e Mon Sep 17 00:00:00 2001 From: Kamil Patryk Kozakowski Date: Wed, 24 Jun 2026 22:03:57 +0200 Subject: [PATCH] test added --- .../HeuristicCallGraphEngineExtendedTest.java | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/HeuristicCallGraphEngineExtendedTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/HeuristicCallGraphEngineExtendedTest.java index 92a9393..378b11f 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/HeuristicCallGraphEngineExtendedTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/HeuristicCallGraphEngineExtendedTest.java @@ -130,4 +130,101 @@ class HeuristicCallGraphEngineExtendedTest { assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()) .containsExactlyInAnyOrder("TransitionEnum.STATE_Y"); } + /** + * Diagnostic: Enum tracked through an overridden method and a super() call. + * + * Pattern: + * 1. EntryPoint calls an overridden method on a subclass (CustomHandler.handleEvent) + * with an initial enum (ActionType.BEGIN_CHECKOUT). + * 2. The subclass intercepts the call, does some localized logic, and delegates + * back to the parent using super.handleEvent(ActionType). + * 3. The parent class (BaseHandler) takes that enum, transforms it via a switch + * statement to a TransitionEnum, and fires the state machine. + * + * Root cause targeted: The engine must successfully resolve the `super` keyword + * to the parent class's AST node, maintain the argument mapping across the inheritance + * boundary, and evaluate the switch to get the final TransitionEnum. It must not + * get stuck in a recursive loop or eagerly return ActionType.BEGIN_CHECKOUT. + */ + @Test + void shouldResolveMappedEnumAcrossOverriddenMethodAndSuperDelegation() throws IOException { + String source = """ + package com.example; + + public class ApiEndpoint { + private CustomHandler handler; + + public void triggerCheckout() { + // Entry point passes the initial enum + handler.handleEvent(ActionType.BEGIN_CHECKOUT); + } + } + + abstract class BaseHandler { + protected StateMachine machine; + + // The method that gets overridden, but ultimately does the work + public void handleEvent(ActionType action) { + TransitionEnum transition = switch (action) { + case BEGIN_CHECKOUT -> TransitionEnum.CHECKOUT_STARTED; // Expected + case ABORT_CHECKOUT -> TransitionEnum.CHECKOUT_CANCELLED; + }; + machine.fire(transition); + } + } + + class CustomHandler extends BaseHandler { + + @Override + public void handleEvent(ActionType action) { + // Custom logic before delegation (simulated) + if (action == null) return; + + // The trap: The engine must follow 'super' to BaseHandler + // and carry the 'action' argument with it. + super.handleEvent(action); + } + } + + class StateMachine { + public void fire(TransitionEnum event) {} + } + + enum ActionType { BEGIN_CHECKOUT, ABORT_CHECKOUT } + enum TransitionEnum { CHECKOUT_STARTED, CHECKOUT_CANCELLED } + """; + + java.nio.file.Path tempDir = java.nio.file.Files.createTempDirectory("callgraph_test_super_override"); + java.nio.file.Files.writeString(tempDir.resolve("OrderConfig.java"), source); + + CodebaseContext context = new CodebaseContext(); + context.scan(tempDir); + + HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context); + + EntryPoint entryPoint = EntryPoint.builder() + .className("com.example.ApiEndpoint") + .methodName("triggerCheckout") + .build(); + + TriggerPoint trigger = TriggerPoint.builder() + .className("com.example.StateMachine") + .methodName("fire") + .event("event") + .build(); + + List chains = engine.findChains(List.of(entryPoint), List.of(trigger)); + + assertThat(chains).hasSize(1); + + System.out.println("SUPER DELEGATION polymorphicEvents: " + + chains.get(0).getTriggerPoint().getPolymorphicEvents()); + + // The assertion to prove the bug is fixed. + // It MUST NOT contain "ActionType.BEGIN_CHECKOUT". + // It MUST successfully trace through super.handleEvent() and resolve the switch. + assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()) + .containsExactlyInAnyOrder("TransitionEnum.CHECKOUT_STARTED"); + } + }