Move TestDbCallbackExecutor from rule-engine test sources to
common/util main sources as DirectListeningExecutor, making it
available to all modules. Convert to an enum singleton since the
executor is stateless. Widen JpaAbstractDaoListeningExecutorService
service field type from JpaExecutorService to ListeningExecutor to
allow injecting DirectListeningExecutor in tests. Fix
AbstractChunkedAggregationTimeseriesDaoTest NPE by injecting the
direct executor into the spy.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The stack-based check in DefaultTbContext.input() only catches a loop
on the second Kafka round-trip through the same rule chain. When the
target rule chain equals the node's own rule chain (direct self-loop),
the first iteration is always unnecessary and should be rejected
immediately before any enqueue occurs.
Add an eager check in TbRuleChainInputNode.onMsg(): if the resolved
targetRuleChainId equals ctx.getSelf().getRuleChainId() (the rule chain
that contains this node), tellFailure() is called right away.
Together with the existing stack-based check in DefaultTbContext.input()
this forms a two-layer defence:
- Layer 1 (TbRuleChainInputNode): direct loops A→A, caught immediately
- Layer 2 (DefaultTbContext): indirect cycles A→B→A, caught on second pass
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Delay and deduplication rule nodes were creating brand new TbMsg objects
instead of copying the original, which reset the ruleNodeExecCounter to 0.
This allowed bypassing the maxRuleNodeExecutionsPerMessage limit.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The deleteRequestWithBody and deleteRequestWithoutBody tests used
time-based synchronization (Thread.sleep) to wait for the async
WebClient response callback. Under CI load, the callback could fire
after the verify() check, causing flaky failures.
Replace the sleep-based approach with Mockito's timeout() on verify,
which properly polls for the async interaction.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>