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 aggregationArguments = new AtomicReference<>(); + + private RepositoryStub(int historicalCount) { + this.historicalCount = historicalCount; + } + + private PaymentRepository asRepository() { + return (PaymentRepository) Proxy.newProxyInstance( + PaymentRepository.class.getClassLoader(), + new Class[]{PaymentRepository.class}, + (proxy, method, args) -> { + if (method.getName().equals("uniqCountOperationWithGroupBy")) { + aggregationArguments.set(args); + return historicalCount; + } + return null; + } + ); + } + + private Object[] getAggregationFields() { + Object[] arguments = aggregationArguments.get(); + if (arguments == null) { + return null; + } + return new Object[]{arguments[0], arguments[1], arguments[2]}; + } + } +} diff --git a/src/test/java/dev/vality/fraudbusters/repository/clickhouse/impl/HistoricalFraudResultDataTest.java b/src/test/java/dev/vality/fraudbusters/repository/clickhouse/impl/HistoricalFraudResultDataTest.java index d6ce7627..caaf41dd 100644 --- a/src/test/java/dev/vality/fraudbusters/repository/clickhouse/impl/HistoricalFraudResultDataTest.java +++ b/src/test/java/dev/vality/fraudbusters/repository/clickhouse/impl/HistoricalFraudResultDataTest.java @@ -3,6 +3,7 @@ import dev.vality.clickhouse.initializer.ChInitializer; import dev.vality.fraudbusters.config.TestClickhouseConfig; import dev.vality.fraudbusters.config.properties.ClickhouseProperties; +import dev.vality.fraudbusters.constant.FraudResultField; import dev.vality.fraudbusters.constant.PaymentField; import dev.vality.fraudbusters.constant.SortOrder; import dev.vality.fraudbusters.domain.Event; @@ -102,6 +103,38 @@ void getFraudResultsByTimeSlotAndSearchPatterns() { assertEquals("partyId_2", fraudResults.get(0).getPartyId()); } + @Test + void getFraudResultsByEmailTemplateAndRule() { + FilterDto filter = new FilterDto(); + filter.setTimeFrom("2020-05-01T18:04:53"); + filter.setTimeTo("2020-10-01T18:04:53"); + filter.setSearchFields(Set.of( + SearchFieldDto.builder() + .field(PaymentField.EMAIL) + .type(FieldType.STRING) + .value("email_2") + .build(), + SearchFieldDto.builder() + .field(FraudResultField.CHECKED_TEMPLATE) + .type(FieldType.STRING) + .value("3DS_TEMPLATE") + .build(), + SearchFieldDto.builder() + .field(FraudResultField.CHECKED_RULE) + .type(FieldType.STRING) + .value("3DS_RULE") + .build() + )); + SortDto sortDto = new SortDto(); + sortDto.setOrder(SortOrder.DESC); + filter.setSort(sortDto); + + List fraudResults = fraudResultRepository.getByFilter(filter); + + assertEquals(1, fraudResults.size()); + assertEquals("1VMI3GwdR5s.1", fraudResults.get(0).getId()); + } + @Test void getFraudResultsByTimeSlotAndLimitSize() { FilterDto filter = new FilterDto(); @@ -168,4 +201,4 @@ public void initialize(ConfigurableApplicationContext configurableApplicationCon )); } } -} \ No newline at end of file +} diff --git a/src/test/java/dev/vality/fraudbusters/repository/clickhouse/impl/HistoricalPaymentDataTest.java b/src/test/java/dev/vality/fraudbusters/repository/clickhouse/impl/HistoricalPaymentDataTest.java index e4a6387a..93087ae8 100644 --- a/src/test/java/dev/vality/fraudbusters/repository/clickhouse/impl/HistoricalPaymentDataTest.java +++ b/src/test/java/dev/vality/fraudbusters/repository/clickhouse/impl/HistoricalPaymentDataTest.java @@ -3,6 +3,7 @@ import dev.vality.clickhouse.initializer.ChInitializer; import dev.vality.fraudbusters.config.TestClickhouseConfig; import dev.vality.fraudbusters.config.properties.ClickhouseProperties; +import dev.vality.fraudbusters.constant.FraudResultField; import dev.vality.fraudbusters.constant.PaymentField; import dev.vality.fraudbusters.constant.SortOrder; import dev.vality.fraudbusters.domain.CheckedPayment; @@ -25,6 +26,8 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.testcontainers.junit.jupiter.Testcontainers; +import java.time.LocalDate; +import java.time.ZoneOffset; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -50,7 +53,9 @@ class HistoricalPaymentDataTest { @BeforeAll static void setUp() throws Exception { ChInitializer.initAllScripts(ClickHouseContainerExtension.CLICKHOUSE_CONTAINER, List.of( - "sql/data/insert_history_payments.sql" + "sql/data/insert_history_payments.sql", + "sql/data/insert_history_fraud_results.sql", + "sql/data/insert_recent_payment_rule_filter.sql" )); } @@ -98,6 +103,39 @@ void getPaymentsByTimeSlotAndSearchPatterns() { assertEquals("partyId_2", payments.get(0).getPartyId()); } + @Test + void getPaymentsByEmailTemplateAndRule() { + LocalDate today = LocalDate.now(ZoneOffset.UTC); + FilterDto filter = new FilterDto(); + filter.setTimeFrom(today.minusDays(1) + "T00:00:00"); + filter.setTimeTo(today.plusDays(1) + "T00:00:00"); + filter.setSearchFields(Set.of( + SearchFieldDto.builder() + .field(PaymentField.EMAIL) + .type(FieldType.STRING) + .value("rule_filter_email") + .build(), + SearchFieldDto.builder() + .field(FraudResultField.CHECKED_TEMPLATE) + .type(FieldType.STRING) + .value("3DS_TEMPLATE") + .build(), + SearchFieldDto.builder() + .field(FraudResultField.CHECKED_RULE) + .type(FieldType.STRING) + .value("3DS_RULE") + .build() + )); + SortDto sortDto = new SortDto(); + sortDto.setOrder(SortOrder.DESC); + filter.setSort(sortDto); + + List payments = paymentRepository.getByFilter(filter); + + assertEquals(1, payments.size()); + assertEquals("rule-filter-payment.1", payments.get(0).getId()); + } + @Test void getPaymentsByTimeSlotAndLimitSize() { FilterDto filter = new FilterDto(); diff --git a/src/test/java/dev/vality/fraudbusters/repository/clickhouse/util/FilterUtilTest.java b/src/test/java/dev/vality/fraudbusters/repository/clickhouse/util/FilterUtilTest.java new file mode 100644 index 00000000..ef8f56cd --- /dev/null +++ b/src/test/java/dev/vality/fraudbusters/repository/clickhouse/util/FilterUtilTest.java @@ -0,0 +1,91 @@ +package dev.vality.fraudbusters.repository.clickhouse.util; + +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; +import dev.vality.fraudbusters.service.dto.FilterDto; +import dev.vality.fraudbusters.service.dto.SearchFieldDto; +import dev.vality.fraudbusters.service.dto.SortDto; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class FilterUtilTest { + + @Test + void combinesEmailTemplateAndRuleFilters() { + FilterDto filter = new FilterDto(); + filter.setTimeFrom("2020-05-01T00:00:00"); + filter.setTimeTo("2020-05-31T23:59:59"); + SortDto sort = new SortDto(); + sort.setOrder(SortOrder.DESC); + filter.setSort(sort); + filter.setSearchFields(Set.of( + searchField(PaymentField.EMAIL, FieldType.STRING, "test@example.com"), + searchField(FraudResultField.CHECKED_TEMPLATE, FieldType.STRING, "shop-template"), + searchField(FraudResultField.CHECKED_RULE, FieldType.STRING, "many_emails_per_card") + )); + + String sql = FilterUtil.appendPaymentFilters(filter); + + assertTrue(sql.contains("like(email,'test@example.com')")); + assertTrue(sql.contains("id in (")); + assertTrue(sql.contains("SELECT id")); + assertTrue(sql.contains("fraud.events_unique")); + assertTrue(sql.contains("shopId != 'TEST'")); + assertTrue(sql.contains("checkedTemplate = :checkedTemplate")); + assertTrue(sql.contains("checkedRule = :checkedRule")); + assertEquals("shop-template", FilterUtil.initParams(filter).getValue("checkedTemplate")); + assertEquals("many_emails_per_card", FilterUtil.initParams(filter).getValue("checkedRule")); + } + + @Test + void appliesTemplateAndRuleDirectlyForFraudResults() { + FilterDto filter = new FilterDto(); + SortDto sort = new SortDto(); + sort.setOrder(SortOrder.DESC); + filter.setSort(sort); + filter.setSearchFields(Set.of( + searchField(FraudResultField.CHECKED_TEMPLATE, FieldType.STRING, "shop-template"), + searchField(FraudResultField.CHECKED_RULE, FieldType.STRING, "many_emails_per_card") + )); + + String sql = FilterUtil.appendFraudResultFilters(filter); + + assertTrue(sql.contains("checkedTemplate = :checkedTemplate")); + assertTrue(sql.contains("checkedRule = :checkedRule")); + assertFalse(sql.contains("id in (select id")); + } + + @Test + void doesNotApplyFraudResultFieldsToOtherHistoryQueries() { + FilterDto filter = new FilterDto(); + SortDto sort = new SortDto(); + sort.setOrder(SortOrder.DESC); + filter.setSort(sort); + filter.setSearchFields(Set.of( + searchField(FraudResultField.CHECKED_TEMPLATE, FieldType.STRING, "shop-template"), + searchField(FraudResultField.CHECKED_RULE, FieldType.STRING, "many_emails_per_card") + )); + + String sql = FilterUtil.appendFilters(filter); + + assertFalse(sql.contains("checkedTemplate")); + assertFalse(sql.contains("checkedRule")); + assertFalse(sql.contains("id in (select id")); + } + + private SearchFieldDto searchField(FilterField field, FieldType type, String value) { + return SearchFieldDto.builder() + .field(field) + .type(type) + .value(value) + .build(); + } +} diff --git a/src/test/resources/sql/data/insert_recent_payment_rule_filter.sql b/src/test/resources/sql/data/insert_recent_payment_rule_filter.sql new file mode 100644 index 00000000..737828bb --- /dev/null +++ b/src/test/resources/sql/data/insert_recent_payment_rule_filter.sql @@ -0,0 +1,16 @@ +INSERT INTO fraud.payment +(timestamp, eventTime, eventTimeHour, partyId, shopId, email, fingerprint, amount, currency, status, errorReason, id, + ip, bin, lastDigits, paymentTool, cardToken, paymentSystem, terminal, providerId, bankCountry, errorCode, + tokenProvider) +VALUES (today(), toUInt64(toUnixTimestamp(now())), toUInt64(toUnixTimestamp(toStartOfHour(now()))) * 1000, + 'rule_filter_party', 'rule_filter_shop', 'rule_filter_email', 'rule_filter_fingerprint', + 50000, 'RUB', 'captured', '', 'rule-filter-payment.1', '127.0.0.1', '666', '3125', 'bank_card', + 'rule_filter_card_token', 'VISA', '123', '1', 'RUS', '', 'tprovider'); + +INSERT INTO fraud.events_unique +(timestamp, eventTime, eventTimeHour, partyId, shopId, email, fingerprint, amount, currency, mobile, id, paymentId, + ip, bin, lastDigits, cardToken, resultStatus, checkedRule, checkedTemplate, bankCountry, invoiceId, bankName) +VALUES (today(), toUInt64(toUnixTimestamp(now())), toUInt64(toUnixTimestamp(toStartOfHour(now()))) * 1000, + 'rule_filter_party', 'rule_filter_shop', 'rule_filter_email', 'rule_filter_fingerprint', + 50000, 'RUB', 0, 'rule-filter-payment.1', '1', '127.0.0.1', '666', '3125', + 'rule_filter_card_token', 'DECLINE', '3DS_RULE', '3DS_TEMPLATE', 'RUS', 'rule-filter-invoice', 'SBER');