From e0fbbf6b4e32ded0cf8f57cd1b389d32e75da748 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Tue, 6 Jan 2026 15:10:16 +0200 Subject: [PATCH] REST API call node: support duplicate query param names --- .../common/data/util/CollectionsUtil.java | 10 -- .../rule/engine/rest/QueryParam.java | 23 +++ .../rule/engine/rest/TbHttpClient.java | 27 ++-- .../rest/TbRestApiCallNodeConfiguration.java | 6 +- .../rule/engine/rest/TbHttpClientTest.java | 146 +++++++++--------- .../engine/rest/TbRestApiCallNodeTest.java | 27 +++- 6 files changed, 136 insertions(+), 103 deletions(-) create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/QueryParam.java diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/util/CollectionsUtil.java b/common/data/src/main/java/org/thingsboard/server/common/data/util/CollectionsUtil.java index bdc4430387..082be9b71f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/util/CollectionsUtil.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/util/CollectionsUtil.java @@ -18,7 +18,6 @@ package org.thingsboard.server.common.data.util; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -73,15 +72,6 @@ public class CollectionsUtil { return map; } - @SafeVarargs - public static LinkedHashMap orderedMapOf(Map.Entry... entries) { - LinkedHashMap map = new LinkedHashMap<>(); - for (Map.Entry entry : entries) { - map.put(entry.getKey(), entry.getValue()); - } - return map; - } - public static boolean emptyOrContains(Collection collection, V element) { return isEmpty(collection) || collection.contains(element); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/QueryParam.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/QueryParam.java new file mode 100644 index 0000000000..2a72b3a88a --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/QueryParam.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2025 The Thingsboard Authors + * + * 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 org.thingsboard.rule.engine.rest; + +import jakarta.validation.constraints.NotNull; + +record QueryParam( + @NotNull(message = "query parameter names must be non-null") String name, + @NotNull(message = "query parameter values must be non-null") String value +) {} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index a16b3f5fb5..0d5a78810b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -15,7 +15,6 @@ */ package org.thingsboard.rule.engine.rest; -import com.google.common.collect.Maps; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.handler.ssl.SslContext; @@ -217,14 +216,15 @@ public class TbHttpClient { String endpointUrl = TbNodeUtils.processPattern(config.getRestEndpointUrlPattern(), msg); - Map processedQueryParams; + List processedQueryParams; if (config.isUseNewEncoding()) { - processedQueryParams = Maps.newHashMapWithExpectedSize(config.getQueryParams().size()); - config.getQueryParams().forEach((name, value) -> { - var processedParamName = TbNodeUtils.processPattern(name, msg); - var processedParamValue = TbNodeUtils.processPattern(value, msg); - processedQueryParams.put(processedParamName, processedParamValue); - }); + processedQueryParams = config.getQueryParams().stream() + .map(param -> { + var processedParamName = TbNodeUtils.processPattern(param.name(), msg); + var processedParamValue = TbNodeUtils.processPattern(param.value(), msg); + return new QueryParam(processedParamName, processedParamValue); + }) + .toList(); } else { processedQueryParams = null; } @@ -278,7 +278,7 @@ public class TbHttpClient { return origin; } - public URI buildEncodedUri(String endpointUrl, Map queryParams) { + public URI buildEncodedUri(String endpointUrl, List queryParams) { if (endpointUrl == null) { throw new RuntimeException("Url string cannot be null!"); } @@ -306,16 +306,15 @@ public class TbHttpClient { return uri; } - private URI buildEncodedUriNew(String endpointUrl, Map queryParams) { + private URI buildEncodedUriNew(String endpointUrl, List queryParams) { try { URIBuilder builder = new URIBuilder(endpointUrl); - queryParams.forEach(builder::addParameter); + queryParams.forEach(param -> builder.addParameter(param.name(), param.value())); return builder.build(); } catch (URISyntaxException e) { throw new IllegalArgumentException(""" - Invalid REST endpoint URL: '%s'. The URL must be valid and properly encoded. - If the path contains special characters (e.g., spaces, braces), they must be percent-encoded (e.g., '/my file/' should be '/my%%20file/', '/path/{id}/' should be '/path/%%7Bid%%7D/'). - Query parameters should be configured separately and will be encoded automatically.""".formatted(endpointUrl), e); + Invalid request URL: '%s'. The URL must be valid and properly encoded. + If URL contains special characters (e.g., spaces), they must be percent-encoded (e.g., '/my file/' should be '/my%%20file/').""".formatted(endpointUrl), e); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java index c896dea324..cd12eb5211 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java @@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.rest; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import jakarta.validation.Valid; import jakarta.validation.constraints.AssertTrue; import jakarta.validation.constraints.NotNull; import lombok.Data; @@ -27,6 +28,7 @@ import org.thingsboard.rule.engine.credentials.AnonymousCredentials; import org.thingsboard.rule.engine.credentials.ClientCredentials; import java.util.Collections; +import java.util.List; import java.util.Map; @JsonIgnoreProperties(ignoreUnknown = true) @@ -36,7 +38,7 @@ public class TbRestApiCallNodeConfiguration implements NodeConfiguration queryParams; + private List<@NotNull @Valid QueryParam> queryParams; private Map headers; private boolean useSimpleClientHttpFactory; private int readTimeoutMs; @@ -78,7 +80,7 @@ public class TbRestApiCallNodeConfiguration implements NodeConfiguration queryParams, String expectedEncodedUrl) { + public void testQueryParamsEncoding(String endpointUrl, List queryParams, String expectedEncodedUrl) { // GIVEN Mockito.when(client.buildEncodedUri(any(), any())).thenCallRealMethod(); @@ -253,42 +251,42 @@ public class TbHttpClientTest { return Stream.of( Arguments.of( Named.named("ISO 8601 date-time in value", "http://somecompany/api/data/fetch"), - Map.of("ts", "2016-08-01T09:06:06.0+02:00"), + List.of(new QueryParam("ts", "2016-08-01T09:06:06.0+02:00")), "http://somecompany/api/data/fetch?ts=2016-08-01T09%3A06%3A06.0%2B02%3A00" ), Arguments.of( Named.named("email with plus sign in value", "http://localhost:8080/api/user/sendActivationMail"), - Map.of("email", "someperson+test1289@thingsboard.io"), + List.of(new QueryParam("email", "someperson+test1289@thingsboard.io")), "http://localhost:8080/api/user/sendActivationMail?email=someperson%2Btest1289%40thingsboard.io" ), Arguments.of( Named.named("plus mixed with spaces in value", "http://url/api"), - Map.of("q", "a + b"), + List.of(new QueryParam("q", "a + b")), "http://url/api?q=a%20%2B%20b" ), Arguments.of( Named.named("colon in value", "http://url/api"), - Map.of("time", "12:00"), + List.of(new QueryParam("time", "12:00")), "http://url/api?time=12%3A00" ), Arguments.of( Named.named("slash in value", "http://url"), - Map.of("ref", "/home/user"), + List.of(new QueryParam("ref", "/home/user")), "http://url?ref=%2Fhome%2Fuser" ), Arguments.of( Named.named("comma and semicolon in value", "http://url"), - Map.of("l", "a,b;c"), + List.of(new QueryParam("l", "a,b;c")), "http://url?l=a%2Cb%3Bc" ), Arguments.of( Named.named("ampersand and equals in value", "http://url"), - Map.of("q", "key1=value1&key2=value2"), + List.of(new QueryParam("q", "key1=value1&key2=value2")), "http://url?q=key1%3Dvalue1%26key2%3Dvalue2" ), Arguments.of( Named.named("JSON in value", "http://url"), - Map.of("json", """ + List.of(new QueryParam("json", """ { "string": "hello", "integer": 42, @@ -305,245 +303,253 @@ public class TbHttpClientTest { "object": { "nested": "value" } - }"""), + }""")), "http://url?json=%7B%0A%20%20%20%20%22string%22%3A%20%22hello%22%2C%0A%20%20%20%20%22integer%22%3A%2042%2C%0A%20%20%20%20%22float%22%3A%203.14%2C%0A%20%20%20%20%22boolTrue%22%3A%20true%2C%0A%20%20%20%20%22boolFalse%22%3A%20false%2C%0A%20%20%20%20%22null%22%3A%20null%2C%0A%20%20%20%20%22array%22%3A%20%5B%0A%20%20%20%20%20%20%20%201%2C%0A%20%20%20%20%20%20%20%20%22two%22%2C%0A%20%20%20%20%20%20%20%20true%2C%0A%20%20%20%20%20%20%20%20null%0A%20%20%20%20%5D%2C%0A%20%20%20%20%22object%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%22nested%22%3A%20%22value%22%0A%20%20%20%20%7D%0A%7D" ), Arguments.of( Named.named("UTF-8 in query", "http://url/cafes"), - orderedMapOf( - entry("nom", "Le Goût Moderne"), - entry("назва", "У Миколи \uD83D\uDE0B") + List.of( + new QueryParam("nom", "Le Goût Moderne"), + new QueryParam("назва", "У Миколи \uD83D\uDE0B") ), "http://url/cafes?nom=Le%20Go%C3%BBt%20Moderne&%D0%BD%D0%B0%D0%B7%D0%B2%D0%B0=%D0%A3%20%D0%9C%D0%B8%D0%BA%D0%BE%D0%BB%D0%B8%20%F0%9F%98%8B" ), Arguments.of( Named.named("empty value", "http://url/empty"), - Map.of("name", ""), + List.of(new QueryParam("name", "")), "http://url/empty?name=" ), Arguments.of( Named.named("blank value (spaces)", "http://url/empty"), - Map.of("name", " "), + List.of(new QueryParam("name", " ")), "http://url/empty?name=%20%20" ), Arguments.of( Named.named("empty key", "http://url/empty"), - Map.of("", "value"), + List.of(new QueryParam("", "value")), "http://url/empty?=value" ), Arguments.of( Named.named("blank key (spaces)", "http://url/empty"), - Map.of(" ", "value"), + List.of(new QueryParam(" ", "value")), "http://url/empty?%20%20=value" ), Arguments.of( Named.named("blank key with value that needs to be encoded", "http://url/empty"), - Map.of(" ", "value1+value2"), + List.of(new QueryParam(" ", "value1+value2")), "http://url/empty?%20%20=value1%2Bvalue2" ), Arguments.of( Named.named("blank key and value", "http://url/empty"), - Map.of(" ", " "), + List.of(new QueryParam(" ", " ")), "http://url/empty?%20%20=%20%20" ), Arguments.of( Named.named("fragment with query params", "http://url#frag"), - Map.of("docs", "مستندات عقدة القاعدة\n"), + List.of(new QueryParam("docs", "مستندات عقدة القاعدة\n")), "http://url?docs=%D9%85%D8%B3%D8%AA%D9%86%D8%AF%D8%A7%D8%AA%20%D8%B9%D9%82%D8%AF%D8%A9%20%D8%A7%D9%84%D9%82%D8%A7%D8%B9%D8%AF%D8%A9%0A#frag" ), Arguments.of( Named.named("fragment only", "http://url#frag"), - Collections.emptyMap(), + Collections.emptyList(), "http://url#frag" ), Arguments.of( Named.named("multiple query params (ordered)", "http://url/api"), - orderedMapOf( - entry("param1", "value1"), - entry("param2", "value2"), - entry("param3", "value3") + List.of( + new QueryParam("param1", "value1"), + new QueryParam("param2", "value2"), + new QueryParam("param3", "value3") ), "http://url/api?param1=value1¶m2=value2¶m3=value3" ), Arguments.of( Named.named("hash (#) in value", "http://url/api"), - Map.of("color", "#ff0000"), + List.of(new QueryParam("color", "#ff0000")), "http://url/api?color=%23ff0000" ), Arguments.of( Named.named("question mark (?) in value", "http://url/api"), - Map.of("query", "what?"), + List.of(new QueryParam("query", "what?")), "http://url/api?query=what%3F" ), Arguments.of( Named.named("percent sign (%) in value", "http://url/api"), - Map.of("discount", "50%"), + List.of(new QueryParam("discount", "50%")), "http://url/api?discount=50%25" ), Arguments.of( Named.named("already encoded string - double encoding", "http://url/api"), - Map.of("encoded", "%20"), + List.of(new QueryParam("encoded", "%20")), "http://url/api?encoded=%2520" ), Arguments.of( Named.named("URL with port number", "http://localhost:8080/api/v1/data"), - Map.of("key", "value"), + List.of(new QueryParam("key", "value")), "http://localhost:8080/api/v1/data?key=value" ), Arguments.of( Named.named("URL with userinfo (auth)", "http://user:password@hostname/path"), - Map.of("secure", "true"), + List.of(new QueryParam("secure", "true")), "http://user:password@hostname/path?secure=true" ), Arguments.of( Named.named("IPv4 address in URL", "http://192.168.1.100:9090/endpoint"), - Map.of("ip", "test"), + List.of(new QueryParam("ip", "test")), "http://192.168.1.100:9090/endpoint?ip=test" ), Arguments.of( Named.named("IPv6 address in URL", "http://[::1]:8080/api"), - Map.of("ipv6", "true"), + List.of(new QueryParam("ipv6", "true")), "http://[::1]:8080/api?ipv6=true" ), Arguments.of( Named.named("HTTPS protocol", "https://secure.example.com/api"), - Map.of("token", "abc123"), + List.of(new QueryParam("token", "abc123")), "https://secure.example.com/api?token=abc123" ), Arguments.of( Named.named("pipe (|) in value", "http://url/api"), - Map.of("filter", "a|b|c"), + List.of(new QueryParam("filter", "a|b|c")), "http://url/api?filter=a%7Cb%7Cc" ), Arguments.of( Named.named("caret (^) and backtick (`) in value", "http://url/api"), - Map.of("special", "a^b`c"), + List.of(new QueryParam("special", "a^b`c")), "http://url/api?special=a%5Eb%60c" ), Arguments.of( Named.named("tab character in value", "http://url/api"), - Map.of("data", "col1\tcol2\tcol3"), + List.of(new QueryParam("data", "col1\tcol2\tcol3")), "http://url/api?data=col1%09col2%09col3" ), Arguments.of( Named.named("CRLF in value", "http://url/api"), - Map.of("text", "line1\r\nline2"), + List.of(new QueryParam("text", "line1\r\nline2")), "http://url/api?text=line1%0D%0Aline2" ), Arguments.of( Named.named("square brackets in value", "http://url/api"), - Map.of("array", "[1,2,3]"), + List.of(new QueryParam("array", "[1,2,3]")), "http://url/api?array=%5B1%2C2%2C3%5D" ), Arguments.of( Named.named("single and double quotes in value", "http://url/api"), - Map.of("quoted", "He said \"Hello\" and 'Hi'"), + List.of(new QueryParam("quoted", "He said \"Hello\" and 'Hi'")), "http://url/api?quoted=He%20said%20%22Hello%22%20and%20%27Hi%27" ), Arguments.of( Named.named("angle brackets in value (XSS test)", "http://url/api"), - Map.of("html", ""), + List.of(new QueryParam("html", "")), "http://url/api?html=%3Cscript%3Ealert%28%27xss%27%29%3C%2Fscript%3E" ), Arguments.of( Named.named("backslash in value", "http://url/api"), - Map.of("path", "C:\\Users\\test"), + List.of(new QueryParam("path", "C:\\Users\\test")), "http://url/api?path=C%3A%5CUsers%5Ctest" ), Arguments.of( Named.named("at sign (@) in value", "http://url/api"), - Map.of("contact", "user@domain.com"), + List.of(new QueryParam("contact", "user@domain.com")), "http://url/api?contact=user%40domain.com" ), Arguments.of( Named.named("URL as value", "http://url/api"), - Map.of("redirect", "https://example.com/path?foo=bar"), + List.of(new QueryParam("redirect", "https://example.com/path?foo=bar")), "http://url/api?redirect=https%3A%2F%2Fexample.com%2Fpath%3Ffoo%3Dbar" ), Arguments.of( - Named.named("empty map (not null)", "http://url/api"), - Collections.emptyMap(), + Named.named("empty list (not null)", "http://url/api"), + Collections.emptyList(), "http://url/api" ), Arguments.of( Named.named("underscore and hyphen in key/value", "http://url/api"), - Map.of("my-key_name", "my-value_data"), + List.of(new QueryParam("my-key_name", "my-value_data")), "http://url/api?my-key_name=my-value_data" ), Arguments.of( Named.named("dots in key and value", "http://url/api"), - Map.of("version.major", "1.2.3"), + List.of(new QueryParam("version.major", "1.2.3")), "http://url/api?version.major=1.2.3" ), Arguments.of( Named.named("multiple reserved chars combined", "http://url/api"), - Map.of("complex", "a=1&b=2#section?query"), + List.of(new QueryParam("complex", "a=1&b=2#section?query")), "http://url/api?complex=a%3D1%26b%3D2%23section%3Fquery" ), Arguments.of( Named.named("trailing slash in URL", "http://url/api/"), - Map.of("param", "value"), + List.of(new QueryParam("param", "value")), "http://url/api/?param=value" ), Arguments.of( Named.named("long value (1000 chars)", "http://url/api"), - Map.of("long", "a".repeat(1000)), + List.of(new QueryParam("long", "a".repeat(1000))), "http://url/api?long=" + "a".repeat(1000) ), Arguments.of( Named.named("Chinese characters in value", "http://url/api"), - Map.of("greeting", "你好世界"), + List.of(new QueryParam("greeting", "你好世界")), "http://url/api?greeting=%E4%BD%A0%E5%A5%BD%E4%B8%96%E7%95%8C" ), Arguments.of( Named.named("Japanese characters in value", "http://url/api"), - Map.of("text", "こんにちは"), + List.of(new QueryParam("text", "こんにちは")), "http://url/api?text=%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF" ), Arguments.of( Named.named("existing query params in URL", "http://url/api?existing=param"), - Map.of("new", "value"), + List.of(new QueryParam("new", "value")), "http://url/api?existing=param&new=value" ), Arguments.of( Named.named("existing query and fragment in URL", "http://url/api?existing=param#section"), - Map.of("additional", "data"), + List.of(new QueryParam("additional", "data")), "http://url/api?existing=param&additional=data#section" ), Arguments.of( Named.named("null byte in value", "http://url/api"), - Map.of("data", "before\u0000after"), + List.of(new QueryParam("data", "before\u0000after")), "http://url/api?data=before%00after" ), Arguments.of( Named.named("form feed and vertical tab in value", "http://url/api"), - Map.of("whitespace", "a\fb\u000Bc"), + List.of(new QueryParam("whitespace", "a\fb\u000Bc")), "http://url/api?whitespace=a%0Cb%0Bc" ), Arguments.of( Named.named("tilde (unreserved, not encoded)", "http://url/api"), - Map.of("pattern", "~user"), + List.of(new QueryParam("pattern", "~user")), "http://url/api?pattern=~user" ), Arguments.of( Named.named("asterisk in value", "http://url/api"), - Map.of("wildcard", "*.txt"), + List.of(new QueryParam("wildcard", "*.txt")), "http://url/api?wildcard=%2A.txt" ), Arguments.of( Named.named("curly braces in key", "http://url/api"), - Map.of("{key}", "value"), + List.of(new QueryParam("{key}", "value")), "http://url/api?%7Bkey%7D=value" ), Arguments.of( Named.named("curly braces in value", "http://url/api"), - Map.of("data", "{value}"), + List.of(new QueryParam("data", "{value}")), "http://url/api?data=%7Bvalue%7D" ), Arguments.of( Named.named("curly braces in both key and value", "http://url/api"), - Map.of("{param}", "{data}"), + List.of(new QueryParam("{param}", "{data}")), "http://url/api?%7Bparam%7D=%7Bdata%7D" + ), + Arguments.of( + Named.named("duplicate param names with different values", "http://url/api"), + List.of( + new QueryParam("test", "a+b"), + new QueryParam("test", "b c") + ), + "http://url/api?test=a%2Bb&test=b%20c" ) ); } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java index 8c3654bdc5..39d9e0d585 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java @@ -52,8 +52,7 @@ import org.thingsboard.server.dao.exception.DataValidationException; import java.io.IOException; import java.util.Collections; -import java.util.HashMap; -import java.util.Map; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.stream.Stream; @@ -120,14 +119,28 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { } } + @Test + public void shouldNotAllowNullQueryParamNames() { + // GIVEN + var config = new TbRestApiCallNodeConfiguration().defaultConfiguration(); + config.setUseNewEncoding(true); + config.setQueryParams(List.of(new QueryParam(null, "value"))); + + // WHEN-THEN + assertThatThrownBy(() -> new TbRestApiCallNode().init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config)))) + .isInstanceOf(TbNodeException.class) + .matches(e -> ((TbNodeException) e).isUnrecoverable()) + .rootCause() + .isInstanceOf(DataValidationException.class) + .hasMessageContaining("query parameter names must be non-null"); + } + @Test public void shouldNotAllowNullQueryParamValues() { // GIVEN var config = new TbRestApiCallNodeConfiguration().defaultConfiguration(); config.setUseNewEncoding(true); - var params = new HashMap(); - params.put("key", null); - config.setQueryParams(params); + config.setQueryParams(List.of(new QueryParam("key", null))); // WHEN-THEN assertThatThrownBy(() -> new TbRestApiCallNode().init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config)))) @@ -143,7 +156,7 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { // GIVEN var config = new TbRestApiCallNodeConfiguration().defaultConfiguration(); config.setUseNewEncoding(false); - config.setQueryParams(Map.of("key", "value")); + config.setQueryParams(List.of(new QueryParam("key", "value"))); // WHEN-THEN assertThatThrownBy(() -> new TbRestApiCallNode().init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config)))) @@ -175,7 +188,7 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { // THEN assertTrue(defaultConfig.isUseNewEncoding()); - assertEquals(Collections.emptyMap(), defaultConfig.getQueryParams()); + assertEquals(Collections.emptyList(), defaultConfig.getQueryParams()); } @Test