B
This commit is contained in:
@@ -157,22 +157,8 @@ public class HeuristicEventMatchingEngine implements EventMatchingEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isWildcardVariable(String eventStr) {
|
private boolean isWildcardVariable(String eventStr) {
|
||||||
if (eventStr == null) return false;
|
return click.kamil.springstatemachineexporter.analysis.resolver.DynamicTriggerExpressions
|
||||||
|
.isDynamic(eventStr);
|
||||||
if (click.kamil.springstatemachineexporter.analysis.service.EventCarrierPolicy
|
|
||||||
.isDynamicEventToken(eventStr)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eventStr.contains(".valueOf(") || eventStr.contains("valueOf (")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eventStr.contains(" ? ") && eventStr.contains(" : ")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String simplify(String name) {
|
private String simplify(String name) {
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package click.kamil.springstatemachineexporter.analysis.resolver;
|
||||||
|
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.service.EventCarrierPolicy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared detection of AST-derived trigger event expressions that must not be rewritten
|
||||||
|
* as package-canonical enum constants.
|
||||||
|
*/
|
||||||
|
public final class DynamicTriggerExpressions {
|
||||||
|
|
||||||
|
private DynamicTriggerExpressions() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True for unresolved tokens, {@code valueOf}, ternaries, method calls, placeholders,
|
||||||
|
* and bare lowercase identifiers — not for concrete enum constants like {@code OrderEvent.PAY}.
|
||||||
|
*/
|
||||||
|
public static boolean isDynamic(String eventStr) {
|
||||||
|
if (eventStr == null || eventStr.isBlank()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (eventStr.startsWith("<SYMBOLIC: ")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (eventStr.contains("${")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (EventCarrierPolicy.isDynamicEventToken(eventStr)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (eventStr.contains(".valueOf(") || eventStr.contains("valueOf (")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (eventStr.contains(" ? ") && eventStr.contains(" : ")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (eventStr.contains("(") || eventStr.contains(")")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (Character.isLowerCase(eventStr.charAt(0)) && !eventStr.contains(".")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -672,32 +672,7 @@ public final class MachineEnumCanonicalizer {
|
|||||||
* ({@code getType()}, {@code valueOf(...)}, ternary dispatchers, parameter names, etc.).
|
* ({@code getType()}, {@code valueOf(...)}, ternary dispatchers, parameter names, etc.).
|
||||||
*/
|
*/
|
||||||
public static boolean isDynamicTriggerExpression(String eventStr) {
|
public static boolean isDynamicTriggerExpression(String eventStr) {
|
||||||
if (eventStr == null || eventStr.isBlank()) {
|
return DynamicTriggerExpressions.isDynamic(eventStr);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (eventStr.startsWith("<SYMBOLIC: ")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (eventStr.contains("${")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (click.kamil.springstatemachineexporter.analysis.service.EventCarrierPolicy
|
|
||||||
.isDynamicEventToken(eventStr)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (eventStr.contains(".valueOf(") || eventStr.contains("valueOf (")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (eventStr.contains(" ? ") && eventStr.contains(" : ")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (eventStr.contains("(") || eventStr.contains(")")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (Character.isLowerCase(eventStr.charAt(0)) && !eventStr.contains(".")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,56 +2,53 @@ package click.kamil.springstatemachineexporter.analysis.service;
|
|||||||
|
|
||||||
import click.kamil.springstatemachineexporter.analysis.resolver.BooleanConstraintEvaluator;
|
import click.kamil.springstatemachineexporter.analysis.resolver.BooleanConstraintEvaluator;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Carrier-aware constraint helpers shared by call-graph filtering and transition linking.
|
* Carrier-aware constraint helpers shared by call-graph filtering and transition linking.
|
||||||
* Payload/event binding clauses are not machine-domain evidence.
|
* Payload/event binding clauses are classified by primary identifier via {@link EventCarrierPolicy},
|
||||||
|
* not by a parallel name-alternation regex.
|
||||||
*/
|
*/
|
||||||
public final class CarrierConstraintSupport {
|
public final class CarrierConstraintSupport {
|
||||||
|
|
||||||
|
private static final Pattern EQUALS_CALL = Pattern.compile(
|
||||||
|
"(?i)(?:\"[^\"]+\"|'[^']+')\\s*\\.\\s*(?:equalsIgnoreCase|equals)\\s*\\(\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*\\)"
|
||||||
|
+ "|"
|
||||||
|
+ "(?i)([A-Za-z_][A-Za-z0-9_]*)\\s*\\.\\s*(?:equalsIgnoreCase|equals)\\s*\\(\\s*(?:\"[^\"]+\"|'[^']+')\\s*\\)");
|
||||||
|
|
||||||
|
private static final Pattern EQ_COMPARE = Pattern.compile(
|
||||||
|
"(?i)([A-Za-z_][A-Za-z0-9_]*)\\s*(?:==|!=)\\s*(?:\"[^\"]+\"|'[^']+'|[A-Za-z_][A-Za-z0-9_.]*)"
|
||||||
|
+ "|"
|
||||||
|
+ "(?i)(?:\"[^\"]+\"|'[^']+'|[A-Za-z_][A-Za-z0-9_.]*)\\s*(?:==|!=)\\s*([A-Za-z_][A-Za-z0-9_]*)");
|
||||||
|
|
||||||
private CarrierConstraintSupport() {
|
private CarrierConstraintSupport() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes equals / {@code ==} / {@code !=} clauses whose parameter is an event/payload carrier,
|
* Removes AND-clauses whose primary identifier is an event/payload carrier,
|
||||||
* leaving machine-domain discriminators for {@link BooleanConstraintEvaluator#isCompatibleWithMachineDomain}.
|
* leaving machine-domain discriminators for {@link BooleanConstraintEvaluator#isCompatibleWithMachineDomain}.
|
||||||
*/
|
*/
|
||||||
public static String stripNonMachineClauses(String constraint) {
|
public static String stripNonMachineClauses(String constraint) {
|
||||||
if (constraint == null || constraint.isBlank()) {
|
if (constraint == null || constraint.isBlank()) {
|
||||||
return constraint;
|
return constraint;
|
||||||
}
|
}
|
||||||
String eventLike = "(?i)(" + EventCarrierPolicy.carrierNameAlternation() + ")";
|
List<String> kept = new ArrayList<>();
|
||||||
String stripped = constraint.replaceAll(
|
for (String clause : splitAndClauses(constraint)) {
|
||||||
"\"[^\"]+\"\\s*\\.\\s*equalsIgnoreCase\\s*\\(\\s*" + eventLike + "\\s*\\)",
|
String ident = primaryIdent(clause);
|
||||||
"true");
|
if (ident != null && EventCarrierPolicy.isCarrierParamName(ident)) {
|
||||||
stripped = stripped.replaceAll(
|
kept.add("true");
|
||||||
eventLike + "\\s*\\.\\s*equalsIgnoreCase\\s*\\(\\s*\"[^\"]+\"\\s*\\)",
|
continue;
|
||||||
"true");
|
}
|
||||||
stripped = stripped.replaceAll(
|
kept.add(clause.trim());
|
||||||
"\"[^\"]+\"\\s*\\.\\s*equals\\s*\\(\\s*" + eventLike + "\\s*\\)",
|
}
|
||||||
"true");
|
return joinAndTidy(kept);
|
||||||
stripped = stripped.replaceAll(
|
|
||||||
eventLike + "\\s*\\.\\s*equals\\s*\\(\\s*\"[^\"]+\"\\s*\\)",
|
|
||||||
"true");
|
|
||||||
stripped = stripped.replaceAll(
|
|
||||||
eventLike + "\\s*==\\s*\"[^\"]+\"",
|
|
||||||
"true");
|
|
||||||
stripped = stripped.replaceAll(
|
|
||||||
"\"[^\"]+\"\\s*==\\s*" + eventLike,
|
|
||||||
"true");
|
|
||||||
stripped = stripped.replaceAll(
|
|
||||||
eventLike + "\\s*!=\\s*\"[^\"]+\"",
|
|
||||||
"true");
|
|
||||||
stripped = stripped.replaceAll(
|
|
||||||
"\"[^\"]+\"\\s*!=\\s*" + eventLike,
|
|
||||||
"true");
|
|
||||||
stripped = stripped.replaceAll("&&\\s*&&+", "&&");
|
|
||||||
stripped = stripped.replaceAll("^\\s*&&\\s*", "");
|
|
||||||
stripped = stripped.replaceAll("\\s*&&\\s*$", "");
|
|
||||||
return stripped.trim();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,13 +59,17 @@ public final class CarrierConstraintSupport {
|
|||||||
if (constraint == null || constraint.isBlank() || bindings == null || bindings.isEmpty()) {
|
if (constraint == null || constraint.isBlank() || bindings == null || bindings.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
Set<String> carrierIdentsInConstraint = carrierPrimaryIdents(constraint);
|
||||||
|
if (carrierIdentsInConstraint.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
Map<String, String> carrierBindings = new LinkedHashMap<>();
|
Map<String, String> carrierBindings = new LinkedHashMap<>();
|
||||||
for (Map.Entry<String, String> entry : bindings.entrySet()) {
|
for (Map.Entry<String, String> entry : bindings.entrySet()) {
|
||||||
String key = entry.getKey();
|
String key = entry.getKey();
|
||||||
if (key == null || !EventCarrierPolicy.isCarrierParamName(key)) {
|
if (key == null || !EventCarrierPolicy.isCarrierParamName(key)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!constraintMentionsIdent(constraint, key)) {
|
if (!containsIgnoreCase(carrierIdentsInConstraint, key)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
carrierBindings.put(key, entry.getValue());
|
carrierBindings.put(key, entry.getValue());
|
||||||
@@ -79,11 +80,111 @@ public final class CarrierConstraintSupport {
|
|||||||
return !BooleanConstraintEvaluator.isCompatibleWithKnownBindings(constraint, carrierBindings);
|
return !BooleanConstraintEvaluator.isCompatibleWithKnownBindings(constraint, carrierBindings);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean constraintMentionsIdent(String constraint, String ident) {
|
private static Set<String> carrierPrimaryIdents(String constraint) {
|
||||||
if (constraint == null || ident == null || ident.isBlank()) {
|
Set<String> idents = new LinkedHashSet<>();
|
||||||
|
for (String clause : splitAndClauses(constraint)) {
|
||||||
|
String ident = primaryIdent(clause);
|
||||||
|
if (ident != null && EventCarrierPolicy.isCarrierParamName(ident)) {
|
||||||
|
idents.add(ident);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return idents;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean containsIgnoreCase(Set<String> idents, String key) {
|
||||||
|
for (String ident : idents) {
|
||||||
|
if (ident.equalsIgnoreCase(key)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<String> splitAndClauses(String constraint) {
|
||||||
|
List<String> clauses = new ArrayList<>();
|
||||||
|
StringBuilder current = new StringBuilder();
|
||||||
|
int depth = 0;
|
||||||
|
for (int i = 0; i < constraint.length(); i++) {
|
||||||
|
char c = constraint.charAt(i);
|
||||||
|
if (c == '(') {
|
||||||
|
depth++;
|
||||||
|
current.append(c);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (c == ')') {
|
||||||
|
depth = Math.max(0, depth - 1);
|
||||||
|
current.append(c);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (depth == 0
|
||||||
|
&& c == '&'
|
||||||
|
&& i + 1 < constraint.length()
|
||||||
|
&& constraint.charAt(i + 1) == '&') {
|
||||||
|
String piece = current.toString().trim();
|
||||||
|
if (!piece.isEmpty()) {
|
||||||
|
clauses.add(piece);
|
||||||
|
}
|
||||||
|
current.setLength(0);
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
current.append(c);
|
||||||
|
}
|
||||||
|
String tail = current.toString().trim();
|
||||||
|
if (!tail.isEmpty()) {
|
||||||
|
clauses.add(tail);
|
||||||
|
}
|
||||||
|
if (clauses.isEmpty() && constraint != null && !constraint.isBlank()) {
|
||||||
|
clauses.add(constraint.trim());
|
||||||
|
}
|
||||||
|
return clauses;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primary parameter/local identifier in a comparison or equals clause.
|
||||||
|
*/
|
||||||
|
static String primaryIdent(String clause) {
|
||||||
|
if (clause == null || clause.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String trimmed = clause.trim();
|
||||||
|
Matcher equals = EQUALS_CALL.matcher(trimmed);
|
||||||
|
if (equals.find()) {
|
||||||
|
String a = equals.group(1);
|
||||||
|
String b = equals.group(2);
|
||||||
|
return a != null ? a : b;
|
||||||
|
}
|
||||||
|
Matcher eq = EQ_COMPARE.matcher(trimmed);
|
||||||
|
if (eq.find()) {
|
||||||
|
String a = eq.group(1);
|
||||||
|
String b = eq.group(2);
|
||||||
|
String left = a != null ? a : b;
|
||||||
|
if (left != null && looksLikeSimpleIdent(left)) {
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean looksLikeSimpleIdent(String value) {
|
||||||
|
if (value == null || value.isBlank() || value.contains(".")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Pattern word = Pattern.compile("(?i)\\b" + Pattern.quote(ident) + "\\b");
|
char first = value.charAt(0);
|
||||||
return word.matcher(constraint).find();
|
return Character.isJavaIdentifierStart(first)
|
||||||
|
&& value.chars().allMatch(Character::isJavaIdentifierPart)
|
||||||
|
&& (Character.isLowerCase(first) || EventCarrierPolicy.isCarrierParamName(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String joinAndTidy(List<String> clauses) {
|
||||||
|
if (clauses.isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
String joined = String.join(" && ", clauses);
|
||||||
|
joined = joined.replaceAll("\\s+", " ").trim();
|
||||||
|
joined = joined.replaceAll("(?:^|\\s)&&\\s*", " && ");
|
||||||
|
joined = joined.replaceAll("^\\s*&&\\s*", "");
|
||||||
|
joined = joined.replaceAll("\\s*&&\\s*$", "");
|
||||||
|
return joined.trim();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
|
|||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shared identity for parameters that carry event / payload / machine-route discriminators
|
* Shared identity for parameters that carry event / payload / machine-route discriminators
|
||||||
@@ -200,17 +198,6 @@ public final class EventCarrierPolicy {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Alternation for carrier-clause regex stripping ({@link CarrierConstraintSupport}).
|
|
||||||
* Includes camelCase aliases so source that uses {@code commandKey} matches under {@code (?i)}.
|
|
||||||
*/
|
|
||||||
static String carrierNameAlternation() {
|
|
||||||
return Stream.concat(CARRIER_NAMES.stream(), Stream.of("commandKey", "eventString", "actionKey"))
|
|
||||||
.distinct()
|
|
||||||
.sorted()
|
|
||||||
.collect(Collectors.joining("|"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean hasAnnotation(EntryPoint.Parameter parameter, String simpleName) {
|
private static boolean hasAnnotation(EntryPoint.Parameter parameter, String simpleName) {
|
||||||
if (parameter.getAnnotations() == null || simpleName == null) {
|
if (parameter.getAnnotations() == null || simpleName == null) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package click.kamil.springstatemachineexporter.analysis.resolver;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class DynamicTriggerExpressionsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void bareCarrierTokensAreDynamic() {
|
||||||
|
assertThat(DynamicTriggerExpressions.isDynamic("event")).isTrue();
|
||||||
|
assertThat(DynamicTriggerExpressions.isDynamic("message")).isTrue();
|
||||||
|
assertThat(DynamicTriggerExpressions.isDynamic("payload")).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void concreteEnumConstantsAreNotDynamic() {
|
||||||
|
assertThat(DynamicTriggerExpressions.isDynamic("OrderEvent.PAY")).isFalse();
|
||||||
|
assertThat(DynamicTriggerExpressions.isDynamic("com.example.OrderEvent.SHIP")).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void valueOfAndTernaryAreDynamic() {
|
||||||
|
assertThat(DynamicTriggerExpressions.isDynamic("OrderEvent.valueOf(x)")).isTrue();
|
||||||
|
assertThat(DynamicTriggerExpressions.isDynamic("cond ? OrderEvent.PAY : OrderEvent.SHIP")).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void symbolicMarkerIsNotDynamicForCanonicalRewrite() {
|
||||||
|
assertThat(DynamicTriggerExpressions.isDynamic("<SYMBOLIC: com.example.OrderEvent.*>")).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void placeholderAndBareLowercaseAreDynamic() {
|
||||||
|
assertThat(DynamicTriggerExpressions.isDynamic("${event}")).isTrue();
|
||||||
|
assertThat(DynamicTriggerExpressions.isDynamic("cmd")).isTrue();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,16 @@ class CarrierConstraintSupportTest {
|
|||||||
"\"ORDER\".equalsIgnoreCase(machineType) && message == \"PAY\"");
|
"\"ORDER\".equalsIgnoreCase(machineType) && message == \"PAY\"");
|
||||||
assertThat(stripped).contains("machineType");
|
assertThat(stripped).contains("machineType");
|
||||||
assertThat(stripped).doesNotContain("message == ");
|
assertThat(stripped).doesNotContain("message == ");
|
||||||
|
assertThat(stripped).contains("true");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void stripMixedLeavesOnlyDomainClauseMeaningfully() {
|
||||||
|
String stripped = CarrierConstraintSupport.stripNonMachineClauses(
|
||||||
|
"\"ORDER\".equalsIgnoreCase(machineType) && message == \"PAY\"");
|
||||||
|
assertThat(CarrierConstraintSupport.primaryIdent(
|
||||||
|
CarrierConstraintSupport.splitAndClauses(stripped).get(0)))
|
||||||
|
.isEqualTo("machineType");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -43,4 +53,16 @@ class CarrierConstraintSupportTest {
|
|||||||
"command == ORDER_PAY",
|
"command == ORDER_PAY",
|
||||||
Map.of("commandKey", "order.pay"))).isFalse();
|
Map.of("commandKey", "order.pay"))).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void primaryIdentExtractsEqualsIgnoreCaseArgument() {
|
||||||
|
assertThat(CarrierConstraintSupport.primaryIdent("\"PAY\".equalsIgnoreCase(message)"))
|
||||||
|
.isEqualTo("message");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void primaryIdentExtractsEqLeftOperand() {
|
||||||
|
assertThat(CarrierConstraintSupport.primaryIdent("message == \"PAY\""))
|
||||||
|
.isEqualTo("message");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user