diff --git a/pom.xml b/pom.xml
index ad45f74b..22da5c32 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,7 +23,7 @@
UTF-8
1.0.9
0.3.1
- 1.128-79c32f2
+ 1.134-b64531d
1.7-6d29f86
1.44-5dbd6d4
21.12.0
diff --git a/src/main/java/dev/vality/fraudbusters/constant/FilterField.java b/src/main/java/dev/vality/fraudbusters/constant/FilterField.java
new file mode 100644
index 00000000..bbe65515
--- /dev/null
+++ b/src/main/java/dev/vality/fraudbusters/constant/FilterField.java
@@ -0,0 +1,6 @@
+package dev.vality.fraudbusters.constant;
+
+public interface FilterField {
+
+ String getValue();
+}
diff --git a/src/main/java/dev/vality/fraudbusters/constant/FraudResultField.java b/src/main/java/dev/vality/fraudbusters/constant/FraudResultField.java
index b5021f83..b8f39c75 100644
--- a/src/main/java/dev/vality/fraudbusters/constant/FraudResultField.java
+++ b/src/main/java/dev/vality/fraudbusters/constant/FraudResultField.java
@@ -4,7 +4,7 @@
import lombok.Getter;
@AllArgsConstructor
-public enum FraudResultField {
+public enum FraudResultField implements FilterField {
RESULT_STATUS("resultStatus"),
CHECKED_RULE("checkedRule"),
diff --git a/src/main/java/dev/vality/fraudbusters/constant/PaymentField.java b/src/main/java/dev/vality/fraudbusters/constant/PaymentField.java
index 325d3601..f49a0d2b 100644
--- a/src/main/java/dev/vality/fraudbusters/constant/PaymentField.java
+++ b/src/main/java/dev/vality/fraudbusters/constant/PaymentField.java
@@ -4,7 +4,7 @@
import lombok.Getter;
@AllArgsConstructor
-public enum PaymentField {
+public enum PaymentField implements FilterField {
EVENT_TIME("eventTime"),
SHOP_ID("shopId"),
diff --git a/src/main/java/dev/vality/fraudbusters/converter/FilterConverter.java b/src/main/java/dev/vality/fraudbusters/converter/FilterConverter.java
index 135241b1..f9ebe35a 100644
--- a/src/main/java/dev/vality/fraudbusters/converter/FilterConverter.java
+++ b/src/main/java/dev/vality/fraudbusters/converter/FilterConverter.java
@@ -3,6 +3,8 @@
import dev.vality.damsel.fraudbusters.Filter;
import dev.vality.damsel.fraudbusters.Page;
import dev.vality.damsel.fraudbusters.Sort;
+import dev.vality.fraudbusters.constant.FilterField;
+import dev.vality.fraudbusters.constant.FraudResultField;
import dev.vality.fraudbusters.constant.PaymentField;
import dev.vality.fraudbusters.constant.SortOrder;
import dev.vality.fraudbusters.service.dto.FieldType;
@@ -75,18 +77,24 @@ private Set assembleSearchFields(Filter filter) {
if (filter.isSetInvoiceId() && StringUtils.hasLength(filter.getInvoiceId())) {
addSearchField(searchFields, PaymentField.INVOICE_ID, FieldType.STRING, filter.getInvoiceId());
}
+ if (filter.isSetTemplate() && StringUtils.hasLength(filter.getTemplate())) {
+ addSearchField(searchFields, FraudResultField.CHECKED_TEMPLATE, FieldType.STRING, filter.getTemplate());
+ }
+ if (filter.isSetRule() && StringUtils.hasLength(filter.getRule())) {
+ addSearchField(searchFields, FraudResultField.CHECKED_RULE, FieldType.STRING, filter.getRule());
+ }
return searchFields;
}
private void addSearchField(Set searchFields,
- PaymentField cardToken,
- FieldType string,
- String filter) {
+ FilterField field,
+ FieldType type,
+ String filterValue) {
searchFields.add(
SearchFieldDto.builder()
- .field(cardToken)
- .type(string)
- .value(filter)
+ .field(field)
+ .type(type)
+ .value(filterValue)
.build()
);
}
diff --git a/src/main/java/dev/vality/fraudbusters/repository/clickhouse/impl/FraudResultRepository.java b/src/main/java/dev/vality/fraudbusters/repository/clickhouse/impl/FraudResultRepository.java
index 630089e0..62ab258a 100644
--- a/src/main/java/dev/vality/fraudbusters/repository/clickhouse/impl/FraudResultRepository.java
+++ b/src/main/java/dev/vality/fraudbusters/repository/clickhouse/impl/FraudResultRepository.java
@@ -65,7 +65,7 @@ public void insertBatch(List events) {
@Override
public List getByFilter(FilterDto filter) {
- String filters = FilterUtil.appendFilters(filter);
+ String filters = FilterUtil.appendFraudResultFilters(filter);
String query = FraudResultQuery.SELECT_HISTORY_FRAUD_RESULT + filters;
MapSqlParameterSource params = FilterUtil.initParams(filter);
return namedParameterJdbcTemplate.query(query, params, eventMapper);
diff --git a/src/main/java/dev/vality/fraudbusters/repository/clickhouse/impl/PaymentRepositoryImpl.java b/src/main/java/dev/vality/fraudbusters/repository/clickhouse/impl/PaymentRepositoryImpl.java
index a332728c..b21e83b6 100644
--- a/src/main/java/dev/vality/fraudbusters/repository/clickhouse/impl/PaymentRepositoryImpl.java
+++ b/src/main/java/dev/vality/fraudbusters/repository/clickhouse/impl/PaymentRepositoryImpl.java
@@ -58,7 +58,7 @@ public void insertBatch(List batch) {
@Override
public List getByFilter(FilterDto filter) {
- String filters = FilterUtil.appendFilters(filter);
+ String filters = FilterUtil.appendPaymentFilters(filter);
String query = PaymentQuery.SELECT_HISTORY_PAYMENT + filters;
MapSqlParameterSource params = FilterUtil.initParams(filter);
return namedParameterJdbcTemplate.query(query, params, checkedPaymentMapper);
diff --git a/src/main/java/dev/vality/fraudbusters/repository/clickhouse/query/FraudResultQuery.java b/src/main/java/dev/vality/fraudbusters/repository/clickhouse/query/FraudResultQuery.java
index be33f739..d541404d 100644
--- a/src/main/java/dev/vality/fraudbusters/repository/clickhouse/query/FraudResultQuery.java
+++ b/src/main/java/dev/vality/fraudbusters/repository/clickhouse/query/FraudResultQuery.java
@@ -7,7 +7,20 @@
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class FraudResultQuery {
- public static final String SELECT_HISTORY_FRAUD_RESULT = String.format("""
+ private static final String FROM_WHERE_PARAMS_SOURCE = String.format("""
+ FROM
+ %s
+ WHERE
+ timestamp >= toDate(:from)
+ and timestamp <= toDate(:to)
+ and toDateTime(eventTime) >= toDateTime(:from)
+ and toDateTime(eventTime) <= toDateTime(:to)
+ and shopId != 'TEST'""",
+ EventSource.FRAUD_EVENTS_UNIQUE.getTable());
+
+ public static final String SELECT_HISTORY_FRAUD_RESULT_IDS = "SELECT id\n" + FROM_WHERE_PARAMS_SOURCE;
+
+ public static final String SELECT_HISTORY_FRAUD_RESULT = """
SELECT
eventTime,
partyId,
@@ -30,13 +43,5 @@ public class FraudResultQuery {
checkedTemplate,
mobile,
recurrent
- FROM
- %s
- WHERE
- timestamp >= toDate(:from)
- and timestamp <= toDate(:to)
- and toDateTime(eventTime) >= toDateTime(:from)
- and toDateTime(eventTime) <= toDateTime(:to)
- and shopId != 'TEST'""",
- EventSource.FRAUD_EVENTS_UNIQUE.getTable());
+ """ + FROM_WHERE_PARAMS_SOURCE;
}
diff --git a/src/main/java/dev/vality/fraudbusters/repository/clickhouse/util/FilterUtil.java b/src/main/java/dev/vality/fraudbusters/repository/clickhouse/util/FilterUtil.java
index 66e3d53b..b70fc394 100644
--- a/src/main/java/dev/vality/fraudbusters/repository/clickhouse/util/FilterUtil.java
+++ b/src/main/java/dev/vality/fraudbusters/repository/clickhouse/util/FilterUtil.java
@@ -1,7 +1,10 @@
package dev.vality.fraudbusters.repository.clickhouse.util;
+import dev.vality.fraudbusters.constant.FraudResultField;
+import dev.vality.fraudbusters.constant.PaymentField;
import dev.vality.fraudbusters.constant.QueryParamName;
+import dev.vality.fraudbusters.repository.clickhouse.query.FraudResultQuery;
import dev.vality.fraudbusters.service.dto.FieldType;
import dev.vality.fraudbusters.service.dto.FilterDto;
import dev.vality.fraudbusters.service.dto.SearchFieldDto;
@@ -18,11 +21,20 @@
public class FilterUtil {
public static String appendFilters(FilterDto filter) {
+ return appendFilters(filter, FraudResultFilterMode.NONE);
+ }
+
+ private static String appendFilters(FilterDto filter, FraudResultFilterMode fraudResultFilterMode) {
StringBuilder filters = new StringBuilder();
Set searchFields = filter.getSearchFields();
if (!CollectionUtils.isEmpty(searchFields)) {
addLikeSearchFields(filters, searchFields);
addEqualSearchFields(filters, searchFields);
+ if (fraudResultFilterMode == FraudResultFilterMode.DIRECT) {
+ addDirectFraudResultSearchFields(filters, searchFields);
+ } else if (fraudResultFilterMode == FraudResultFilterMode.PAYMENT_SUBQUERY) {
+ addPaymentFraudResultSearchFields(filters, searchFields);
+ }
}
String sorting = String.format(" ORDER BY (eventTime, id) %s ", filter.getSort().getOrder().name());
String limit = " LIMIT :size ";
@@ -33,9 +45,18 @@ public static String appendFilters(FilterDto filter) {
return filters.append(sorting).append(limit).toString();
}
+ public static String appendPaymentFilters(FilterDto filter) {
+ return appendFilters(filter, FraudResultFilterMode.PAYMENT_SUBQUERY);
+ }
+
+ public static String appendFraudResultFilters(FilterDto filter) {
+ return appendFilters(filter, FraudResultFilterMode.DIRECT);
+ }
+
private static void addLikeSearchFields(StringBuilder filters, Set searchFields) {
searchFields.stream()
.filter(searchField -> searchField.getType().equals(FieldType.STRING))
+ .filter(searchField -> searchField.getField() instanceof PaymentField)
.forEach(searchField ->
filters
.append(" and like(")
@@ -48,6 +69,7 @@ private static void addLikeSearchFields(StringBuilder filters, Set searchFields) {
searchFields.stream()
.filter(searchField -> searchField.getType().equals(FieldType.ENUM))
+ .filter(searchField -> searchField.getField() instanceof PaymentField)
.forEach(searchField ->
filters
.append(" and ")
@@ -57,6 +79,28 @@ private static void addEqualSearchFields(StringBuilder filters, Set searchFields) {
+ boolean containsFraudResultFields = searchFields.stream()
+ .anyMatch(searchField -> searchField.getField() instanceof FraudResultField);
+ if (!containsFraudResultFields) {
+ return;
+ }
+ filters.append(" and id in (")
+ .append(FraudResultQuery.SELECT_HISTORY_FRAUD_RESULT_IDS);
+ addDirectFraudResultSearchFields(filters, searchFields);
+ filters.append(")");
+ }
+
+ private static void addDirectFraudResultSearchFields(StringBuilder filters, Set searchFields) {
+ searchFields.stream()
+ .filter(searchField -> searchField.getField() instanceof FraudResultField)
+ .forEach(searchField -> filters
+ .append(" and ")
+ .append(searchField.getField().getValue())
+ .append(" = :")
+ .append(searchField.getField().getValue()));
+ }
+
public static MapSqlParameterSource initParams(FilterDto filter) {
MapSqlParameterSource params = new MapSqlParameterSource();
if (Objects.nonNull(filter.getLastId())) {
@@ -70,7 +114,20 @@ private static MapSqlParameterSource addTimeParams(MapSqlParameterSource params,
params.addValue(QueryParamName.FROM, filter.getTimeFrom())
.addValue(QueryParamName.TO, filter.getTimeTo())
.addValue(QueryParamName.SIZE, filter.getSize());
+ if (!CollectionUtils.isEmpty(filter.getSearchFields())) {
+ filter.getSearchFields().stream()
+ .filter(searchField -> searchField.getField() instanceof FraudResultField)
+ .forEach(searchField -> params.addValue(
+ searchField.getField().getValue(),
+ searchField.getValue()));
+ }
return params;
}
+ private enum FraudResultFilterMode {
+ NONE,
+ PAYMENT_SUBQUERY,
+ DIRECT
+ }
+
}
diff --git a/src/main/java/dev/vality/fraudbusters/service/dto/SearchFieldDto.java b/src/main/java/dev/vality/fraudbusters/service/dto/SearchFieldDto.java
index 3b22dcb2..c4a55687 100644
--- a/src/main/java/dev/vality/fraudbusters/service/dto/SearchFieldDto.java
+++ b/src/main/java/dev/vality/fraudbusters/service/dto/SearchFieldDto.java
@@ -1,6 +1,6 @@
package dev.vality.fraudbusters.service.dto;
-import dev.vality.fraudbusters.constant.PaymentField;
+import dev.vality.fraudbusters.constant.FilterField;
import lombok.Builder;
import lombok.Data;
@@ -8,7 +8,7 @@
@Builder
public class SearchFieldDto {
- private PaymentField field;
+ private FilterField field;
private FieldType type;
private String value;
}
diff --git a/src/test/java/dev/vality/fraudbusters/converter/CardTokenNormalizationTest.java b/src/test/java/dev/vality/fraudbusters/converter/CardTokenNormalizationTest.java
new file mode 100644
index 00000000..9a3ba4a4
--- /dev/null
+++ b/src/test/java/dev/vality/fraudbusters/converter/CardTokenNormalizationTest.java
@@ -0,0 +1,79 @@
+package dev.vality.fraudbusters.converter;
+
+import dev.vality.damsel.domain.PaymentTool;
+import dev.vality.damsel.fraudbusters.Payment;
+import dev.vality.damsel.fraudbusters.PaymentStatus;
+import dev.vality.damsel.proxy_inspector.Context;
+import dev.vality.fraudbusters.domain.CheckedPayment;
+import dev.vality.fraudbusters.domain.FraudRequest;
+import dev.vality.fraudbusters.fraud.model.PaymentModel;
+import dev.vality.fraudbusters.util.BeanUtil;
+import dev.vality.fraudbusters.util.PaymentTypeByContextResolver;
+import org.junit.jupiter.api.Test;
+
+import static dev.vality.fraudbusters.constant.ClickhouseUtilsValue.UNKNOWN;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class CardTokenNormalizationTest {
+
+ private final ContextToFraudRequestConverter contextConverter =
+ new ContextToFraudRequestConverter(new PaymentTypeByContextResolver());
+ private final PaymentToPaymentModelConverter historicalPaymentConverter =
+ new PaymentToPaymentModelConverter();
+ private final PaymentToCheckedPaymentConverter checkedPaymentConverter =
+ new PaymentToCheckedPaymentConverter(new PaymentTypeByContextResolver());
+
+ @Test
+ void normalizesAbsentOnlineBankCardToUnknownToken() {
+ Context context = BeanUtil.createContext();
+ context.getPayment().getPayment().getPayer().getPaymentResource()
+ .getResource().setPaymentTool(new PaymentTool());
+
+ FraudRequest fraudRequest = contextConverter.convert(context);
+
+ assertEquals(UNKNOWN, fraudRequest.getFraudModel().getCardToken());
+ }
+
+ @Test
+ void normalizesMissingOnlineCardTokenToUnknown() {
+ Context context = BeanUtil.createContext();
+ context.getPayment().getPayment().getPayer().getPaymentResource()
+ .getResource().getPaymentTool().getBankCard().unsetToken();
+
+ FraudRequest fraudRequest = contextConverter.convert(context);
+
+ assertEquals(UNKNOWN, fraudRequest.getFraudModel().getCardToken());
+ }
+
+ @Test
+ void normalizesMissingHistoricalCardTokenToUnknown() {
+ Payment payment = BeanUtil.createPayment(PaymentStatus.captured);
+ payment.getPaymentTool().getBankCard().unsetToken();
+
+ PaymentModel paymentModel = historicalPaymentConverter.convert(payment);
+
+ assertEquals(UNKNOWN, paymentModel.getCardToken());
+ }
+
+ @Test
+ void normalizesMissingStoredPaymentCardTokenToUnknown() {
+ Payment payment = BeanUtil.createPayment(PaymentStatus.captured);
+ payment.getPaymentTool().getBankCard().unsetToken();
+
+ CheckedPayment checkedPayment = checkedPaymentConverter.convert(payment);
+
+ assertEquals(UNKNOWN, checkedPayment.getCardToken());
+ }
+
+ @Test
+ void preservesPresentCardToken() {
+ String cardToken = "card-token";
+ Context context = BeanUtil.createContext();
+ context.getPayment().getPayment().getPayer().getPaymentResource()
+ .getResource().getPaymentTool().getBankCard().setToken(cardToken);
+
+ FraudRequest fraudRequest = contextConverter.convert(context);
+
+ assertEquals(cardToken, fraudRequest.getFraudModel().getCardToken());
+ }
+}
diff --git a/src/test/java/dev/vality/fraudbusters/converter/FilterConverterTest.java b/src/test/java/dev/vality/fraudbusters/converter/FilterConverterTest.java
index fad39c98..5e726cab 100644
--- a/src/test/java/dev/vality/fraudbusters/converter/FilterConverterTest.java
+++ b/src/test/java/dev/vality/fraudbusters/converter/FilterConverterTest.java
@@ -4,8 +4,10 @@
import dev.vality.damsel.fraudbusters.Page;
import dev.vality.damsel.fraudbusters.Sort;
import dev.vality.damsel.fraudbusters.SortOrder;
+import dev.vality.fraudbusters.constant.FraudResultField;
import dev.vality.fraudbusters.constant.PaymentField;
import dev.vality.fraudbusters.factory.TestObjectsFactory;
+import dev.vality.fraudbusters.service.dto.FieldType;
import dev.vality.fraudbusters.service.dto.FilterDto;
import dev.vality.fraudbusters.service.dto.SearchFieldDto;
import org.junit.jupiter.api.Test;
@@ -93,7 +95,15 @@ void convert() {
.filter(searchFieldDto -> searchFieldDto.getField().equals(PaymentField.LAST_DIGITS))
.map(SearchFieldDto::getValue)
.anyMatch(value -> filter.getMaskedPan().equals(value)));
+ assertTrue(searchFields.stream()
+ .anyMatch(searchFieldDto -> searchFieldDto.getField().equals(FraudResultField.CHECKED_TEMPLATE)
+ && searchFieldDto.getType().equals(FieldType.STRING)
+ && filter.getTemplate().equals(searchFieldDto.getValue())));
+ assertTrue(searchFields.stream()
+ .anyMatch(searchFieldDto -> searchFieldDto.getField().equals(FraudResultField.CHECKED_RULE)
+ && searchFieldDto.getType().equals(FieldType.STRING)
+ && filter.getRule().equals(searchFieldDto.getValue())));
assertEquals(sort.getField(), dto.getSort().getField());
assertEquals(sort.getOrder(), SortOrder.valueOf(dto.getSort().getOrder().name()));
}
-}
\ No newline at end of file
+}
diff --git a/src/test/java/dev/vality/fraudbusters/dgraph/DgraphAbstractIntegrationTest.java b/src/test/java/dev/vality/fraudbusters/dgraph/DgraphAbstractIntegrationTest.java
index ab1aa697..9e2ed026 100644
--- a/src/test/java/dev/vality/fraudbusters/dgraph/DgraphAbstractIntegrationTest.java
+++ b/src/test/java/dev/vality/fraudbusters/dgraph/DgraphAbstractIntegrationTest.java
@@ -28,7 +28,6 @@
import org.apache.thrift.TBase;
import org.awaitility.Awaitility;
import org.awaitility.Durations;
-import org.junit.Ignore;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.extension.ExtendWith;
import org.rnorth.ducttape.unreliables.Unreliables;
@@ -75,7 +74,7 @@
"dgraph.negotiationType=PLAINTEXT"
})
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
-@Ignore
+@DgraphDisabled
public abstract class DgraphAbstractIntegrationTest {
@Autowired
diff --git a/src/test/java/dev/vality/fraudbusters/dgraph/DgraphDisabled.java b/src/test/java/dev/vality/fraudbusters/dgraph/DgraphDisabled.java
new file mode 100644
index 00000000..4a5a6700
--- /dev/null
+++ b/src/test/java/dev/vality/fraudbusters/dgraph/DgraphDisabled.java
@@ -0,0 +1,16 @@
+package dev.vality.fraudbusters.dgraph;
+
+import org.junit.jupiter.api.Disabled;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Inherited
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Disabled("Dgraph is no longer used")
+public @interface DgraphDisabled {
+}
diff --git a/src/test/java/dev/vality/fraudbusters/dgraph/service/aggregator/DgraphCountPaymentsAggregatorTest.java b/src/test/java/dev/vality/fraudbusters/dgraph/service/aggregator/DgraphCountPaymentsAggregatorTest.java
index 5e7b4ba7..d2aa942f 100644
--- a/src/test/java/dev/vality/fraudbusters/dgraph/service/aggregator/DgraphCountPaymentsAggregatorTest.java
+++ b/src/test/java/dev/vality/fraudbusters/dgraph/service/aggregator/DgraphCountPaymentsAggregatorTest.java
@@ -5,7 +5,6 @@
import dev.vality.fraudbusters.fraud.model.PaymentModel;
import dev.vality.fraudo.payment.aggregator.CountPaymentAggregator;
import lombok.extern.slf4j.Slf4j;
-import org.junit.Ignore;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
@@ -15,7 +14,6 @@
import static dev.vality.fraudbusters.util.DgraphTestAggregationUtils.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
-@Ignore
@Slf4j
@ActiveProfiles("full-prod")
public class DgraphCountPaymentsAggregatorTest extends AbstractDgraphPaymentAggregatorTest {
diff --git a/src/test/java/dev/vality/fraudbusters/dgraph/service/aggregator/DgraphSumPaymentsAggregatorTest.java b/src/test/java/dev/vality/fraudbusters/dgraph/service/aggregator/DgraphSumPaymentsAggregatorTest.java
index e6a75737..a7887eba 100644
--- a/src/test/java/dev/vality/fraudbusters/dgraph/service/aggregator/DgraphSumPaymentsAggregatorTest.java
+++ b/src/test/java/dev/vality/fraudbusters/dgraph/service/aggregator/DgraphSumPaymentsAggregatorTest.java
@@ -5,7 +5,6 @@
import dev.vality.fraudbusters.fraud.model.PaymentModel;
import dev.vality.fraudo.payment.aggregator.SumPaymentAggregator;
import lombok.extern.slf4j.Slf4j;
-import org.junit.Ignore;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
@@ -15,7 +14,6 @@
import static dev.vality.fraudbusters.util.DgraphTestAggregationUtils.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
-@Ignore
@Slf4j
@ActiveProfiles("full-prod")
public class DgraphSumPaymentsAggregatorTest extends AbstractDgraphPaymentAggregatorTest {
diff --git a/src/test/java/dev/vality/fraudbusters/dgraph/service/aggregator/DgraphUniqueAggregatorTest.java b/src/test/java/dev/vality/fraudbusters/dgraph/service/aggregator/DgraphUniqueAggregatorTest.java
index 70af24aa..0f4c29bf 100644
--- a/src/test/java/dev/vality/fraudbusters/dgraph/service/aggregator/DgraphUniqueAggregatorTest.java
+++ b/src/test/java/dev/vality/fraudbusters/dgraph/service/aggregator/DgraphUniqueAggregatorTest.java
@@ -5,7 +5,6 @@
import dev.vality.fraudbusters.fraud.model.PaymentModel;
import dev.vality.fraudo.aggregator.UniqueValueAggregator;
import lombok.extern.slf4j.Slf4j;
-import org.junit.Ignore;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
@@ -15,7 +14,6 @@
import static dev.vality.fraudbusters.util.DgraphTestAggregationUtils.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
-@Ignore
@Slf4j
@ActiveProfiles("full-prod")
public class DgraphUniqueAggregatorTest extends AbstractDgraphPaymentAggregatorTest {
diff --git a/src/test/java/dev/vality/fraudbusters/dgraph/service/query/AbstractDgraphQueryBuilderServiceTest.java b/src/test/java/dev/vality/fraudbusters/dgraph/service/query/AbstractDgraphQueryBuilderServiceTest.java
index 05a3f3b6..e51e4b19 100644
--- a/src/test/java/dev/vality/fraudbusters/dgraph/service/query/AbstractDgraphQueryBuilderServiceTest.java
+++ b/src/test/java/dev/vality/fraudbusters/dgraph/service/query/AbstractDgraphQueryBuilderServiceTest.java
@@ -1,6 +1,7 @@
package dev.vality.fraudbusters.dgraph.service.query;
import dev.vality.fraudbusters.config.dgraph.TemplateConfig;
+import dev.vality.fraudbusters.dgraph.DgraphDisabled;
import dev.vality.fraudbusters.fraud.payment.aggregator.dgraph.query.builder.DgraphCountQueryBuilderService;
import dev.vality.fraudbusters.fraud.payment.aggregator.dgraph.query.builder.DgraphSumQueryBuilderService;
import dev.vality.fraudbusters.fraud.payment.aggregator.dgraph.query.builder.DgraphUniqueQueryBuilderService;
@@ -15,6 +16,7 @@
import dev.vality.fraudbusters.service.template.aggregate.unique.UniqueQueryTemplateService;
import org.apache.velocity.app.VelocityEngine;
+@DgraphDisabled
public abstract class AbstractDgraphQueryBuilderServiceTest {
private VelocityEngine velocityEngine = new TemplateConfig().velocityEngine();
diff --git a/src/test/java/dev/vality/fraudbusters/dgraph/service/query/DgraphEntityResolverTest.java b/src/test/java/dev/vality/fraudbusters/dgraph/service/query/DgraphEntityResolverTest.java
index 91d937ac..4951c90e 100644
--- a/src/test/java/dev/vality/fraudbusters/dgraph/service/query/DgraphEntityResolverTest.java
+++ b/src/test/java/dev/vality/fraudbusters/dgraph/service/query/DgraphEntityResolverTest.java
@@ -1,9 +1,9 @@
package dev.vality.fraudbusters.dgraph.service.query;
+import dev.vality.fraudbusters.dgraph.DgraphDisabled;
import dev.vality.fraudbusters.fraud.constant.DgraphEntity;
import dev.vality.fraudbusters.fraud.constant.PaymentCheckedField;
import dev.vality.fraudbusters.fraud.payment.resolver.DgraphEntityResolver;
-import org.junit.Ignore;
import org.junit.jupiter.api.Test;
import java.util.List;
@@ -11,7 +11,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
-@Ignore
+@DgraphDisabled
public class DgraphEntityResolverTest {
private DgraphEntityResolver dgraphEntityResolver = new DgraphEntityResolver();
diff --git a/src/test/java/dev/vality/fraudbusters/dgraph/service/query/DgraphPaymentCountQueryBuilderServiceTest.java b/src/test/java/dev/vality/fraudbusters/dgraph/service/query/DgraphPaymentCountQueryBuilderServiceTest.java
index a5aabcb6..a4768ee9 100644
--- a/src/test/java/dev/vality/fraudbusters/dgraph/service/query/DgraphPaymentCountQueryBuilderServiceTest.java
+++ b/src/test/java/dev/vality/fraudbusters/dgraph/service/query/DgraphPaymentCountQueryBuilderServiceTest.java
@@ -4,7 +4,6 @@
import dev.vality.fraudbusters.dgraph.service.data.DgraphPaymentCountQueryBuilderServiceTestData;
import dev.vality.fraudbusters.fraud.constant.DgraphEntity;
import dev.vality.fraudbusters.fraud.constant.PaymentCheckedField;
-import org.junit.Ignore;
import org.junit.jupiter.api.Test;
import java.time.Instant;
@@ -15,7 +14,6 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
-@Ignore
public class DgraphPaymentCountQueryBuilderServiceTest extends AbstractDgraphQueryBuilderServiceTest {
@Test
diff --git a/src/test/java/dev/vality/fraudbusters/dgraph/service/query/DgraphPaymentSumQueryBuilderServiceTest.java b/src/test/java/dev/vality/fraudbusters/dgraph/service/query/DgraphPaymentSumQueryBuilderServiceTest.java
index dbd2b247..6369bf95 100644
--- a/src/test/java/dev/vality/fraudbusters/dgraph/service/query/DgraphPaymentSumQueryBuilderServiceTest.java
+++ b/src/test/java/dev/vality/fraudbusters/dgraph/service/query/DgraphPaymentSumQueryBuilderServiceTest.java
@@ -3,7 +3,6 @@
import dev.vality.fraudbusters.constant.PaymentStatus;
import dev.vality.fraudbusters.fraud.constant.DgraphEntity;
import dev.vality.fraudbusters.fraud.constant.PaymentCheckedField;
-import org.junit.Ignore;
import org.junit.jupiter.api.Test;
import java.time.Instant;
@@ -15,7 +14,6 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
-@Ignore
public class DgraphPaymentSumQueryBuilderServiceTest extends AbstractDgraphQueryBuilderServiceTest {
@Test
diff --git a/src/test/java/dev/vality/fraudbusters/dgraph/service/query/DgraphRefundCountQueryBuilderServiceTest.java b/src/test/java/dev/vality/fraudbusters/dgraph/service/query/DgraphRefundCountQueryBuilderServiceTest.java
index 9c63848e..842b6bf2 100644
--- a/src/test/java/dev/vality/fraudbusters/dgraph/service/query/DgraphRefundCountQueryBuilderServiceTest.java
+++ b/src/test/java/dev/vality/fraudbusters/dgraph/service/query/DgraphRefundCountQueryBuilderServiceTest.java
@@ -3,7 +3,6 @@
import dev.vality.fraudbusters.constant.RefundStatus;
import dev.vality.fraudbusters.fraud.constant.DgraphEntity;
import dev.vality.fraudbusters.fraud.constant.PaymentCheckedField;
-import org.junit.Ignore;
import org.junit.jupiter.api.Test;
import java.time.Instant;
@@ -15,7 +14,6 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
-@Ignore
public class DgraphRefundCountQueryBuilderServiceTest extends AbstractDgraphQueryBuilderServiceTest {
@Test
diff --git a/src/test/java/dev/vality/fraudbusters/dgraph/service/query/DgraphRefundSumQueryBuilderServiceTest.java b/src/test/java/dev/vality/fraudbusters/dgraph/service/query/DgraphRefundSumQueryBuilderServiceTest.java
index 78661818..854dec61 100644
--- a/src/test/java/dev/vality/fraudbusters/dgraph/service/query/DgraphRefundSumQueryBuilderServiceTest.java
+++ b/src/test/java/dev/vality/fraudbusters/dgraph/service/query/DgraphRefundSumQueryBuilderServiceTest.java
@@ -3,7 +3,6 @@
import dev.vality.fraudbusters.constant.RefundStatus;
import dev.vality.fraudbusters.fraud.constant.DgraphEntity;
import dev.vality.fraudbusters.fraud.constant.PaymentCheckedField;
-import org.junit.Ignore;
import org.junit.jupiter.api.Test;
import java.time.Instant;
@@ -15,7 +14,6 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
-@Ignore
public class DgraphRefundSumQueryBuilderServiceTest extends AbstractDgraphQueryBuilderServiceTest {
@Test
diff --git a/src/test/java/dev/vality/fraudbusters/dgraph/service/query/DgraphUniqueQueryBuilderServiceTest.java b/src/test/java/dev/vality/fraudbusters/dgraph/service/query/DgraphUniqueQueryBuilderServiceTest.java
index a14d5cf3..2c2032ac 100644
--- a/src/test/java/dev/vality/fraudbusters/dgraph/service/query/DgraphUniqueQueryBuilderServiceTest.java
+++ b/src/test/java/dev/vality/fraudbusters/dgraph/service/query/DgraphUniqueQueryBuilderServiceTest.java
@@ -3,7 +3,6 @@
import dev.vality.fraudbusters.constant.PaymentStatus;
import dev.vality.fraudbusters.fraud.constant.DgraphEntity;
import dev.vality.fraudbusters.fraud.constant.PaymentCheckedField;
-import org.junit.Ignore;
import org.junit.jupiter.api.Test;
import java.time.Instant;
@@ -16,7 +15,6 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
-@Ignore
public class DgraphUniqueQueryBuilderServiceTest extends AbstractDgraphQueryBuilderServiceTest {
@Test
diff --git a/src/test/java/dev/vality/fraudbusters/factory/TestObjectsFactory.java b/src/test/java/dev/vality/fraudbusters/factory/TestObjectsFactory.java
index b4b24ed4..3f5f80df 100644
--- a/src/test/java/dev/vality/fraudbusters/factory/TestObjectsFactory.java
+++ b/src/test/java/dev/vality/fraudbusters/factory/TestObjectsFactory.java
@@ -96,6 +96,8 @@ public static Filter testFilter() {
filter.setPaymentId(id);
filter.setMaskedPan(lastDigits);
filter.setInvoiceId(invoiceId);
+ filter.setTemplate(randomString());
+ filter.setRule(randomString());
TimestampInterval timestampInterval = new TimestampInterval();
TimestampIntervalBound lowerBound = new TimestampIntervalBound();
String lowerBoundTime = LocalDateTime.now().toString();
diff --git a/src/test/java/dev/vality/fraudbusters/fraud/payment/ManyEmailsPerCardRuleTest.java b/src/test/java/dev/vality/fraudbusters/fraud/payment/ManyEmailsPerCardRuleTest.java
new file mode 100644
index 00000000..bdcd51ea
--- /dev/null
+++ b/src/test/java/dev/vality/fraudbusters/fraud/payment/ManyEmailsPerCardRuleTest.java
@@ -0,0 +1,201 @@
+package dev.vality.fraudbusters.fraud.payment;
+
+import dev.vality.fraudbusters.config.payment.PaymentFraudoConfig;
+import dev.vality.fraudbusters.fraud.constant.PaymentCheckedField;
+import dev.vality.fraudbusters.fraud.model.PaymentModel;
+import dev.vality.fraudbusters.fraud.payment.aggregator.clickhouse.UniqueValueAggregatorImpl;
+import dev.vality.fraudbusters.fraud.payment.resolver.DatabasePaymentFieldResolver;
+import dev.vality.fraudbusters.fraud.payment.resolver.PaymentModelFieldResolver;
+import dev.vality.fraudbusters.fraud.payment.resolver.PaymentTypeResolverImpl;
+import dev.vality.fraudbusters.fraud.payment.validator.PaymentTemplateValidator;
+import dev.vality.fraudbusters.repository.PaymentRepository;
+import dev.vality.fraudbusters.service.TimeBoundaryServiceImpl;
+import dev.vality.fraudo.aggregator.UniqueValueAggregator;
+import dev.vality.fraudo.constant.ResultStatus;
+import dev.vality.fraudo.finder.InListFinder;
+import dev.vality.fraudo.model.ResultModel;
+import dev.vality.fraudo.payment.aggregator.CountPaymentAggregator;
+import dev.vality.fraudo.payment.aggregator.SumPaymentAggregator;
+import dev.vality.fraudo.payment.resolver.CustomerTypeResolver;
+import dev.vality.fraudo.payment.visitor.impl.FirstFindVisitorImpl;
+import dev.vality.fraudo.resolver.CountryResolver;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Proxy;
+import java.time.Instant;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static dev.vality.fraudbusters.constant.ClickhouseUtilsValue.UNKNOWN;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class ManyEmailsPerCardRuleTest {
+
+ private static final String CARD_TOKEN = "card-token";
+ private static final String EMAIL = "test_af_as@test.com";
+ private static final long TIMESTAMP = Instant.parse("2026-08-11T07:43:52Z").toEpochMilli();
+ private static final String UNPARENTHESIZED_RULE = """
+ rule: many_emails_per_card:
+ not in("card_token", "UNKNOWN")
+ and not in("email", "UNKNOWN")
+ and unique("card_token", "email", 24, hours) > 5
+ -> decline;
+ """;
+ private static final String RULE = """
+ rule: many_emails_per_card:
+ (not in("card_token", "UNKNOWN"))
+ and (not in("email", "UNKNOWN"))
+ and unique("card_token", "email", 24, hours) > 5
+ -> decline;
+ """;
+
+ @Test
+ void unparenthesizedNotNegatesTheRemainingExpressionAndSkipsUnique() {
+ RepositoryStub repository = new RepositoryStub(1);
+
+ ResultModel result = applyRule(UNPARENTHESIZED_RULE, createPayment(CARD_TOKEN), repository);
+
+ assertEquals(1, result.getRuleResults().size());
+ assertEquals(ResultStatus.DECLINE, result.getRuleResults().get(0).getResultStatus());
+ assertEquals("many_emails_per_card", result.getRuleResults().get(0).getRuleChecked());
+ assertNull(repository.getAggregationFields());
+ }
+
+ @Test
+ void doesNotDeclineWhenFunctionResultIsTwo() {
+ RepositoryStub repository = new RepositoryStub(1);
+
+ ResultModel result = applyRule(RULE, createPayment(CARD_TOKEN), repository);
+
+ assertTrue(result.getRuleResults().isEmpty(), result.toString());
+ assertArrayEquals(
+ new Object[]{"cardToken", CARD_TOKEN, "email"},
+ repository.getAggregationFields()
+ );
+ }
+
+ @Test
+ void declinesWhenFunctionResultIsSix() {
+ RepositoryStub repository = new RepositoryStub(5);
+
+ ResultModel result = applyRule(RULE, createPayment(CARD_TOKEN), repository);
+
+ assertEquals(1, result.getRuleResults().size());
+ assertEquals(ResultStatus.DECLINE, result.getRuleResults().get(0).getResultStatus());
+ assertEquals("many_emails_per_card", result.getRuleResults().get(0).getRuleChecked());
+ assertArrayEquals(
+ new Object[]{"cardToken", CARD_TOKEN, "email"},
+ repository.getAggregationFields()
+ );
+ }
+
+ @Test
+ void doesNotQueryHistoryForUnknownCardToken() {
+ RepositoryStub repository = new RepositoryStub(100);
+
+ ResultModel result = applyRule(RULE, createPayment(UNKNOWN), repository);
+
+ assertTrue(result.getRuleResults().isEmpty());
+ assertNull(repository.getAggregationFields());
+ }
+
+ @Test
+ void doesNotQueryHistoryForNullCardToken() {
+ RepositoryStub repository = new RepositoryStub(100);
+
+ ResultModel result = applyRule(RULE, createPayment(null), repository);
+
+ assertTrue(result.getRuleResults().isEmpty());
+ assertNull(repository.getAggregationFields());
+ }
+
+ private ResultModel applyRule(String rule, PaymentModel paymentModel, RepositoryStub repository) {
+ assertTrue(new PaymentTemplateValidator().validate(rule).isEmpty());
+ FirstFindVisitorImpl visitor = createVisitor(repository.asRepository());
+ return visitor.visit(new PaymentContextParserImpl().parse(rule), paymentModel);
+ }
+
+ private FirstFindVisitorImpl createVisitor(PaymentRepository repository) {
+ DatabasePaymentFieldResolver databaseResolver = new DatabasePaymentFieldResolver();
+ UniqueValueAggregator uniqueAggregator =
+ new UniqueValueAggregatorImpl(databaseResolver, repository, new TimeBoundaryServiceImpl());
+ PaymentFraudoConfig config = new PaymentFraudoConfig();
+ return config.paymentRuleVisitor(
+ unusedProxy(CountPaymentAggregator.class),
+ unusedProxy(SumPaymentAggregator.class),
+ uniqueAggregator,
+ unusedProxy(CountryResolver.class),
+ unusedProxy(InListFinder.class),
+ new PaymentModelFieldResolver(),
+ new PaymentTypeResolverImpl(),
+ unusedProxy(CustomerTypeResolver.class)
+ );
+ }
+
+ private PaymentModel createPayment(String cardToken) {
+ PaymentModel paymentModel = new PaymentModel();
+ paymentModel.setCardToken(cardToken);
+ paymentModel.setEmail(EMAIL);
+ paymentModel.setTimestamp(TIMESTAMP);
+ return paymentModel;
+ }
+
+ @SuppressWarnings("unchecked")
+ private T unusedProxy(Class type) {
+ return (T) Proxy.newProxyInstance(
+ type.getClassLoader(),
+ new Class>[]{type},
+ (proxy, method, args) -> defaultValue(method.getReturnType())
+ );
+ }
+
+ private Object defaultValue(Class> type) {
+ if (type == Boolean.class || type == boolean.class) {
+ return false;
+ }
+ if (type == Integer.class || type == int.class) {
+ return 0;
+ }
+ if (type == Long.class || type == long.class) {
+ return 0L;
+ }
+ if (type == Double.class || type == double.class) {
+ return 0D;
+ }
+ return null;
+ }
+
+ private static final class RepositoryStub {
+
+ private final int historicalCount;
+ private final AtomicReference