diff --git a/src/main/java/com/exactpro/th2/common/schema/filter/strategy/impl/AbstractFilterStrategy.java b/src/main/java/com/exactpro/th2/common/schema/filter/strategy/impl/AbstractFilterStrategy.java index fee14236e..e82fa58e1 100644 --- a/src/main/java/com/exactpro/th2/common/schema/filter/strategy/impl/AbstractFilterStrategy.java +++ b/src/main/java/com/exactpro/th2/common/schema/filter/strategy/impl/AbstractFilterStrategy.java @@ -21,6 +21,7 @@ import com.google.protobuf.Message; import org.apache.commons.collections4.MultiMapUtils; import org.apache.commons.collections4.MultiValuedMap; +import org.jetbrains.annotations.NotNull; import java.util.Collection; import java.util.List; @@ -30,18 +31,15 @@ public abstract class AbstractFilterStrategy implements Filte @Override public boolean verify(T message, RouterFilter routerFilter) { - - MultiValuedMap msgFieldFilters = MultiMapUtils.newListValuedHashMap(); - msgFieldFilters.putAll(routerFilter.getMessage()); - msgFieldFilters.putAll(routerFilter.getMetadata()); - + MultiValuedMap msgFieldFilters = extractConfigMapFromRouterFilter(routerFilter); return checkValues(getFields(message), msgFieldFilters); } @Override public boolean verify(T message, List routerFilters) { + Map fields = getFields(message); for (var fieldsFilter : routerFilters) { - if (verify(message, fieldsFilter)) { + if (verify(fields, fieldsFilter)) { return true; } } @@ -49,10 +47,24 @@ public boolean verify(T message, List routerFilters) { return false; } + private boolean verify(Map fields, RouterFilter routerFilter) { + MultiValuedMap msgFieldFilters = extractConfigMapFromRouterFilter(routerFilter); + return checkValues(fields, msgFieldFilters); + } + + @NotNull + private MultiValuedMap extractConfigMapFromRouterFilter(RouterFilter routerFilter) { + MultiValuedMap msgFieldFilters = MultiMapUtils.newListValuedHashMap(); + msgFieldFilters.putAll(routerFilter.getMessage()); + msgFieldFilters.putAll(routerFilter.getMetadata()); + msgFieldFilters.putAll(routerFilter.getProperties()); + return msgFieldFilters; + } + protected abstract Map getFields(T message); private boolean checkValues(Map messageFields, MultiValuedMap fieldFilters) { - return fieldFilters.isEmpty() || fieldFilters.keys().stream().anyMatch(fieldName -> { + return fieldFilters.isEmpty() || fieldFilters.keys().stream().allMatch(fieldName -> { String messageValue = messageFields.get(fieldName); Collection filters = fieldFilters.get(fieldName); return !filters.isEmpty() && filters.stream().allMatch(filter -> FieldValueChecker.checkFieldValue(filter, messageValue)); diff --git a/src/main/java/com/exactpro/th2/common/schema/filter/strategy/impl/AbstractTh2MsgFilterStrategy.java b/src/main/java/com/exactpro/th2/common/schema/filter/strategy/impl/AbstractTh2MsgFilterStrategy.java index 2d0fe5964..c954cd073 100644 --- a/src/main/java/com/exactpro/th2/common/schema/filter/strategy/impl/AbstractTh2MsgFilterStrategy.java +++ b/src/main/java/com/exactpro/th2/common/schema/filter/strategy/impl/AbstractTh2MsgFilterStrategy.java @@ -29,6 +29,7 @@ public abstract class AbstractTh2MsgFilterStrategy extends AbstractFilterStrateg public static final String SESSION_ALIAS_KEY = "session_alias"; public static final String MESSAGE_TYPE_KEY = "message_type"; public static final String DIRECTION_KEY = "direction"; + public static final String PROTOCOL_KEY = "protocol"; @Override public Map getFields(com.google.protobuf.Message message) { @@ -44,9 +45,11 @@ public Map getFields(com.google.protobuf.Message message) { var metadataMsgFields = Map.of( SESSION_ALIAS_KEY, messageID.getConnectionId().getSessionAlias(), MESSAGE_TYPE_KEY, metadata.getMessageType(), - DIRECTION_KEY, messageID.getDirection().name() + DIRECTION_KEY, messageID.getDirection().name(), + PROTOCOL_KEY, metadata.getProtocol() ); + messageFields.putAll(th2Msg.getMetadata().getPropertiesMap()); messageFields.putAll(metadataMsgFields); return messageFields; diff --git a/src/main/kotlin/com/exactpro/th2/common/schema/filter/strategy/impl/AnyMessageFilterStrategy.kt b/src/main/kotlin/com/exactpro/th2/common/schema/filter/strategy/impl/AnyMessageFilterStrategy.kt index d39f229d1..70e958b95 100644 --- a/src/main/kotlin/com/exactpro/th2/common/schema/filter/strategy/impl/AnyMessageFilterStrategy.kt +++ b/src/main/kotlin/com/exactpro/th2/common/schema/filter/strategy/impl/AnyMessageFilterStrategy.kt @@ -22,24 +22,29 @@ import com.google.protobuf.Message class AnyMessageFilterStrategy : AbstractFilterStrategy() { + // FIXME data in a fields with the same name rewrite each other, so data can get lost override fun getFields(message: Message): MutableMap { check(message is AnyMessage) { "Message is not an ${AnyMessage::class.qualifiedName}: ${message.toJson()}" } - val result = HashMap(); + val result = HashMap() when { message.hasMessage() -> { result.putAll(message.message.fieldsMap.mapValues { it.value.simpleValue }) val metadata = message.message.metadata + result.putAll(metadata.propertiesMap) result[AbstractTh2MsgFilterStrategy.SESSION_ALIAS_KEY] = metadata.id.connectionId.sessionAlias result[AbstractTh2MsgFilterStrategy.MESSAGE_TYPE_KEY] = metadata.messageType result[AbstractTh2MsgFilterStrategy.DIRECTION_KEY] = metadata.id.direction.name + result[AbstractTh2MsgFilterStrategy.PROTOCOL_KEY] = metadata.protocol } message.hasRawMessage() -> { val metadata = message.rawMessage.metadata + result.putAll(message.rawMessage.metadata.propertiesMap) result[AbstractTh2MsgFilterStrategy.SESSION_ALIAS_KEY] = metadata.id.connectionId.sessionAlias result[AbstractTh2MsgFilterStrategy.DIRECTION_KEY] = metadata.id.direction.name + result[AbstractTh2MsgFilterStrategy.PROTOCOL_KEY] = metadata.protocol } else -> throw IllegalStateException("Message has not messages: ${message.toJson()}") } diff --git a/src/main/kotlin/com/exactpro/th2/common/schema/grpc/configuration/GrpcRawFilterStrategy.kt b/src/main/kotlin/com/exactpro/th2/common/schema/grpc/configuration/GrpcRawFilterStrategy.kt index 50dfc202c..efe922487 100644 --- a/src/main/kotlin/com/exactpro/th2/common/schema/grpc/configuration/GrpcRawFilterStrategy.kt +++ b/src/main/kotlin/com/exactpro/th2/common/schema/grpc/configuration/GrpcRawFilterStrategy.kt @@ -30,7 +30,8 @@ data class GrpcRawFilterStrategy(var filters: List = emptyMultiMap(), - @JsonDeserialize(using = MultiMapFiltersDeserializer::class) override var message: MultiValuedMap = emptyMultiMap() + @JsonDeserialize(using = MultiMapFiltersDeserializer::class) override var message: MultiValuedMap = emptyMultiMap(), + @JsonDeserialize(using = MultiMapFiltersDeserializer::class) override var properties: MultiValuedMap = emptyMultiMap() ) : RouterFilter, Configuration() { @JsonGetter("metadata") @@ -39,4 +40,7 @@ data class GrpcRouterFilterConfiguration( @JsonGetter("message") fun getJsonMessage(): Collection = message.values() + @JsonGetter("properties") + fun getJsonProperties(): Collection = properties.values() + } \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/common/schema/message/configuration/MessageRouterConfiguration.kt b/src/main/kotlin/com/exactpro/th2/common/schema/message/configuration/MessageRouterConfiguration.kt index ed63c3fc2..62774fa56 100644 --- a/src/main/kotlin/com/exactpro/th2/common/schema/message/configuration/MessageRouterConfiguration.kt +++ b/src/main/kotlin/com/exactpro/th2/common/schema/message/configuration/MessageRouterConfiguration.kt @@ -47,12 +47,14 @@ data class QueueConfiguration( data class MqRouterFilterConfiguration( @JsonDeserialize(using = MultiMapFiltersDeserializer::class) override var metadata: MultiValuedMap = emptyMultiMap(), - @JsonDeserialize(using = MultiMapFiltersDeserializer::class) override var message: MultiValuedMap = emptyMultiMap() + @JsonDeserialize(using = MultiMapFiltersDeserializer::class) override var message: MultiValuedMap = emptyMultiMap(), + @JsonDeserialize(using = MultiMapFiltersDeserializer::class) override var properties: MultiValuedMap = emptyMultiMap() ) : Configuration(), RouterFilter { constructor( metadata: Collection = emptyList(), - message: Collection = emptyList() + message: Collection = emptyList(), + properties: Collection = emptyList() ) : this( MultiMapUtils.newListValuedHashMap().also { metadataMultiMap -> metadata.forEach { @@ -63,6 +65,11 @@ data class MqRouterFilterConfiguration( message.forEach { messageMultiMap.put(it.fieldName, it) } + }, + MultiMapUtils.newListValuedHashMap().also { propertiesMultiMap -> + properties.forEach { + propertiesMultiMap.put(it.fieldName, it) + } } ) @@ -71,6 +78,9 @@ data class MqRouterFilterConfiguration( @JsonGetter("message") fun getJsonMessage(): Collection = message.values() + + @JsonGetter("properties") + fun getJsonProperties(): Collection = properties.values() } data class FieldFilterConfigurationOld( @@ -79,8 +89,8 @@ data class FieldFilterConfigurationOld( ) : Configuration() data class FieldFilterConfiguration( - @JsonProperty(value = "fieldName", required = true) var fieldName: String, - @JsonProperty("expectedValue") @JsonAlias("value") var expectedValue: String?, + @JsonProperty(value = "fieldName", required = true) @JsonAlias("field-name") var fieldName: String, + @JsonProperty("expectedValue") @JsonAlias("value", "expected-value") var expectedValue: String?, @JsonProperty(required = true) var operation: FieldFilterOperation ) : Configuration() diff --git a/src/main/kotlin/com/exactpro/th2/common/schema/message/configuration/RouterFilter.kt b/src/main/kotlin/com/exactpro/th2/common/schema/message/configuration/RouterFilter.kt index fcdb650b7..e6d845a6b 100644 --- a/src/main/kotlin/com/exactpro/th2/common/schema/message/configuration/RouterFilter.kt +++ b/src/main/kotlin/com/exactpro/th2/common/schema/message/configuration/RouterFilter.kt @@ -20,4 +20,5 @@ import org.apache.commons.collections4.MultiValuedMap interface RouterFilter { val metadata: MultiValuedMap val message: MultiValuedMap + val properties: MultiValuedMap } \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/common/schema/message/impl/rabbitmq/group/RabbitMessageGroupBatchRouter.kt b/src/main/kotlin/com/exactpro/th2/common/schema/message/impl/rabbitmq/group/RabbitMessageGroupBatchRouter.kt index d1baf2502..468d26068 100644 --- a/src/main/kotlin/com/exactpro/th2/common/schema/message/impl/rabbitmq/group/RabbitMessageGroupBatchRouter.kt +++ b/src/main/kotlin/com/exactpro/th2/common/schema/message/impl/rabbitmq/group/RabbitMessageGroupBatchRouter.kt @@ -51,7 +51,7 @@ class RabbitMessageGroupBatchRouter : AbstractRabbitRouter() val builder = MessageGroupBatch.newBuilder() message.groupsList.forEach { group -> - if (group.messagesList.all { filterMessage(it, pinConfiguration.filters) }) { + if (group.messagesList.any { filterMessage(it, pinConfiguration.filters) }) { builder.addGroups(group) } else { incrementDroppedMetrics( diff --git a/src/test/kotlin/com/exactpro/th2/common/schema/TestJsonConfiguration.kt b/src/test/kotlin/com/exactpro/th2/common/schema/TestJsonConfiguration.kt index 16a5857b4..89e906b5f 100644 --- a/src/test/kotlin/com/exactpro/th2/common/schema/TestJsonConfiguration.kt +++ b/src/test/kotlin/com/exactpro/th2/common/schema/TestJsonConfiguration.kt @@ -183,7 +183,14 @@ class TestJsonConfiguration { FieldFilterOperation.EQUAL ) ), - listOf(FieldFilterConfiguration("test_field", "test_value", FieldFilterOperation.EQUAL)) + listOf(FieldFilterConfiguration("test_field", "test_value", FieldFilterOperation.EQUAL)), + listOf( + FieldFilterConfiguration( + "method", + "POST", + FieldFilterOperation.EQUAL + ) + ), ), MqRouterFilterConfiguration( listOf( @@ -196,6 +203,10 @@ class TestJsonConfiguration { listOf( FieldFilterConfiguration("test_field", "test_value0", FieldFilterOperation.EQUAL), FieldFilterConfiguration("test_field", "test_value1", FieldFilterOperation.EQUAL) + ), + listOf( + FieldFilterConfiguration("method1", "POST", FieldFilterOperation.EQUAL), + FieldFilterConfiguration("method2", "GET", FieldFilterOperation.NOT_EQUAL) ) ) ) diff --git a/src/test/kotlin/com/exactpro/th2/common/schema/filter/strategy/impl/TestAnyMessageFilterStrategy.kt b/src/test/kotlin/com/exactpro/th2/common/schema/filter/strategy/impl/TestAnyMessageFilterStrategy.kt index be2cb2a9a..13bf9e5a6 100644 --- a/src/test/kotlin/com/exactpro/th2/common/schema/filter/strategy/impl/TestAnyMessageFilterStrategy.kt +++ b/src/test/kotlin/com/exactpro/th2/common/schema/filter/strategy/impl/TestAnyMessageFilterStrategy.kt @@ -19,24 +19,210 @@ package com.exactpro.th2.common.schema.filter.strategy.impl import com.exactpro.th2.common.grpc.AnyMessage import com.exactpro.th2.common.grpc.Direction import com.exactpro.th2.common.grpc.RawMessage +import com.exactpro.th2.common.message.addField import com.exactpro.th2.common.message.message import com.exactpro.th2.common.message.toJson import com.exactpro.th2.common.schema.message.configuration.FieldFilterConfiguration import com.exactpro.th2.common.schema.message.configuration.FieldFilterOperation import com.exactpro.th2.common.schema.message.configuration.MqRouterFilterConfiguration import org.apache.commons.collections4.MultiMapUtils -import org.junit.jupiter.api.Assertions +import org.junit.Ignore import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Disabled import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.Arguments.arguments import org.junit.jupiter.params.provider.MethodSource -import org.junit.jupiter.params.provider.ValueSource class TestAnyMessageFilterStrategy { private val strategy = AnyMessageFilterStrategy() + @ParameterizedTest + @MethodSource("multipleFiltersMatch") + fun `matches any filter`(anyMessage: AnyMessage, expectMatch: Boolean) { + val match = strategy.verify( + anyMessage, + listOf( + MqRouterFilterConfiguration( + metadata = MultiMapUtils.newListValuedHashMap().apply { + put("message_type", FieldFilterConfiguration( + fieldName = "message_type", + operation = FieldFilterOperation.EQUAL, + expectedValue = "test" + )) + } + ), + MqRouterFilterConfiguration( + metadata = MultiMapUtils.newListValuedHashMap().apply { + put("direction", FieldFilterConfiguration( + fieldName = "direction", + operation = FieldFilterOperation.EQUAL, + expectedValue = "FIRST" + )) + } + ), + ) + ) + assertEquals(expectMatch, match) { "The message ${anyMessage.toJson()} was${if (expectMatch) "" else " not"} matched" } + } + + @ParameterizedTest + @MethodSource("rawMessagesBothFilters") + fun `matches with multiple metadata filters`(anyMessage: AnyMessage, expectMatch: Boolean) { + val match = strategy.verify( + anyMessage, + MqRouterFilterConfiguration( + metadata = MultiMapUtils.newListValuedHashMap().apply { + put("session_alias", FieldFilterConfiguration( + fieldName = "session_alias", + operation = FieldFilterOperation.EQUAL, + expectedValue = "test-alias" + )) + put("direction", FieldFilterConfiguration( + fieldName = "direction", + operation = FieldFilterOperation.EQUAL, + expectedValue = "FIRST" + )) + } + ) + ) + assertEquals(expectMatch, match) { "The message ${anyMessage.toJson()} was${if (expectMatch) "" else " not"} matched" } + } + + @ParameterizedTest + @MethodSource("parsedMessagesBothFilters") + fun `matches with multiple message filters`(anyMessage: AnyMessage, expectMatch: Boolean) { + val match = strategy.verify( + anyMessage, + MqRouterFilterConfiguration( + message = MultiMapUtils.newListValuedHashMap().apply { + put("test-field1", FieldFilterConfiguration( + fieldName = "test-field1", + operation = FieldFilterOperation.EQUAL, + expectedValue = "test-value1" + )) + put("test-field2", FieldFilterConfiguration( + fieldName = "test-field2", + operation = FieldFilterOperation.EQUAL, + expectedValue = "test-value2" + )) + } + ) + ) + assertEquals(expectMatch, match) { "The message ${anyMessage.toJson()} was${if (expectMatch) "" else " not"} matched" } + } + + @ParameterizedTest + @MethodSource("messagesWithProperties") + fun `matches with multiple properties filters`(anyMessage: AnyMessage, expectMatch: Boolean) { + val match = strategy.verify( + anyMessage, + MqRouterFilterConfiguration( + properties = MultiMapUtils.newListValuedHashMap().apply { + put("prop-field1", FieldFilterConfiguration( + fieldName = "prop-field1", + operation = FieldFilterOperation.EQUAL, + expectedValue = "prop-value1" + )) + put("prop-field2", FieldFilterConfiguration( + fieldName = "prop-field2", + operation = FieldFilterOperation.EQUAL, + expectedValue = "prop-value2" + )) + } + ) + ) + assertEquals(expectMatch, match) { "The message ${anyMessage.toJson()} was${if (expectMatch) "" else " not"} matched" } + } + + @ParameterizedTest + @MethodSource("messagesWithMessageAndMetadataFilters") + fun `matches with multiple message and metadata filters`(anyMessage: AnyMessage, expectMatch: Boolean) { + val match = strategy.verify( + anyMessage, + MqRouterFilterConfiguration( + message = MultiMapUtils.newListValuedHashMap().apply { + put("test-field1", FieldFilterConfiguration( + fieldName = "test-field1", + operation = FieldFilterOperation.EQUAL, + expectedValue = "test-value1" + )) + put("test-field2", FieldFilterConfiguration( + fieldName = "test-field2", + operation = FieldFilterOperation.EQUAL, + expectedValue = "test-value2" + )) + }, + metadata = MultiMapUtils.newListValuedHashMap().apply { + put("message_type", FieldFilterConfiguration( + fieldName = "message_type", + operation = FieldFilterOperation.EQUAL, + expectedValue = "test" + )) + put("direction", FieldFilterConfiguration( + fieldName = "direction", + operation = FieldFilterOperation.EQUAL, + expectedValue = "FIRST" + )) + } + ) + ) + assertEquals(expectMatch, match) { "The message ${anyMessage.toJson()} was${if (expectMatch) "" else " not"} matched" } + } + + @ParameterizedTest + @MethodSource("messageWithAllParts") + @Disabled + // FIXME data in a fields with the same name rewrite each other, so data get lost + fun `matches with message and metadata and property same field filters`(anyMessage: AnyMessage, expectMatch: Boolean) { + val match = strategy.verify( + anyMessage, + MqRouterFilterConfiguration( + message = MultiMapUtils.newListValuedHashMap().apply { + put("message_type", FieldFilterConfiguration( + fieldName = "message_type", + operation = FieldFilterOperation.EQUAL, + expectedValue = "message_value" + )) + }, + metadata = MultiMapUtils.newListValuedHashMap().apply { + put("message_type", FieldFilterConfiguration( + fieldName = "message_type", + operation = FieldFilterOperation.EQUAL, + expectedValue = "metadata_value" + )) + }, + properties = MultiMapUtils.newListValuedHashMap().apply { + put("message_type", FieldFilterConfiguration( + fieldName = "message_type", + operation = FieldFilterOperation.EQUAL, + expectedValue = "properties_value" + )) + }, + ) + ) + assertEquals(expectMatch, match) { "The message ${anyMessage.toJson()} was${if (expectMatch) "" else " not"} matched" } + } + + @ParameterizedTest + @MethodSource("messageWithProtocol") + fun `matches protocol metadata filter`(anyMessage: AnyMessage, expectMatch: Boolean) { + val match = strategy.verify( + anyMessage, + MqRouterFilterConfiguration( + metadata = MultiMapUtils.newListValuedHashMap().apply { + put("protocol", FieldFilterConfiguration( + fieldName = "protocol", + operation = FieldFilterOperation.EQUAL, + expectedValue = "HTTP" + )) + }, + ) + ) + assertEquals(expectMatch, match) { "The message ${anyMessage.toJson()} was${if (expectMatch) "" else " not"} matched" } + + } + @ParameterizedTest @MethodSource("parsedMessages") fun `matches the parsed message by message type with single filter`(anyMessage: AnyMessage, expectMatch: Boolean) { @@ -91,43 +277,351 @@ class TestAnyMessageFilterStrategy { assertEquals(expectMatch, match) { "The message ${message.toJson()} was${if (expectMatch) "" else " not"} matched" } } + @ParameterizedTest + @MethodSource("messagesWithSameFilterFields") + fun `miss matches with the same filter fields`(message: AnyMessage, expectMatch: Boolean) { + val match = strategy.verify( + message, + MqRouterFilterConfiguration( + message = MultiMapUtils.newListValuedHashMap().apply { + put( + "test-field", FieldFilterConfiguration( + fieldName = "test-field", + operation = FieldFilterOperation.EQUAL, + expectedValue = "test-value1" + ) + ) + put( + "test-field", FieldFilterConfiguration( + fieldName = "test-field", + operation = FieldFilterOperation.EQUAL, + expectedValue = "test-value2" + ) + ) + }, + ) + ) + + assertEquals( + expectMatch, + match + ) { "The message ${message.toJson()} was${if (expectMatch) "" else " not"} matched" } + } + + @ParameterizedTest + @MethodSource("messagesWithMultipleFiltersWithSameFilterField") + fun `matches with multiple filters with the same filter fields`(message: AnyMessage, expectMatch: Boolean) { + val match = strategy.verify( + message, + listOf( + MqRouterFilterConfiguration( + message = MultiMapUtils.newListValuedHashMap().apply { + put( + "test-field", FieldFilterConfiguration( + fieldName = "test-field", + operation = FieldFilterOperation.EQUAL, + expectedValue = "test-value1" + ) + ) + }, + ), + MqRouterFilterConfiguration( + message = MultiMapUtils.newListValuedHashMap().apply { + put( + "test-field", FieldFilterConfiguration( + fieldName = "test-field", + operation = FieldFilterOperation.EQUAL, + expectedValue = "test-value2" + ) + ) + }, + ), + + ) + ) + + assertEquals( + expectMatch, + match + ) { "The message ${message.toJson()} was${if (expectMatch) "" else " not"} matched" } + } + + @ParameterizedTest + @MethodSource("messagesWithMultipleSameFields") + fun `matches message with multiple fields with same name`(message: AnyMessage, expectMatch: Boolean) { + val match = strategy.verify( + message, + MqRouterFilterConfiguration( + message = MultiMapUtils.newListValuedHashMap().apply { + put( + "test-field", FieldFilterConfiguration( + fieldName = "test-field", + operation = FieldFilterOperation.NOT_EQUAL, + expectedValue = "test-value1" + ) + ) + put( + "test-field", FieldFilterConfiguration( + fieldName = "test-field", + operation = FieldFilterOperation.EQUAL, + expectedValue = "test-value2" + ) + ) + }, + ) + ) + + assertEquals( + expectMatch, + match + ) { "The message ${message.toJson()} was${if (expectMatch) "" else " not"} matched" } + } + + @ParameterizedTest + @MethodSource("messagesWithOneProperty") + fun `matches message with properties`(message: AnyMessage, expectMatch: Boolean) { + val match = strategy.verify( + message, + MqRouterFilterConfiguration( + properties = MultiMapUtils.newListValuedHashMap().apply { + put( + "test-property", FieldFilterConfiguration( + fieldName = "test-property", + operation = FieldFilterOperation.EQUAL, + expectedValue = "property-value" + ) + ) + }, + ) + ) + + assertEquals( + expectMatch, + match + ) { "The message ${message.toJson()} was${if (expectMatch) "" else " not"} matched" } + } + + @ParameterizedTest + @MethodSource("messagesWithPropertiesAndMetadata") + fun `matches message with properties and metadata`(message: AnyMessage, expectMatch: Boolean) { + val match = strategy.verify( + message, + MqRouterFilterConfiguration( + properties = MultiMapUtils.newListValuedHashMap().apply { + put( + "test-property", FieldFilterConfiguration( + fieldName = "test-property", + operation = FieldFilterOperation.EQUAL, + expectedValue = "property-value" + ) + ) + }, + metadata = MultiMapUtils.newListValuedHashMap().apply { + put("message_type", FieldFilterConfiguration( + fieldName = "message_type", + operation = FieldFilterOperation.EQUAL, + expectedValue = "test" + )) + } + ) + ) + + assertEquals( + expectMatch, + match + ) { "The message ${message.toJson()} was${if (expectMatch) "" else " not"} matched" } + } + companion object { - private val PARSED_MESSAGE_MATCH = AnyMessage.newBuilder().setMessage( - message("test", Direction.FIRST, "test-alias") - ).build() - - private val RAW_MESSAGE_MATCH = AnyMessage.newBuilder().setRawMessage( - RawMessage.newBuilder().apply { - metadataBuilder.idBuilder.apply { - connectionIdBuilder.sessionAlias = "test-alias" - direction = Direction.FIRST + + private fun simpleMessageBuilder(messageType: String, direction: Direction, sessionAlias: String): AnyMessage { + return AnyMessage.newBuilder().setMessage( + message(messageType, direction, sessionAlias) + ).build() + } + + private fun simpleRawMessageBuilder(sessionAlias: String, directionValue: Direction): AnyMessage { + return AnyMessage.newBuilder().setRawMessage( + RawMessage.newBuilder().apply { + metadataBuilder.idBuilder.apply { + connectionIdBuilder.sessionAlias = sessionAlias + direction = directionValue + } } - } - ).build() - - private val PARSED_MESSAGE_MISS_MATCH = AnyMessage.newBuilder().setMessage( - message("test1", Direction.SECOND, "test-alias1") - ).build() - - private val RAW_MESSAGE_MISS_MATCH = AnyMessage.newBuilder().setRawMessage( - RawMessage.newBuilder().apply { - metadataBuilder.idBuilder.apply { - connectionIdBuilder.sessionAlias = "test-alias1" - direction = Direction.SECOND + ).build() + } + + private fun messageWithFieldsBuilder(messageType: String, direction: Direction, fields: List>): AnyMessage { + return AnyMessage.newBuilder().setMessage( + message(messageType, direction, "test-alias").apply { + fields.forEach { addField(it.first, it.second) } + } + ).build() + } + + private fun messageWithPropertiesBuilder(messageType: String, direction: Direction, properties: List>): AnyMessage { + return AnyMessage.newBuilder().setMessage( + message(messageType, direction, "test-alias").apply { + properties.forEach { metadataBuilder.putProperties(it.first, it.second) } + } + ).build() + } + + private fun rawMessageWithOnePropertyBuilder(propertyKey: String, propertyValue: String): AnyMessage { + return AnyMessage.newBuilder().setRawMessage( + RawMessage.newBuilder().apply { + metadataBuilder.putProperties(propertyKey, propertyValue) + } + ).build() + } + + private fun messageWithOnePropertyBuilder(messageType: String, propertyKey: String, propertyValue: String): AnyMessage { + return messageWithPropertiesBuilder(messageType, Direction.FIRST, listOf(Pair(propertyKey, propertyValue))) + } + + private fun messageWithAllPartsBuilder(messageType: String, propertyKey: String, propertyValue: String, fieldKey: String, fieldValue: String): AnyMessage { + return AnyMessage.newBuilder().setMessage( + message(messageType, Direction.FIRST, "test-alias").apply { + metadataBuilder.putProperties(propertyKey, propertyValue) + addField(fieldKey, fieldValue) + } + ).build() + } + + private fun messageWithProtocolBuilder(protocol: String): AnyMessage { + return AnyMessage.newBuilder().setMessage( + message("test", Direction.FIRST, "test-alias").apply { + metadataBuilder.protocol = protocol } - } - ).build() + ).build() + } @JvmStatic - fun parsedMessages(): List = listOf( - arguments(PARSED_MESSAGE_MATCH, true), - arguments(PARSED_MESSAGE_MISS_MATCH, false) + fun messageWithProtocol(): List = listOf( + arguments(messageWithProtocolBuilder("HTTP"), true), + arguments(messageWithProtocolBuilder("FTP"), false), ) @JvmStatic fun messages(): List = listOf( - arguments(RAW_MESSAGE_MATCH, true), - arguments(RAW_MESSAGE_MISS_MATCH, false) + arguments(simpleRawMessageBuilder("test-alias", Direction.FIRST), true), + arguments(simpleRawMessageBuilder("test-alias1", Direction.SECOND), false) ) + parsedMessages() + + @JvmStatic + fun parsedMessages(): List = listOf( + arguments(simpleMessageBuilder("test", Direction.FIRST, "test-alias"), true), + arguments(simpleMessageBuilder("test1", Direction.SECOND, "test-alias1"), false) + ) + + @JvmStatic + fun messagesWithProperties(): List = listOf( + arguments(messageWithPropertiesBuilder("test", Direction.FIRST, listOf( + Pair("prop-field1", "prop-value1"), Pair("prop-field2", "prop-value2"))), true), + arguments(messageWithPropertiesBuilder("test", Direction.FIRST, listOf( + Pair("prop-field1", "prop-value-wrong"), Pair("prop-field2", "prop-value2"))), false), + arguments(messageWithPropertiesBuilder("test", Direction.FIRST, listOf( + Pair("prop-field1", "prop-value1"), Pair("prop-field2", "prop-value-wrong"))), false), + arguments(messageWithPropertiesBuilder("test", Direction.FIRST, listOf( + Pair("prop-field1", "prop-value-wrong"), Pair("prop-field2", "prop-value-wrong"))), false) + ) + + @JvmStatic + fun messagesWithMultipleSameFields(): List = listOf( + arguments(messageWithFieldsBuilder("test", Direction.FIRST, + listOf(Pair("test-field", "test-value1"), Pair("test-field", "test-value2"))), true), + ) + + @JvmStatic + fun messagesWithSameFilterFields(): List = listOf( + arguments(messageWithFieldsBuilder("test", Direction.FIRST, listOf(Pair("test-field", "test-value1"))), false), + arguments(messageWithFieldsBuilder("test", Direction.FIRST, listOf(Pair("test-field", "test-value2"))), false), + ) + + @JvmStatic + fun messagesWithMultipleFiltersWithSameFilterField(): List = listOf( + arguments(messageWithFieldsBuilder("test", Direction.FIRST, listOf(Pair("test-field", "test-value1"))), true), + arguments(messageWithFieldsBuilder("test", Direction.FIRST, listOf(Pair("test-field", "test-value2"))), true), + ) + + @JvmStatic + fun multipleFiltersMatch(): List = listOf( + arguments(simpleMessageBuilder("test", Direction.FIRST, "test-alias"), true), + arguments(simpleMessageBuilder("test", Direction.SECOND, "test-alias"), true), + arguments(simpleMessageBuilder("test-wrong", Direction.FIRST, "test-alias"), true), + arguments(simpleMessageBuilder("test-wrong", Direction.SECOND, "test-alias"), false), + ) + + @JvmStatic + fun messagesWithMessageAndMetadataFilters() : List = listOf( + // fields full match + arguments(messageWithFieldsBuilder("test", Direction.FIRST, + listOf(Pair("test-field1", "test-value1"), Pair("test-field2", "test-value2"))), true), + + // metadata mismatch + arguments(messageWithFieldsBuilder("test", Direction.SECOND, + listOf(Pair("test-field1", "test-value1"), Pair("test-field2", "test-value2"))), false), + arguments(messageWithFieldsBuilder("test-wrong", Direction.FIRST, + listOf(Pair("test-field1", "test-value1"), Pair("test-field2", "test-value2"))), false), + + // fields mismatch + arguments(messageWithFieldsBuilder("test", Direction.FIRST, + listOf(Pair("test-field1", "test-value-wrong"), Pair("test-field2", "test-value2"))), false), + arguments(messageWithFieldsBuilder("test", Direction.FIRST, + listOf(Pair("test-field1", "test-value1"), Pair("test-field2", "test-value-wrong"))), false), + arguments(messageWithFieldsBuilder("test", Direction.FIRST, + listOf(Pair("test-field1", "test-value-wrong"), Pair("test-field2", "test-value-wrong"))), false), + + // one field and one metadata mismatch + arguments(messageWithFieldsBuilder("test", Direction.SECOND, + listOf(Pair("test-field1", "test-value-wrong"), Pair("test-field2", "test-value2"))), false), + arguments(messageWithFieldsBuilder("test-wrong", Direction.FIRST, + listOf(Pair("test-field1", "test-value1"), Pair("test-field2", "test-value-wrong"))), false), + + ) + + @JvmStatic + fun parsedMessagesBothFilters() : List = listOf( + arguments(messageWithFieldsBuilder("test", Direction.FIRST, + listOf(Pair("test-field1", "test-value1"), Pair("test-field2", "test-value2"))), true), + arguments(messageWithFieldsBuilder("test", Direction.FIRST, + listOf(Pair("test-field1", "test-value-wrong"), Pair("test-field2", "test-value2"))), false), + arguments(messageWithFieldsBuilder("test", Direction.FIRST, + listOf(Pair("test-field1", "test-value1"), Pair("test-field2", "test-value-wrong"))), false), + arguments(messageWithFieldsBuilder("test", Direction.FIRST, + listOf(Pair("test-field1", "test-value-wrong"), Pair("test-field2", "test-value-wrong"))), false), + ) + + @JvmStatic + fun messagesWithOneProperty() : List = listOf( + arguments(messageWithOnePropertyBuilder("test", "test-property", "property-value"), true), + arguments(messageWithOnePropertyBuilder("test", "test-property", "property-value-wrong"), false), + arguments(rawMessageWithOnePropertyBuilder("test-property", "property-value"), true), + arguments(rawMessageWithOnePropertyBuilder("test-property", "property-value-wrong"), false) + ) + + @JvmStatic + fun messagesWithPropertiesAndMetadata() : List = listOf( + arguments(messageWithOnePropertyBuilder("test", "test-property", "property-value"), true), + arguments(messageWithOnePropertyBuilder("test", "test-property", "property-value-wrong"), false), + arguments(messageWithOnePropertyBuilder("test-wrong", "test-property", "property-value"), false), + ) + + @JvmStatic + fun rawMessagesBothFilters() : List = listOf( + arguments(simpleRawMessageBuilder("test-alias", Direction.FIRST), true), + arguments(simpleRawMessageBuilder("test-alias", Direction.SECOND), false), + arguments(simpleRawMessageBuilder("test-alias-wrong-value", Direction.SECOND), false), + ) + + @JvmStatic + fun messageWithAllParts() : List = listOf( + arguments(messageWithAllPartsBuilder("message_type", "message_type", "properties_value", "message_type", "message_value"), true), + arguments(simpleMessageBuilder("metadata_value", Direction.FIRST, "test-alias"), false), + arguments(simpleMessageBuilder("metadata_value1", Direction.FIRST, "test-alias"), false), + arguments(simpleMessageBuilder("message_value", Direction.FIRST, "test-alias"), false), + + ) + } } \ No newline at end of file diff --git a/src/test/kotlin/com/exactpro/th2/common/schema/message/impl/rabbitmq/group/TestRabbitMessageGroupBatchRouter.kt b/src/test/kotlin/com/exactpro/th2/common/schema/message/impl/rabbitmq/group/TestRabbitMessageGroupBatchRouter.kt index 4b48e4510..bb8ab8927 100644 --- a/src/test/kotlin/com/exactpro/th2/common/schema/message/impl/rabbitmq/group/TestRabbitMessageGroupBatchRouter.kt +++ b/src/test/kotlin/com/exactpro/th2/common/schema/message/impl/rabbitmq/group/TestRabbitMessageGroupBatchRouter.kt @@ -35,14 +35,16 @@ import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.api.function.Executable +import org.mockito.kotlin.mock import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.eq -import org.mockito.kotlin.mock import org.mockito.kotlin.never +import org.mockito.kotlin.times import org.mockito.kotlin.verify + class TestRabbitMessageGroupBatchRouter { private val connectionConfiguration = ConnectionManagerConfiguration() private val monitor: SubscriberMonitor = mock { } @@ -263,6 +265,75 @@ class TestRabbitMessageGroupBatchRouter { } } + @Nested + inner class BatchPublishing { + + private val router = createRouter(mapOf( + "test-pine" to QueueConfiguration( + routingKey = "", + queue = "subscribe", + exchange = "test-exchange", + attributes = listOf("subscribe") + ), + "test-pin1" to QueueConfiguration( + routingKey = "test", + queue = "", + exchange = "test-exchange", + attributes = listOf("publish"), + filters = listOf( + MqRouterFilterConfiguration( + metadata = listOf( + FieldFilterConfiguration( + fieldName = "message_type", + expectedValue = "test-message", + operation = FieldFilterOperation.EQUAL + ) + ) + ) + ) + ), + )) + + @Test + fun `publish batch if all messages passed`() { + router.send( + MessageGroupBatch.newBuilder() + .addGroups(MessageGroup.newBuilder() + .apply { this += message("test-message", Direction.FIRST, "test-alias") } + .apply { this += message("test-message", Direction.SECOND, "test-alias") } + .apply { this += message("test-message", Direction.FIRST, "test-alias") } + ).build() + ) + verify(connectionManager, times(1)).basicPublish(any(), any(), anyOrNull(), any()) + } + + @Test + fun `dont publish batch if all messages not passed`() { + router.send( + MessageGroupBatch.newBuilder() + .addGroups(MessageGroup.newBuilder() + .apply { this += message("test-message1", Direction.FIRST, "test-alias") } + .apply { this += message("test-message2", Direction.FIRST, "test-alias") } + .apply { this += message("test-message3", Direction.FIRST, "test-alias") } + ).build() + ) + verify(connectionManager, never()).basicPublish(any(), any(), anyOrNull(), any()) + } + + @Test + fun `publish full batch if one message is passed`() { + router.send( + MessageGroupBatch.newBuilder() + .addGroups(MessageGroup.newBuilder() + .apply { this += message("test-message1", Direction.FIRST, "test-alias") } + .apply { this += message("test-message", Direction.FIRST, "test-alias") } + .apply { this += message("test-message3", Direction.FIRST, "test-alias") } + ).build() + ) + verify(connectionManager, times(1)).basicPublish(any(), any(), anyOrNull(), any()) + } + } + private fun createRouter(pins: Map): MessageRouter = RabbitMessageGroupBatchRouter().apply { init(DefaultMessageRouterContext( diff --git a/src/test/resources/test_json_configurations/message_router.json b/src/test/resources/test_json_configurations/message_router.json index dc8a69397..4f70c1c3e 100644 --- a/src/test/resources/test_json_configurations/message_router.json +++ b/src/test/resources/test_json_configurations/message_router.json @@ -13,6 +13,12 @@ "operation": "EQUAL" } }, + "properties": { + "method": { + "value": "POST", + "operation": "EQUAL" + } + }, "message": { "test_field": { "value": "test_value", @@ -28,6 +34,18 @@ "operation": "EQUAL" } ], + "properties": [ + { + "field-name": "method1", + "expected-value": "POST", + "operation": "EQUAL" + }, + { + "fieldName": "method2", + "value": "GET", + "operation": "NOT_EQUAL" + } + ], "message": [ { "fieldName": "test_field",