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 {

Reply via email to