diff --git a/app/src/main/kotlin/com/exactpro/th2/lwdataprovider/http/TaskDownloadHandler.kt b/app/src/main/kotlin/com/exactpro/th2/lwdataprovider/http/TaskDownloadHandler.kt index b1ccc314..75aca7df 100644 --- a/app/src/main/kotlin/com/exactpro/th2/lwdataprovider/http/TaskDownloadHandler.kt +++ b/app/src/main/kotlin/com/exactpro/th2/lwdataprovider/http/TaskDownloadHandler.kt @@ -29,13 +29,14 @@ import com.exactpro.th2.lwdataprovider.entities.requests.MessagesGroupRequest import com.exactpro.th2.lwdataprovider.entities.requests.ProviderMessageStream import com.exactpro.th2.lwdataprovider.entities.requests.SearchDirection import com.exactpro.th2.lwdataprovider.entities.requests.SseEventSearchRequest -import com.exactpro.th2.lwdataprovider.entities.responses.BaseEventEntity import com.exactpro.th2.lwdataprovider.entities.responses.Event import com.exactpro.th2.lwdataprovider.entities.responses.ProviderMessage53 -import com.exactpro.th2.lwdataprovider.filter.DataFilter +import com.exactpro.th2.lwdataprovider.filter.FilterRequest +import com.exactpro.th2.lwdataprovider.filter.events.EventsFilterFactory import com.exactpro.th2.lwdataprovider.handlers.SearchEventsHandler import com.exactpro.th2.lwdataprovider.handlers.SearchMessagesHandler import com.exactpro.th2.lwdataprovider.http.serializers.CustomMillisOrNanosInstantDeserializer +import com.exactpro.th2.lwdataprovider.http.serializers.FiltersDeserializer import com.exactpro.th2.lwdataprovider.http.util.JSON_STREAM_CONTENT_TYPE import com.exactpro.th2.lwdataprovider.http.util.writeJsonStream import com.exactpro.th2.lwdataprovider.workers.EventTaskInfo @@ -425,7 +426,7 @@ class TaskDownloadHandler( scope = scope, searchDirection = searchDirection, resultCountLimit = limit, - filter = filter, + filter = EventsFilterFactory.create(filter), ) } @@ -485,7 +486,8 @@ class TaskDownloadHandler( limit: Int? = null, searchDirection: SearchDirection = SearchDirection.next, val parentEvent: String? = null, - val filter: DataFilter + @field:JsonDeserialize(using = FiltersDeserializer::class) + val filter: Collection = emptyList() ): CreateTaskRequest( Resource.EVENTS, bookID, startTimestamp, endTimestamp, limit, searchDirection ) diff --git a/app/src/main/kotlin/com/exactpro/th2/lwdataprovider/http/serializers/FiltersDeserializer.kt b/app/src/main/kotlin/com/exactpro/th2/lwdataprovider/http/serializers/FiltersDeserializer.kt new file mode 100644 index 00000000..c9811864 --- /dev/null +++ b/app/src/main/kotlin/com/exactpro/th2/lwdataprovider/http/serializers/FiltersDeserializer.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2024 Exactpro (Exactpro Systems Limited) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.exactpro.th2.lwdataprovider.http.serializers + +import com.exactpro.th2.lwdataprovider.entities.requests.converter.HttpFilterConverter +import com.exactpro.th2.lwdataprovider.filter.FilterRequest +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.deser.std.StdDeserializer +import com.fasterxml.jackson.databind.node.JsonNodeType + +internal class FiltersDeserializer : StdDeserializer>(Collection::class.java) { + override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Collection { + val node = p.codec.readTree(p) ?: return emptyList() + check(node.nodeType == JsonNodeType.OBJECT) { + "Expected ${JsonNodeType.OBJECT} instead of ${node.nodeType} for filter conversion, value: $node" + } + val params: Map> = node.fieldNames().asSequence() + .map { key -> + val subNode = node.get(key) + key to when(subNode.nodeType) { + JsonNodeType.STRING, JsonNodeType.BOOLEAN -> setOf(subNode.asText()) + JsonNodeType.ARRAY -> subNode.asSequence().map { element -> + check(element.nodeType == JsonNodeType.STRING) { + "Expected ${JsonNodeType.STRING} instead of ${element.nodeType} for '$key' field, value: $element" + } + element.asText() + }.toSet() + else -> error( + "Expected ${JsonNodeType.STRING} or ${JsonNodeType.ARRAY} instead of ${subNode.nodeType} for '$key' field, value: $subNode" + ) + } + }.toMap() + return HttpFilterConverter.convert(params) + } + + companion object { + private const val serialVersionUID: Long = 2794140580320053221L + } +} \ No newline at end of file diff --git a/app/src/test/kotlin/com/exactpro/th2/lwdataprovider/http/TestTaskDownloadHandler.kt b/app/src/test/kotlin/com/exactpro/th2/lwdataprovider/http/TestTaskDownloadHandler.kt index 1024a24a..c227bc5d 100644 --- a/app/src/test/kotlin/com/exactpro/th2/lwdataprovider/http/TestTaskDownloadHandler.kt +++ b/app/src/test/kotlin/com/exactpro/th2/lwdataprovider/http/TestTaskDownloadHandler.kt @@ -108,7 +108,7 @@ class TestTaskDownloadHandler : AbstractHttpHandlerTest() { } @Test - fun `creates task`() { + fun `creates messages task`() { startTest { _, client -> val response = client.post( path = "/download", @@ -143,7 +143,44 @@ class TestTaskDownloadHandler : AbstractHttpHandlerTest() { } @Test - fun `reports incorrect params`() { + fun `creates events task`() { + startTest { _, client -> + val response = client.post( + path = "/download", + json = mapOf( + "resource" to "EVENTS", + "bookID" to "test-book", + "startTimestamp" to Instant.now().plusSeconds(10).toEpochMilli(), + "endTimestamp" to Instant.now().toEpochMilli(), + "scope" to "test-scope", + "limit" to 42, + "searchDirection" to "previous", + "parentEvent" to "test-book:test-scope:20241101103050123456789:test-event-id", + "filter" to mapOf( + "filters" to setOf("type", "name"), + "type-values" to setOf("type-a", "type-b"), + "name-values" to setOf("name-a", "name-b"), + "type-conjunct" to "true", + "name-conjunct" to "false", + "type-negative" to "false", + "name-negative" to "true", + ), + ) + ) + + expectThat(response) { + get { code } isEqualTo HttpStatus.CREATED.code + jsonBody() + .isObject() + .has("taskID") + .path("taskID") + .isTextual() + } + } + } + + @Test + fun `reports incorrect params for messages task`() { startTest { _, client -> val response = client.post( path = "/download", @@ -171,6 +208,43 @@ class TestTaskDownloadHandler : AbstractHttpHandlerTest() { } } + @Test + fun `reports incorrect params for events task`() { + startTest { _, client -> + val response = client.post( + path = "/download", + json = mapOf( + "resource" to "EVENTS", + "bookID" to "", + "startTimestamp" to Instant.now().plusSeconds(10).toEpochMilli(), + "endTimestamp" to Instant.now().toEpochMilli(), + "scope" to "", + "limit" to -42, + "searchDirection" to "previous", + "parentEvent" to "test-book:test-scope:20241101103050123456789:test-event-id", + "filter" to mapOf( + "filters" to setOf("type", "name"), + "type-values" to setOf("type-a", "type-b"), + "name-values" to setOf("name-a", "name-b"), + "type-conjunct" to "true", + "name-conjunct" to "false", + "type-negative" to "false", + "name-negative" to "true", + ), + ) + ) + + expectThat(response) { + get { code } isEqualTo HttpStatus.BAD_REQUEST.code + jsonBody() + .isObject() + .has("bookID") + .has("scope") + .has("limit") + } + } + } + @Test fun `removes existing task`() { startTest { _, client ->