This is an automated email from the ASF dual-hosted git repository.
Aias00 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git
The following commit(s) were added to refs/heads/master by this push:
new 8534c46448 fix(admin): serialize default selector registration across
Admins (#7941)
8534c46448 is described below
commit 8534c4644861d1579963c619a6d8008677b1b5f1
Author: redyyang <[email protected]>
AuthorDate: Fri Oct 23 10:05:15 2026 +0800
fix(admin): serialize default selector registration across Admins (#7941)
* fix(admin): serialize default selector registration across Admins
* test(ci): align cryptor failures and wait for usable SDK routes
* refactor(admin): share current-read selector registration locking
---
.../apache/shenyu/admin/mapper/SelectorMapper.java | 13 +++
.../shenyu/admin/service/SelectorService.java | 1 +
.../admin/service/impl/SelectorServiceImpl.java | 18 +++-
.../src/main/resources/mappers/selector-sqlmap.xml | 9 ++
.../ZookeeperClientDiscoveryIntegrationTest.java | 12 ++-
.../SelectorRegistrationConcurrencyTest.java | 120 +++++++++++++++++++++
.../shenyu/admin/service/SelectorServiceTest.java | 2 +-
.../combination/CryptorResponsePluginTest.java | 26 +++--
.../test/sdk/http/SdkHttpIntegratedTest.java | 19 ++++
9 files changed, 204 insertions(+), 16 deletions(-)
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/mapper/SelectorMapper.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/mapper/SelectorMapper.java
index ecac4cca50..4a75b02abf 100644
---
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/mapper/SelectorMapper.java
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/mapper/SelectorMapper.java
@@ -114,6 +114,19 @@ public interface SelectorMapper extends ExistProvider {
@Param("pluginId") String
pluginId,
@Param("namespaceId")
String namespaceId);
+ /**
+ * Read the current selector while holding the registration transaction's
plugin lock.
+ * FOR UPDATE forces a current read rather than an older REPEATABLE READ
snapshot.
+ *
+ * @param name selector name
+ * @param pluginId plugin identifier
+ * @param namespaceId namespace identifier
+ * @return existing selector, or null
+ */
+ SelectorDO findByNameAndPluginIdAndNamespaceIdForUpdate(@Param("name")
String name,
+ @Param("pluginId")
String pluginId,
+
@Param("namespaceId") String namespaceId);
+
/**
* count selector by query.
*
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/SelectorService.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/SelectorService.java
index 046789013f..ac56cc6c31 100644
---
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/SelectorService.java
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/SelectorService.java
@@ -168,6 +168,7 @@ public interface SelectorService extends
PageService<SelectorQueryCondition, Sel
* @param namespaceId namespaceId.
* @return SelectorDO
*/
+ // Requires a caller-owned transaction to retain the per-plugin
registration lock.
SelectorDO findByNameAndPluginNameAndNamespaceIdForUpdate(String name,
String pluginName, String namespaceId);
/**
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/SelectorServiceImpl.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/SelectorServiceImpl.java
index 84718f352f..a3889935a0 100644
---
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/SelectorServiceImpl.java
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/SelectorServiceImpl.java
@@ -24,6 +24,7 @@ import com.google.common.collect.Lists;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
import org.apache.shenyu.admin.aspect.annotation.DataPermission;
import org.apache.shenyu.admin.aspect.annotation.Pageable;
import org.apache.shenyu.admin.discovery.DiscoveryLevel;
@@ -202,12 +203,16 @@ public class SelectorServiceImpl implements
SelectorService {
}
@Override
+ @Transactional(rollbackFor = Exception.class)
public String registerDefault(final MetaDataRegisterDTO dto, final String
pluginName, final String selectorHandler) {
String contextPath =
ContextPathUtils.buildContextPath(dto.getContextPath(), dto.getAppName());
String namespaceId = StringUtils.defaultIfEmpty(dto.getNamespaceId(),
SYS_DEFAULT_NAMESPACE_ID);
- SelectorDO selectorDO =
findByNameAndPluginNameAndNamespaceId(contextPath, pluginName, namespaceId);
+ // The per-plugin lock spans creation and notifications until this
transaction commits.
+ Pair<PluginDO, SelectorDO> registration = lockSelector(contextPath,
pluginName, namespaceId);
+ PluginDO pluginDO = registration.getLeft();
+ SelectorDO selectorDO = registration.getRight();
if (Objects.isNull(selectorDO)) {
- SelectorDTO selectorDTO =
SelectorUtil.buildSelectorDTO(contextPath,
pluginMapper.selectByName(pluginName).getId());
+ SelectorDTO selectorDTO =
SelectorUtil.buildSelectorDTO(contextPath, pluginDO.getId());
selectorDTO.setHandle(selectorHandler);
selectorDTO.setNamespaceId(namespaceId);
return registerDefault(selectorDTO);
@@ -426,8 +431,13 @@ public class SelectorServiceImpl implements
SelectorService {
@Override
public SelectorDO findByNameAndPluginNameAndNamespaceIdForUpdate(final
String name, final String pluginName, final String namespaceId) {
- PluginDO pluginDO = pluginMapper.selectByNameForUpdate(pluginName);
- return selectorMapper.findByNameAndPluginIdAndNamespaceId(name,
pluginDO.getId(), namespaceId);
+ return lockSelector(name, pluginName, namespaceId).getRight();
+ }
+
+ private Pair<PluginDO, SelectorDO> lockSelector(final String name, final
String pluginName, final String namespaceId) {
+ // Lock a persistent parent even when no selector exists yet, across
all Admin instances.
+ PluginDO pluginDO =
Objects.requireNonNull(pluginMapper.selectByNameForUpdate(pluginName), "unknown
plugin: " + pluginName);
+ return Pair.of(pluginDO,
selectorMapper.findByNameAndPluginIdAndNamespaceIdForUpdate(name,
pluginDO.getId(), namespaceId));
}
@Override
diff --git a/shenyu-admin/src/main/resources/mappers/selector-sqlmap.xml
b/shenyu-admin/src/main/resources/mappers/selector-sqlmap.xml
index c8c895cc2c..c8814e99ba 100644
--- a/shenyu-admin/src/main/resources/mappers/selector-sqlmap.xml
+++ b/shenyu-admin/src/main/resources/mappers/selector-sqlmap.xml
@@ -87,6 +87,15 @@
</foreach>
</select>
+ <select id="findByNameAndPluginIdAndNamespaceIdForUpdate"
resultMap="BaseResultMap">
+ SELECT <include refid="Base_Column_List"/>
+ FROM selector
+ WHERE selector_name = #{name, jdbcType=VARCHAR}
+ AND plugin_id = #{pluginId, jdbcType=VARCHAR}
+ AND namespace_id = #{namespaceId, jdbcType=VARCHAR}
+ FOR UPDATE
+ </select>
+
<select id="selectByNameAndNamespaceId" parameterType="java.lang.String"
resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
diff --git
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/discovery/ZookeeperClientDiscoveryIntegrationTest.java
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/discovery/ZookeeperClientDiscoveryIntegrationTest.java
index 09e28e54a7..830dac7a74 100644
---
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/discovery/ZookeeperClientDiscoveryIntegrationTest.java
+++
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/discovery/ZookeeperClientDiscoveryIntegrationTest.java
@@ -41,6 +41,8 @@ import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.TestPropertySource;
import java.io.InputStream;
+import java.io.IOException;
+import java.net.ServerSocket;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
@@ -79,6 +81,8 @@ public class ZookeeperClientDiscoveryIntegrationTest extends
AbstractSpringInteg
private final List<ShenyuInstanceRegisterRepository> clients = new
CopyOnWriteArrayList<>();
+ private final List<ServerSocket> backends = new CopyOnWriteArrayList<>();
+
private final List<DiscoverySyncData> snapshots = new
CopyOnWriteArrayList<>();
private final HttpClient httpClient =
HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
@@ -86,11 +90,14 @@ public class ZookeeperClientDiscoveryIntegrationTest
extends AbstractSpringInteg
private WebSocket websocket;
@AfterEach
- public void cleanup() {
+ public void cleanup() throws IOException {
if (Objects.nonNull(websocket)) {
websocket.abort();
}
clients.forEach(this::stopInstance);
+ for (ServerSocket backend : backends) {
+ backend.close();
+ }
List<DiscoveryDO> discoveries = registeredDiscoveries();
discoveries.forEach(discovery ->
processorHolder.chooseProcessor("zookeeper").removeDiscovery(discovery));
jdbcTemplate.update("DELETE FROM discovery_upstream WHERE
discovery_handler_id IN "
@@ -172,6 +179,9 @@ public class ZookeeperClientDiscoveryIntegrationTest
extends AbstractSpringInteg
}
private ShenyuInstanceRegisterRepository startInstance(final TestingServer
server, final String app, final int port) throws Exception {
+ // The Admin health checker remains active while testing registry
removal.
+ // Keep registered backends reachable so health eviction cannot
replace that event.
+ backends.add(new ServerSocket(port));
String appName = "zk-6525-" + app;
Properties properties = new Properties();
properties.setProperty("name", appName);
diff --git
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/SelectorRegistrationConcurrencyTest.java
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/SelectorRegistrationConcurrencyTest.java
new file mode 100644
index 0000000000..55de2df127
--- /dev/null
+++
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/SelectorRegistrationConcurrencyTest.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.shenyu.admin.service;
+
+import org.apache.shenyu.admin.mapper.DiscoveryHandlerMapper;
+import org.apache.shenyu.admin.mapper.DiscoveryMapper;
+import org.apache.shenyu.admin.mapper.DiscoveryRelMapper;
+import org.apache.shenyu.admin.mapper.DiscoveryUpstreamMapper;
+import org.apache.shenyu.admin.mapper.PluginMapper;
+import org.apache.shenyu.admin.mapper.SelectorConditionMapper;
+import org.apache.shenyu.admin.mapper.SelectorMapper;
+import org.apache.shenyu.admin.discovery.DiscoveryProcessorHolder;
+import org.apache.shenyu.admin.service.impl.SelectorServiceImpl;
+import org.apache.shenyu.admin.service.publish.SelectorEventPublisher;
+import org.apache.shenyu.register.common.dto.MetaDataRegisterDTO;
+import org.junit.jupiter.api.Test;
+import org.mybatis.spring.SqlSessionFactoryBean;
+import org.mybatis.spring.SqlSessionTemplate;
+import org.springframework.aop.framework.ProxyFactory;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+import org.springframework.jdbc.datasource.DriverManagerDataSource;
+import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
+import
org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
+import org.springframework.transaction.interceptor.TransactionInterceptor;
+
+import java.util.UUID;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import static
org.apache.shenyu.common.constant.Constants.SYS_DEFAULT_NAMESPACE_ID;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Independent Admin service instances serialize registration through a
database row lock.
+ */
+class SelectorRegistrationConcurrencyTest {
+
+ @Test
+ void concurrentAdminsCreateOneSelectorPerNamespace() throws Exception {
+ DriverManagerDataSource dataSource = new DriverManagerDataSource(
+ "jdbc:h2:mem:selector-" + UUID.randomUUID() +
";MODE=MySQL;DB_CLOSE_DELAY=-1;LOCK_TIMEOUT=5000", "sa", "");
+ new ResourceDatabasePopulator(new
ClassPathResource("sql-script/h2/schema.sql")).execute(dataSource);
+ final JdbcTemplate jdbc = new JdbcTemplate(dataSource);
+ SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
+ factory.setDataSource(dataSource);
+ factory.setMapperLocations(new
ClassPathResource("mappers/plugin-sqlmap.xml"), new
ClassPathResource("mappers/selector-sqlmap.xml"));
+ SqlSessionTemplate sessions = new
SqlSessionTemplate(factory.getObject());
+ PluginMapper plugins = sessions.getMapper(PluginMapper.class);
+ SelectorMapper selectors = sessions.getMapper(SelectorMapper.class);
+ DataSourceTransactionManager transactions = new
DataSourceTransactionManager(dataSource);
+ final SelectorService first = service(selectors, plugins,
transactions);
+ final SelectorService second = service(selectors, plugins,
transactions);
+ ExecutorService workers = Executors.newFixedThreadPool(2);
+ try {
+ for (int iteration = 0; iteration < 8; iteration++) {
+ final String path = "/concurrent-" + iteration;
+ final CyclicBarrier start = new CyclicBarrier(2);
+ Future<String> one = workers.submit(() -> register(first,
path, start));
+ Future<String> two = workers.submit(() -> register(second,
path, start));
+ assertEquals(one.get(10, TimeUnit.SECONDS), two.get(10,
TimeUnit.SECONDS));
+ assertEquals(1, jdbc.queryForObject("SELECT COUNT(*) FROM
selector WHERE selector_name = ? AND namespace_id = ?",
+ Integer.class, path, SYS_DEFAULT_NAMESPACE_ID));
+ }
+ MetaDataRegisterDTO existing = new MetaDataRegisterDTO();
+ existing.setContextPath("/concurrent-0");
+ existing.setNamespaceId(SYS_DEFAULT_NAMESPACE_ID);
+ String expectedId = jdbc.queryForObject("SELECT id FROM selector
WHERE selector_name = ? AND namespace_id = ?",
+ String.class, "/concurrent-0", SYS_DEFAULT_NAMESPACE_ID);
+ assertEquals(expectedId, first.registerDefault(existing, "divide",
"[]"));
+ assertEquals(1, jdbc.queryForObject("SELECT COUNT(*) FROM selector
WHERE selector_name = ? AND namespace_id = ?",
+ Integer.class, "/concurrent-0", SYS_DEFAULT_NAMESPACE_ID));
+ } finally {
+ workers.shutdownNow();
+ jdbc.execute("SHUTDOWN");
+ }
+ }
+
+ private String register(final SelectorService service, final String path,
final CyclicBarrier start) throws Exception {
+ MetaDataRegisterDTO metadata = new MetaDataRegisterDTO();
+ metadata.setContextPath(path);
+ metadata.setAppName("concurrent-app");
+ metadata.setNamespaceId(SYS_DEFAULT_NAMESPACE_ID);
+ start.await(5, TimeUnit.SECONDS);
+ return service.registerDefault(metadata, "divide", "[]");
+ }
+
+ private SelectorService service(final SelectorMapper selectors, final
PluginMapper plugins,
+ final DataSourceTransactionManager
transactions) {
+ SelectorServiceImpl target = new SelectorServiceImpl(selectors,
mock(SelectorConditionMapper.class), plugins,
+ mock(ApplicationEventPublisher.class),
mock(DiscoveryMapper.class), mock(DiscoveryHandlerMapper.class),
+ mock(DiscoveryRelMapper.class),
mock(DiscoveryUpstreamMapper.class), mock(DiscoveryProcessorHolder.class),
+ mock(SelectorEventPublisher.class));
+ ProxyFactory proxy = new ProxyFactory(target);
+ proxy.addAdvice(new TransactionInterceptor(transactions, new
AnnotationTransactionAttributeSource()));
+ return (SelectorService) proxy.getProxy();
+ }
+}
+
diff --git
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/SelectorServiceTest.java
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/SelectorServiceTest.java
index a77cc361d6..b11c405ceb 100644
---
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/SelectorServiceTest.java
+++
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/SelectorServiceTest.java
@@ -332,7 +332,7 @@ public final class SelectorServiceTest {
publishEvent();
// Test the situation where the selector cannot be found based on the
contextPath.
- given(pluginMapper.selectByName("test")).willReturn(buildPluginDO());
+
given(pluginMapper.selectByNameForUpdate("test")).willReturn(buildPluginDO());
assertNotNull(selectorService.registerDefault(buildMetaDataRegisterDTO(),
"test", ""));
}
diff --git
a/shenyu-integrated-test/shenyu-integrated-test-http/src/test/java/org/apache/shenyu/integrated/test/http/combination/CryptorResponsePluginTest.java
b/shenyu-integrated-test/shenyu-integrated-test-http/src/test/java/org/apache/shenyu/integrated/test/http/combination/CryptorResponsePluginTest.java
index 75c6c28a06..bb426f7002 100644
---
a/shenyu-integrated-test/shenyu-integrated-test-http/src/test/java/org/apache/shenyu/integrated/test/http/combination/CryptorResponsePluginTest.java
+++
b/shenyu-integrated-test/shenyu-integrated-test-http/src/test/java/org/apache/shenyu/integrated/test/http/combination/CryptorResponsePluginTest.java
@@ -21,6 +21,7 @@ import com.google.common.collect.Lists;
import com.google.gson.JsonObject;
import org.apache.shenyu.common.dto.ConditionData;
import org.apache.shenyu.common.enums.PluginEnum;
+import org.apache.shenyu.common.utils.GsonUtils;
import org.apache.shenyu.integratedtest.common.AbstractPluginDataInit;
import org.apache.shenyu.integratedtest.common.dto.AdminResponse;
import org.apache.shenyu.integratedtest.common.dto.UserDTO;
@@ -49,6 +50,8 @@ import static
org.apache.shenyu.plugin.cryptor.strategy.MapTypeEnum.ALL;
import static org.apache.shenyu.plugin.cryptor.strategy.MapTypeEnum.FIELD;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.containsString;
public class CryptorResponsePluginTest extends AbstractPluginDataInit {
@@ -118,17 +121,20 @@ public class CryptorResponsePluginTest extends
AbstractPluginDataInit {
}
- @Test
- @DisplayName("return original message when request doesnt exist filed")
- public void testWhenDoesntExistFiled() throws Exception {
+ @ParameterizedTest
+ @ValueSource(strings = {"decrypt", "encrypt"})
+ @DisplayName("reject a response missing the configured field")
+ public void testWhenConfiguredFieldDoesNotExist(final String way) throws
Exception {
initSelectorAndRules(PluginEnum.CRYPTOR_RESPONSE.getName(),
- "", SINGLETON_CONDITION_LIST, buildRuleLocalDataList("data",
"decrypt", FIELD.getMapType()));
-
- UserDTO actualUser = HttpHelper.INSTANCE.postGateway(TEST_PATH,
originalBody, UserDTO.class);
-
- assertThat(actualUser.getUserId(), is(originalBody.getUserId()));
- assertThat(actualUser.getUserName(), is(originalBody.getUserName()));
-
+ "", SINGLETON_CONDITION_LIST, buildRuleLocalDataList("data",
way, FIELD.getMapType()));
+
+ String body = HttpHelper.INSTANCE.postGateway(TEST_PATH, originalBody,
String.class);
+ AdminResponse response = GsonUtils.getInstance().fromJson(body,
AdminResponse.class);
+ ShenyuResultEnum error =
ShenyuResultEnum.CRYPTOR_RESPONSE_ERROR_CONFIGURATION;
+ assertThat(response.getCode(), is(error.getCode()));
+ assertThat(response.getMessage(), is(error.getMsg() + "[fieldNames]"));
+ assertThat(body, not(containsString(TEST_USER_ID)));
+ assertThat(body, not(containsString(TEST_USER_NAME)));
}
@DisplayName("return failed message when decrypt or encrypt failed")
diff --git
a/shenyu-integrated-test/shenyu-integrated-test-sdk-http/src/test/java/org/apache/shenyu/integrated/test/sdk/http/SdkHttpIntegratedTest.java
b/shenyu-integrated-test/shenyu-integrated-test-sdk-http/src/test/java/org/apache/shenyu/integrated/test/sdk/http/SdkHttpIntegratedTest.java
index e30e1a03e9..01bb356fc7 100644
---
a/shenyu-integrated-test/shenyu-integrated-test-sdk-http/src/test/java/org/apache/shenyu/integrated/test/sdk/http/SdkHttpIntegratedTest.java
+++
b/shenyu-integrated-test/shenyu-integrated-test-sdk-http/src/test/java/org/apache/shenyu/integrated/test/sdk/http/SdkHttpIntegratedTest.java
@@ -21,16 +21,35 @@ import java.util.stream.Stream;
import org.apache.shenyu.integrated.test.sdk.http.dto.SdkTestDto;
import org.apache.shenyu.integratedtest.common.AbstractPluginDataInit;
import org.apache.shenyu.integratedtest.common.helper.HttpHelper;
+import org.junit.jupiter.api.BeforeAll;
import java.io.IOException;
+import java.time.Duration;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.awaitility.Awaitility.await;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
public class SdkHttpIntegratedTest extends AbstractPluginDataInit {
+ @BeforeAll
+ public static void awaitRegisteredRoutes() {
+ contextPortKeys().forEach(arguments -> {
+ Object[] values = arguments.get();
+ await().alias("SDK route registration for " +
values[0]).atMost(Duration.ofSeconds(60))
+ .ignoreExceptionsMatching(error -> error instanceof
IOException)
+ .pollInterval(Duration.ofMillis(500)).untilAsserted(() -> {
+ SdkTestDto response =
HttpHelper.INSTANCE.getHttpService("http://localhost:" + values[1]
+ + "/sdk/" + values[0] + "/findById?id=10",
null, SdkTestDto.class);
+ assertNotNull(response);
+ assertEquals("sdk-currentToken", response.getName());
+ });
+ });
+ }
+
@ParameterizedTest(name = "{index} => test {0}")
@MethodSource("contextPortKeys")
public void testFindById(final String context, final String port) throws
IOException {