You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shenyu.apache.org by xi...@apache.org on 2022/10/10 03:00:34 UTC

[shenyu] branch master updated: [type:e2e] refinement the ShenYuAdmin client (#4048)

This is an automated email from the ASF dual-hosted git repository.

xiaoyu 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 af17b5cb4 [type:e2e] refinement the ShenYuAdmin client (#4048)
af17b5cb4 is described below

commit af17b5cb48ca4b96994bbbe9668ffc2fe4fb683e
Author: Luke.Z <10...@users.noreply.github.com>
AuthorDate: Mon Oct 10 11:00:26 2022 +0800

    [type:e2e] refinement the ShenYuAdmin client (#4048)
    
    * refinement the ShenYuAdmin client
    
    * fix
    
    * fix
---
 .../shenyu/e2e/client/admin/AdminClient.java       | 227 +++++++++++++++------
 .../client/admin/model/data/SearchCondition.java   |  18 ++
 .../e2e/client/admin/model/response/RuleDTO.java   |   2 +
 .../admin/model/response/SearchedResources.java    |   2 +-
 .../org/apache/shenyu/e2e/matcher/RuleMatcher.java |  29 +++
 .../shenyu/e2e/client/admin/AdminClientTest.java   | 127 +++++++++---
 .../apache/shenyu/e2e/engine/ShenYuEngineTest.java |   4 +-
 7 files changed, 312 insertions(+), 97 deletions(-)

diff --git a/shenyu-e2e/shenyu-e2e-client/src/main/java/org/apache/shenyu/e2e/client/admin/AdminClient.java b/shenyu-e2e/shenyu-e2e-client/src/main/java/org/apache/shenyu/e2e/client/admin/AdminClient.java
index 58cf9dadc..5aad12144 100644
--- a/shenyu-e2e/shenyu-e2e-client/src/main/java/org/apache/shenyu/e2e/client/admin/AdminClient.java
+++ b/shenyu-e2e/shenyu-e2e-client/src/main/java/org/apache/shenyu/e2e/client/admin/AdminClient.java
@@ -19,6 +19,7 @@ package org.apache.shenyu.e2e.client.admin;
 
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.base.Function;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
@@ -58,6 +59,8 @@ import java.util.List;
 import java.util.Properties;
 import java.util.stream.Collectors;
 
+import static org.apache.shenyu.e2e.client.admin.model.data.SearchCondition.QUERY_ALL;
+
 @Slf4j
 @ShenYuAdminClient
 public class AdminClient {
@@ -68,14 +71,8 @@ public class AdminClient {
     private final String scenarioId;
     
     private final String baseURL;
-    private final Properties properties;
     private final ImmutableMap<String, String> loginInfo;
     
-    
-    private static final TypeReference<PaginatedResources<SelectorDTO>> PAGINATED_SELECTORS_TYPE_REFERENCE = new TypeReference<>() {
-    };
-    private static final TypeReference<PaginatedResources<RuleDTO>> PAGINATED_RULES_TYPE_REFERENCE = new TypeReference<>() {
-    };
     private static final TypeReference<PaginatedResources<PluginDTO>> PAGINATED_PLUGINS_TYPE_REFERENCE = new TypeReference<>() {
     };
     
@@ -83,35 +80,24 @@ public class AdminClient {
     };
     private static final TypeReference<SearchedResources<RuleDTO>> SEARCHED_RULES_TYPE_REFERENCE = new TypeReference<>() {
     };
-    private static final TypeReference<SearchedResources<PluginDTO>> SEARCHED_PLUGINS_TYPE_REFERENCE = new TypeReference<>() {
-    };
-    private static final TypeReference<PaginatedResources<FakeResourceDTO>> FAKE_VALUE_TYPE = new TypeReference<>() {
+    private static final TypeReference<SearchedResources<FakeResourceDTO>> FAKE_VALUE_TYPE = new TypeReference<>() {
     };
     
-    @Deprecated
-    public AdminClient(String baseURL, String username, String password) {
-        this.baseURL = baseURL;
-        this.scenarioId = "";
-        this.loginInfo = ImmutableMap.<String, String>builder()
-                .put("username", username)
-                .put("password", password)
-                .build();
-        this.properties = null;
-    }
-    
     public AdminClient(String scenarioId, String baseURL, Properties properties) {
         Preconditions.checkArgument(properties.containsKey("username"), "Property username does not exist");
         Preconditions.checkArgument(properties.containsKey("password"), "Property password does not exist");
         
         this.baseURL = baseURL;
         this.scenarioId = scenarioId;
-        this.properties = properties;
         this.loginInfo = ImmutableMap.<String, String>builder()
                 .put("username", properties.getProperty("username"))
                 .put("password", properties.getProperty("password"))
                 .build();
     }
     
+    /**
+     * Login to ShenYu Admin and cache the token.
+     */
     public void login() {
         final String url = baseURL + "/platform/login?userName={username}&password={password}";
         ResponseEntity<ShenYuResult> response = template.getForEntity(
@@ -129,68 +115,125 @@ public class AdminClient {
         Plugin.check(listPlugins());
     }
     
+    /**
+     * List all plugins.
+     *
+     * @return a list of {@link PluginDTO}s
+     */
     public List<PluginDTO> listPlugins() {
-        return list("/plugin", PAGINATED_PLUGINS_TYPE_REFERENCE);
-    }
-    
-    public List<SelectorDTO> listAllSelectors() {
-        return list("/selector", PAGINATED_SELECTORS_TYPE_REFERENCE);
-    }
-    
-    public List<RuleDTO> listAllRules() {
-        return list("/rule", PAGINATED_RULES_TYPE_REFERENCE);
-    }
-    
-    private <T extends ResourceDTO, OUT> List<OUT> list(String uri, TypeReference<PaginatedResources<T>> valueType, Mapper<T, OUT> map) {
-        String url = baseURL + uri + "?currentPage={cur}&pageSize={size}";
-        List<OUT> result = Lists.newArrayList();
+        List<PluginDTO> result = Lists.newArrayList();
         
         int cur = 1;
-        while (true) {
+        int total;
+        do {
             ResponseEntity<ShenYuResult> response = template.exchange(
-                    url,
+                    baseURL + "/plugin?currentPage={cur}&pageSize={page}",
                     HttpMethod.GET,
                     new HttpEntity<>(basicAuth),
                     ShenYuResult.class,
                     cur,
-                    10
+                    30
             );
             ShenYuResult rst = assertAndGet(response, "query success");
             
-            PaginatedResources<T> pagination = Assertions.assertDoesNotThrow(
-                    () -> mapper.readValue(rst.getData().traverse(), valueType),
+            PaginatedResources<PluginDTO> pagination = Assertions.assertDoesNotThrow(
+                    () -> mapper.readValue(rst.getData().traverse(), PAGINATED_PLUGINS_TYPE_REFERENCE),
                     "checking cast to PaginatedResources<T>"
             );
-            pagination.getDataList().forEach(e -> result.add(map.map(e)));
+            result.addAll(pagination.getDataList());
             
-            if (cur >= pagination.getPage().getTotalPage()) {
-                break;
-            }
-            cur++;
-        }
+            total = pagination.getPage().getTotalPage();
+        } while (++cur < total);
+        return result;
+    }
+    
+    /**
+     * List all existence selectors.
+     *
+     * @return a list of {@link SelectorDTO}s
+     */
+    public List<SelectorDTO> listAllSelectors() {
+        SelectorQueryCondition condition = SelectorQueryCondition.builder()
+                .switchStatus(true)
+                .build();
+        return list("/selector/list/search", condition, SEARCHED_SELECTORS_TYPE_REFERENCE, v -> v);
+    }
+    
+    /**
+     * List all existence rules.
+     *
+     * @return a list of {@link RuleDTO}s
+     */
+    public List<RuleDTO> listAllRules() {
+        RuleQueryCondition condition = RuleQueryCondition.builder()
+                .switchStatus(true)
+                .build();
+        return list("/rule/list/search", condition, SEARCHED_RULES_TYPE_REFERENCE, v -> v);
+    }
+    
+    private <T extends ResourceDTO, OUT> List<OUT> list(String uri, QueryCondition condition, TypeReference<SearchedResources<T>> valueType, Mapper<T, OUT> mapper) {
+        List<OUT> result = Lists.newArrayList();
+        
+        int curPage = 1;
+        int total;
+        
+        do {
+            SearchedResources<T> resources = search(uri, curPage, 20, condition, valueType);
+            resources.getList().stream()
+                    .map(mapper)
+                    .forEach(result::add);
+            total = resources.getPages();
+        } while (++curPage <= total);
+        
         return result;
     }
     
-    private <T extends ResourceDTO> List<T> list(String uri, TypeReference<PaginatedResources<T>> valueType) {
-        return list(uri, valueType, value -> value);
+    /**
+     * Fetch the selectors by the given conditions.
+     *
+     * @param keyword expected selectors included the word. return all if absent.
+     * @param plugins expected selectors under specified plugins. return all if absent.
+     * @return paginated info with  list of {@link SelectorDTO}s
+     */
+    public SearchedResources<SelectorDTO> searchSelectors(String keyword, String... plugins) {
+        SelectorQueryCondition condition = SelectorQueryCondition.builder()
+                .keyword(keyword)
+                .plugins(plugins)
+                .switchStatus(true)
+                .build();
+        return search("/selector/list/search", condition, SEARCHED_SELECTORS_TYPE_REFERENCE);
     }
     
-    public List<SelectorDTO> searchSelector(String keyword, String... plugins) {
+    /**
+     * Fetch the selectors by the given conditions.
+     *
+     * @param keyword expected selectors included the word. return all if absent.
+     * @param plugins expected selectors under specified plugins. return all if absent.
+     * @return paginated info with  list of {@link SelectorDTO}s
+     */
+    public SearchedResources<SelectorDTO> searchSelectors(String keyword, int page, int pageSize, String... plugins) {
         SelectorQueryCondition condition = SelectorQueryCondition.builder()
                 .keyword(keyword)
                 .plugins(plugins)
                 .switchStatus(true)
                 .build();
-        return search("/selector/list/search", condition, SEARCHED_SELECTORS_TYPE_REFERENCE).getList();
+        return search("/selector/list/search", page, pageSize, condition, SEARCHED_SELECTORS_TYPE_REFERENCE);
     }
     
-    public List<RuleDTO> searchRule(String keyword, String... selectors) {
+    /**
+     * Fetch the rules by the given conditions.
+     *
+     * @param keyword   expected selectors included the word. return all if absent.
+     * @param selectors expected selectors under specified plugins. return all if absent.
+     * @return paginated info with list of {@link RuleDTO}s
+     */
+    public SearchedResources<RuleDTO> searchRules(String keyword, String... selectors) {
         RuleQueryCondition condition = RuleQueryCondition.builder()
                 .keyword(keyword)
                 .selectors(selectors)
                 .switchStatus(true)
                 .build();
-        return search("/rule/list/search", condition, SEARCHED_RULES_TYPE_REFERENCE).getList();
+        return search("/rule/list/search", condition, SEARCHED_RULES_TYPE_REFERENCE);
     }
     
     private <T extends ResourceDTO> SearchedResources<T> search(String uri, QueryCondition condition, TypeReference<SearchedResources<T>> valueType) {
@@ -220,6 +263,9 @@ public class AdminClient {
         return dto;
     }
     
+    /**
+     * Create Rule.
+     */
     public RuleDTO create(RuleData rule) {
         RuleDTO dto = create("/rule", rule);
         Rules.INSTANCE.put(rule.getName(), dto.getId());
@@ -240,49 +286,76 @@ public class AdminClient {
         Assertions.assertEquals(200, rst.getCode(), "checking shenyu result code");
         Assertions.assertEquals("create success", rst.getMessage(), "checking shenyu result message");
         
-        ResourceDTO created = null;
+        
+        SearchedResources<?> searchedResources = null;
         if (data instanceof SelectorData) {
-            created = searchSelector(data.getName()).get(0);
+            searchedResources = searchSelectors(data.getName());
         } else if (data instanceof RuleData) {
-            created = searchRule(data.getName()).get(0);
+            searchedResources = searchRules(data.getName());
         }
+        Assertions.assertNotNull(searchedResources, "checking searchedResources object is non-null");
+        Assertions.assertEquals(1, searchedResources.getTotal(), "checking the total hits of searching");
+    
+        ResourceDTO created = searchedResources.getList().get(0);
         Assertions.assertNotNull(created, "checking created object is non-null");
         log.info("create resource({}) successful. name: {}, id: {}", data.getClass().getSimpleName(), data.getName(), created.getId());
         
         return (R) created;
     }
     
+    /**
+     * Delete selectors in batch.
+     *
+     * @param ids ID of selectors that needs to delete.
+     */
     public void deleteSelectors(List<String> ids) {
         delete("/selector/batch", ids);
     }
     
+    /**
+     * Delete selectors in batch.
+     *
+     * @param ids ID of selectors that needs to delete.
+     */
     public void deleteSelectors(String... ids) {
         delete("/selector/batch", Lists.newArrayList(ids));
     }
     
+    /**
+     * Delete rules in batch.
+     *
+     * @param ids ID of rules that needs to delete.
+     */
     public void deleteRules(String... ids) {
         delete("/rule/batch", Lists.newArrayList(ids));
     }
     
+    /**
+     * Delete all selectors
+     */
     public void deleteAllSelectors() {
         deleteAll("/selector/batch");
     }
     
+    /**
+     * Delete all rules under given id of selector.
+     *
+     * @param selectorId ID of selector
+     */
     public void deleteAllRules(String selectorId) {
-        List<String> ids = searchRule(null, selectorId).stream().map(RuleDTO::getId).collect(Collectors.toList());
+        List<String> ids = searchRules(null, selectorId).getList().stream().map(RuleDTO::getId).collect(Collectors.toList());
         deleteRules(ids.toArray(new String[]{}));
     }
     
     private void deleteAll(String uri) {
         Preconditions.checkArgument(uri.endsWith("/batch"), "uri[{}] must be end with '/batch'", uri);
         
-        String listAllResourcesUrl = uri.replace("/batch", "");
-        List<String> ids = list(listAllResourcesUrl, FAKE_VALUE_TYPE, FakeResourceDTO::getId);
+        String listAllResourcesUrl = uri.replace("/batch", "") + "/list/search";
+        List<String> ids = list(listAllResourcesUrl, QUERY_ALL, FAKE_VALUE_TYPE, FakeResourceDTO::getId);
         
         delete(uri, ids);
-        
-        List<FakeResourceDTO> result = list(listAllResourcesUrl, FAKE_VALUE_TYPE);
-        Assertions.assertTrue(result.isEmpty(), "checking whether resource list is empty after deleted");
+        List<FakeResourceDTO> result = list(listAllResourcesUrl, QUERY_ALL, FAKE_VALUE_TYPE, v -> v);
+        Assertions.assertEquals(0, result.size(), "resource list is empty after deleted");
     }
     
     private void delete(String uri, List<String> ids) {
@@ -300,10 +373,30 @@ public class AdminClient {
         log.info("delete resources, effected size: {}, effected rows: {}", ids.size(), ids);
     }
     
-    public <T extends ResourceDTO> T getResource(String uri, QueryCondition condition, TypeReference<SearchedResources<T>> valueType) {
-        SearchedResources<T> searchedResources = (SearchedResources<T>) search(uri, condition, valueType);
-        Assertions.assertEquals(1, searchedResources.getTotal(), "checking the total hits of searching");
-        return searchedResources.getList().get(0);
+    /**
+     * Fetch Selector by given id.
+     * @param id of selector that needs to fetch
+     * @return {@link SelectorDTO}
+     */
+    public SelectorDTO getSelector(String id) {
+        return getResource("/selector", id, SelectorDTO.class);
+    }
+    
+    private <T extends ResourceDTO> T getResource(String uri, String id, Class<T> valueType) {
+        ResponseEntity<ShenYuResult> response = template.exchange(baseURL + uri + "/{id}", HttpMethod.GET, new HttpEntity<>(basicAuth), ShenYuResult.class, id);
+        Assertions.assertEquals(HttpStatus.OK, response.getStatusCode(), "checking http status");
+    
+        ShenYuResult rst = response.getBody();
+        Assertions.assertNotNull(rst, "checking http response body");
+        
+        if (rst.getCode() == 500) {
+            Assertions.assertTrue(rst.getMessage().contains("selector is not existed"), "checking shenyu result message");
+            return null;
+        }
+    
+        Assertions.assertEquals("detail success", rst.getMessage(), "checking shenyu result message");
+        Assertions.assertEquals(200, rst.getCode(), "checking shenyu result code");
+        return Assertions.assertDoesNotThrow(() -> rst.toObject(valueType), "checking cast data to " + valueType.getSimpleName());
     }
     
     private ShenYuResult assertAndGet(ResponseEntity<ShenYuResult> response, String message) {
@@ -318,7 +411,7 @@ public class AdminClient {
     }
     
     @FunctionalInterface
-    interface Mapper<IN, OUT> {
-        OUT map(IN value);
+    interface Mapper<IN, OUT> extends Function<IN, OUT> {
+    
     }
 }
diff --git a/shenyu-e2e/shenyu-e2e-client/src/main/java/org/apache/shenyu/e2e/client/admin/model/data/SearchCondition.java b/shenyu-e2e/shenyu-e2e-client/src/main/java/org/apache/shenyu/e2e/client/admin/model/data/SearchCondition.java
index 7e13dd789..dfbe4d2c1 100644
--- a/shenyu-e2e/shenyu-e2e-client/src/main/java/org/apache/shenyu/e2e/client/admin/model/data/SearchCondition.java
+++ b/shenyu-e2e/shenyu-e2e-client/src/main/java/org/apache/shenyu/e2e/client/admin/model/data/SearchCondition.java
@@ -65,6 +65,24 @@ public class SearchCondition {
         private boolean switchStatus;
     }
     
+    public static QueryCondition QUERY_ALL = new QueryCondition() {
+    
+        @Override
+        public String getExcluded() {
+            return null;
+        }
+    
+        @Override
+        public String getKeyword() {
+            return null;
+        }
+    
+        @Override
+        public boolean isSwitchStatus() {
+            return true;
+        }
+    };
+    
     private int pageNum;
     
     private int pageSize;
diff --git a/shenyu-e2e/shenyu-e2e-client/src/main/java/org/apache/shenyu/e2e/client/admin/model/response/RuleDTO.java b/shenyu-e2e/shenyu-e2e-client/src/main/java/org/apache/shenyu/e2e/client/admin/model/response/RuleDTO.java
index 05d967325..abb908246 100644
--- a/shenyu-e2e/shenyu-e2e-client/src/main/java/org/apache/shenyu/e2e/client/admin/model/response/RuleDTO.java
+++ b/shenyu-e2e/shenyu-e2e-client/src/main/java/org/apache/shenyu/e2e/client/admin/model/response/RuleDTO.java
@@ -42,6 +42,8 @@ public class RuleDTO implements ResourceDTO {
     @JsonAlias("loged")
     private boolean logged;
     
+    private boolean enabled;
+    
     private String handle;
     
     private JsonNode ruleConditions;
diff --git a/shenyu-e2e/shenyu-e2e-client/src/main/java/org/apache/shenyu/e2e/client/admin/model/response/SearchedResources.java b/shenyu-e2e/shenyu-e2e-client/src/main/java/org/apache/shenyu/e2e/client/admin/model/response/SearchedResources.java
index 1ff81b3dd..caae64a24 100644
--- a/shenyu-e2e/shenyu-e2e-client/src/main/java/org/apache/shenyu/e2e/client/admin/model/response/SearchedResources.java
+++ b/shenyu-e2e/shenyu-e2e-client/src/main/java/org/apache/shenyu/e2e/client/admin/model/response/SearchedResources.java
@@ -29,7 +29,7 @@ public class SearchedResources<T extends ResourceDTO> {
     private int pageNum;
     private int pageSize;
     private int total;
-    private int page;
+    private int pages;
     
     private List<T> list;
     
diff --git a/shenyu-e2e/shenyu-e2e-client/src/main/java/org/apache/shenyu/e2e/matcher/RuleMatcher.java b/shenyu-e2e/shenyu-e2e-client/src/main/java/org/apache/shenyu/e2e/matcher/RuleMatcher.java
index d5e6e877d..9c22b662c 100644
--- a/shenyu-e2e/shenyu-e2e-client/src/main/java/org/apache/shenyu/e2e/matcher/RuleMatcher.java
+++ b/shenyu-e2e/shenyu-e2e-client/src/main/java/org/apache/shenyu/e2e/matcher/RuleMatcher.java
@@ -21,9 +21,16 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import lombok.SneakyThrows;
 import org.apache.shenyu.e2e.client.admin.model.data.RuleData;
 import org.apache.shenyu.e2e.client.admin.model.response.RuleDTO;
+import org.skyscreamer.jsonassert.JSONAssert;
+import org.skyscreamer.jsonassert.JSONCompareMode;
 
+import java.util.Objects;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.startsWith;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.hasProperty;
+import static org.hamcrest.Matchers.isEmptyOrNullString;
 import static org.hamcrest.Matchers.notNullValue;
 
 public class RuleMatcher {
@@ -36,7 +43,29 @@ public class RuleMatcher {
     
     @SneakyThrows
     public void matches(RuleDTO actual) {
+        String handle = actual.getHandle();
+        if (Objects.nonNull(expected.getHandle())) {
+            String expected = mapper.writer().writeValueAsString(this.expected.getHandle());
+            JSONAssert.assertEquals(expected, handle, JSONCompareMode.LENIENT);
+        } else {
+            assertThat(actual, hasProperty("handle", isEmptyOrNullString()));
+        }
+        
+        assertThat(actual, hasProperty("name", startsWith(expected.getName())));
+        assertThat(actual, hasProperty("selectorId", equalTo(expected.getSelectorId())));
+        assertThat(actual, hasProperty("matchMode", equalTo(Integer.parseInt(expected.getMatchMode().getId()))));
+        assertThat(actual, hasProperty("sort", equalTo(expected.getSort())));
+        assertThat(actual, hasProperty("logged", equalTo(expected.isLogged())));
+        assertThat(actual, hasProperty("enabled", equalTo(expected.isEnabled())));
+        
+        assertThat(actual, hasProperty("matchModeName", equalTo(expected.getMatchMode().alias())));
+        
+        assertThat(actual, hasProperty("dateCreated", notNullValue()));
+        assertThat(actual, hasProperty("dateUpdated", notNullValue()));
+    }
     
+    public static RuleMatcher verify(RuleData expected) {
+        return new RuleMatcher(expected);
     }
     
 }
diff --git a/shenyu-e2e/shenyu-e2e-client/src/test/java/org/apache/shenyu/e2e/client/admin/AdminClientTest.java b/shenyu-e2e/shenyu-e2e-client/src/test/java/org/apache/shenyu/e2e/client/admin/AdminClientTest.java
index fd1b44580..055c69ee7 100644
--- a/shenyu-e2e/shenyu-e2e-client/src/test/java/org/apache/shenyu/e2e/client/admin/AdminClientTest.java
+++ b/shenyu-e2e/shenyu-e2e-client/src/test/java/org/apache/shenyu/e2e/client/admin/AdminClientTest.java
@@ -17,7 +17,6 @@
 
 package org.apache.shenyu.e2e.client.admin;
 
-import com.fasterxml.jackson.core.JsonProcessingException;
 import com.google.common.collect.Lists;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.shenyu.e2e.client.admin.model.MatchMode;
@@ -32,12 +31,15 @@ import org.apache.shenyu.e2e.client.admin.model.handle.DivideRuleHandle;
 import org.apache.shenyu.e2e.client.admin.model.handle.Upstreams;
 import org.apache.shenyu.e2e.client.admin.model.handle.Upstreams.Upstream;
 import org.apache.shenyu.e2e.client.admin.model.response.RuleDTO;
+import org.apache.shenyu.e2e.client.admin.model.response.SearchedResources;
 import org.apache.shenyu.e2e.client.admin.model.response.SelectorDTO;
+import org.apache.shenyu.e2e.matcher.RuleMatcher;
 import org.apache.shenyu.e2e.matcher.SelectorMatcher;
 import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.MethodOrderer;
 import org.junit.jupiter.api.Order;
 import org.junit.jupiter.api.Test;
@@ -50,18 +52,20 @@ import org.testcontainers.junit.jupiter.Testcontainers;
 import java.time.Duration;
 import java.util.List;
 import java.util.Properties;
-import java.util.stream.Collectors;
 
 @Slf4j
 @Testcontainers
 @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
 public class AdminClientTest {
     static AdminClient client;
-    
+
     static GenericContainer<?> container = new GenericContainer<>("shenyu/admin:latest")
             .withExposedPorts(9095)
             .withLogConsumer(new Slf4jLogConsumer(log));
     
+    SelectorDTO selector;
+    RuleDTO rule;
+    
     @BeforeAll
     static void setup() {
         container.start();
@@ -98,10 +102,10 @@ public class AdminClientTest {
                 )
                 .sort(1)
                 .build();
-        SelectorDTO selector = client.create(selectorData);
-        
+        selector = client.create(selectorData);
+    
         RuleData ruleData = RuleData.builder()
-                .name("my-rule")
+                .name("test-create-rule")
                 .enabled(true)
                 .logged(true)
                 .handle(DivideRuleHandle.builder()
@@ -122,14 +126,13 @@ public class AdminClientTest {
                         .operator(Operator.EQUAL)
                         .build()))
                 .build();
-        client.create(ruleData);
+        rule = client.create(ruleData);
     }
     
     @Test
-    @Order(0)
-    void testCreateSelector() throws JsonProcessingException {
+    void testCreateSelector() {
         SelectorData selectorData = SelectorData.builder()
-                .name("my-plugin-divide")
+                .name("test-create-selector")
                 .plugin(Plugin.DIVIDE)
                 .type(SelectorType.CUSTOM)
                 .matchMode(MatchMode.AND)
@@ -144,12 +147,17 @@ public class AdminClientTest {
                 .build();
         SelectorDTO selector = client.create(selectorData);
         
-        List<SelectorDTO> selectors = client.searchSelector(selector.getName());
+        List<SelectorDTO> selectors = client.searchSelectors(selector.getName()).getList();
         Assertions.assertThat(selectors.size()).isEqualTo(1);
         SelectorMatcher.verify(selectorData).matches(selector);
-        
-        RuleDTO ruleDTO = client.create(RuleData.builder()
-                .name("my-rule")
+    
+        client.deleteAllRules(selector.getId());
+    }
+    
+    @Test
+    void testCreateRule() {
+        RuleData ruleData = RuleData.builder()
+                .name("test-create-rule")
                 .enabled(true)
                 .logged(true)
                 .handle(DivideRuleHandle.builder()
@@ -169,25 +177,90 @@ public class AdminClientTest {
                         .paramName("/")
                         .operator(Operator.EQUAL)
                         .build()))
-                .build());
+                .build();
+        RuleDTO rule = client.create(ruleData);
+        RuleMatcher.verify(ruleData).matches(rule);
     }
     
     @Test
-    void testDeleteAllSelectors() {
-        client.deleteAllSelectors();
-        List<SelectorDTO> selectors = client.listAllSelectors();
-        Assertions.assertThat(selectors).isEmpty();
+    @Order(200)
+    void testListSelectors() {
+        for (int i = 0; i <= 20; i++) {
+            client.create(SelectorData.builder()
+                    .name("test-list-selectors")
+                    .plugin(Plugin.DIVIDE)
+                    .type(SelectorType.CUSTOM)
+                    .matchMode(MatchMode.AND)
+                    .logged(true)
+                    .enabled(true)
+                    .continued(true)
+                    .handle(Upstreams.builder().add(Upstream.builder().upstreamUrl("httpbin.org:80").build()).build())
+                    .conditionList(
+                            Lists.newArrayList(Condition.builder().paramType(ParamType.URI).operator(Operator.MATCH).paramName("/").paramValue("/**").build())
+                    )
+                    .sort(1)
+                    .build());
+        }
+        SearchedResources<SelectorDTO> searchedResources = client.searchSelectors(null);
+        Assertions.assertThat(searchedResources.getTotal()).isGreaterThan(20);
+        Assertions.assertThat(client.listAllSelectors().size()).isEqualTo(searchedResources.getTotal());
     }
     
     @Test
-    void testListRules() {
-        List<String> list = client.listAllRules()
-                .stream()
-                .map(RuleDTO::getId)
-                .collect(Collectors.toList());
-        client.deleteRules(list.toArray(new String[]{}));
+    @Order(201)
+    // depends on {@link #testListSelectors}
+    void testSearchSelectors() {
+        SearchedResources<SelectorDTO> searched = client.searchSelectors("test-list-selectors");
+        Assertions.assertThat(searched.getTotal()).isGreaterThanOrEqualTo(20);
+        Assertions.assertThat(searched.getPages()).isGreaterThanOrEqualTo(2);
+        Assertions.assertThat(searched.getPageNum()).isEqualTo(1);
+        Assertions.assertThat(searched.getPageSize()).isEqualTo(10);
         
-        Assertions.assertThat(client.listAllRules()).isEmpty();
+        searched = client.searchSelectors("test-list-selectors", 2, 10);
+        Assertions.assertThat(searched.getTotal()).isGreaterThanOrEqualTo(20);
+        Assertions.assertThat(searched.getPages()).isGreaterThanOrEqualTo(2);
+        Assertions.assertThat(searched.getPageNum()).isEqualTo(2);
+        Assertions.assertThat(searched.getPageSize()).isEqualTo(10);
+    }
+    
+    @Test
+    void searchRules() {
+        SearchedResources<RuleDTO> searched = client.searchRules(rule.getName());
+        Assertions.assertThat(searched.getPageNum()).isEqualTo(1);
+        Assertions.assertThat(searched.getPageSize()).isEqualTo(10);
+        Assertions.assertThat(searched.getPages()).isEqualTo(1);
+        Assertions.assertThat(searched.getTotal()).isEqualTo(1);
+    
+        searched = client.searchRules(rule.getName(), selector.getId());
+        Assertions.assertThat(searched.getPageNum()).isEqualTo(1);
+        Assertions.assertThat(searched.getPageSize()).isEqualTo(10);
+        Assertions.assertThat(searched.getPages()).isEqualTo(1);
+        Assertions.assertThat(searched.getTotal()).isEqualTo(1);
+    
+        searched = client.searchRules(null, selector.getId());
+        Assertions.assertThat(searched.getPageNum()).isEqualTo(1);
+        Assertions.assertThat(searched.getPageSize()).isEqualTo(10);
+        Assertions.assertThat(searched.getPages()).isEqualTo(1);
+        Assertions.assertThat(searched.getTotal()).isEqualTo(1);
+    
+        searched = client.searchRules(null, "fake");
+        Assertions.assertThat(searched.getPageNum()).isEqualTo(1);
+        Assertions.assertThat(searched.getPageSize()).isEqualTo(10);
+        Assertions.assertThat(searched.getPages()).isEqualTo(0);
+        Assertions.assertThat(searched.getTotal()).isEqualTo(0);
+    }
+    
+    @AfterEach
+    void deleteResources() {
+        client.deleteSelectors(selector.getId());
+        Assertions.assertThat(client.getSelector(selector.getId())).isNull();
+        Assertions.assertThat(client.searchSelectors(selector.getName()).getTotal()).isZero();
+        Assertions.assertThat(client.searchRules(null, selector.getId()).getTotal()).isZero();
+    }
+    
+    @AfterAll
+    static void testDeleteAllSelectors() {
+        client.deleteAllSelectors();
     }
     
 }
diff --git a/shenyu-e2e/shenyu-e2e-engine/src/test/java/org/apache/shenyu/e2e/engine/ShenYuEngineTest.java b/shenyu-e2e/shenyu-e2e-engine/src/test/java/org/apache/shenyu/e2e/engine/ShenYuEngineTest.java
index 4b480950d..43eb1e90a 100644
--- a/shenyu-e2e/shenyu-e2e-engine/src/test/java/org/apache/shenyu/e2e/engine/ShenYuEngineTest.java
+++ b/shenyu-e2e/shenyu-e2e-engine/src/test/java/org/apache/shenyu/e2e/engine/ShenYuEngineTest.java
@@ -20,6 +20,7 @@ package org.apache.shenyu.e2e.engine;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import org.apache.shenyu.e2e.client.admin.AdminClient;
 import org.apache.shenyu.e2e.client.admin.model.data.SearchCondition.SelectorQueryCondition;
+import org.apache.shenyu.e2e.client.admin.model.response.SearchedResources;
 import org.apache.shenyu.e2e.client.admin.model.response.SelectorDTO;
 import org.apache.shenyu.e2e.engine.annotation.ShenYuTest;
 import org.apache.shenyu.e2e.engine.annotation.ShenYuTest.Parameter;
@@ -38,7 +39,6 @@ import org.testcontainers.containers.wait.strategy.HttpWaitStrategy;
 import java.io.File;
 import java.net.URL;
 import java.time.Duration;
-import java.util.List;
 
 @Disabled
 @ShenYuTest(
@@ -91,7 +91,7 @@ public class ShenYuEngineTest {
                 .switchStatus(true)
                 .build();
         
-        List<SelectorDTO> divide = client.searchSelector("divide");
+        SearchedResources<SelectorDTO> divide = client.searchSelectors("divide");
     }
     
     @Test