浏览代码

单元测试

zhangji 2 年之前
父节点
当前提交
8691833c97
共有 22 个文件被更改,包括 2134 次插入1 次删除
  1. 0 1
      jetlinks-components/common-component/src/main/java/org/jetlinks/community/micrometer/MeterRegistryManager.java
  2. 1 0
      jetlinks-components/pom.xml
  3. 130 0
      jetlinks-components/test-component/pom.xml
  4. 29 0
      jetlinks-components/test-component/src/main/java/org/jetlinks/community/test/spring/TestJetLinksApplication.java
  5. 86 0
      jetlinks-components/test-component/src/main/java/org/jetlinks/community/test/spring/TestJetLinksController.java
  6. 52 0
      jetlinks-components/test-component/src/main/java/org/jetlinks/community/test/utils/ContainerUtils.java
  7. 297 0
      jetlinks-components/test-component/src/main/java/org/jetlinks/community/test/web/MockReactiveRestServiceServer.java
  8. 83 0
      jetlinks-components/test-component/src/main/java/org/jetlinks/community/test/web/TestAuthentication.java
  9. 36 0
      jetlinks-components/test-component/src/test/java/org/jetlinks/community/test/utils/ContainerUtilsTest.java
  10. 7 0
      jetlinks-manager/authentication-manager/pom.xml
  11. 22 0
      jetlinks-manager/authentication-manager/src/test/java/org/jetlinks/community/auth/AuthenticationTestConfiguration.java
  12. 88 0
      jetlinks-manager/authentication-manager/src/test/java/org/jetlinks/community/auth/relation/UserRelationObjectProviderTest.java
  13. 151 0
      jetlinks-manager/authentication-manager/src/test/java/org/jetlinks/community/auth/service/OrganizationServiceTest.java
  14. 34 0
      jetlinks-manager/authentication-manager/src/test/java/org/jetlinks/community/auth/service/terms/ThirdPartyUserTermBuilderTest.java
  15. 73 0
      jetlinks-manager/authentication-manager/src/test/java/org/jetlinks/community/auth/web/AuthorizationSettingDetailControllerTest.java
  16. 266 0
      jetlinks-manager/authentication-manager/src/test/java/org/jetlinks/community/auth/web/MenuControllerTest.java
  17. 201 0
      jetlinks-manager/authentication-manager/src/test/java/org/jetlinks/community/auth/web/OrganizationControllerTest.java
  18. 81 0
      jetlinks-manager/authentication-manager/src/test/java/org/jetlinks/community/auth/web/RoleControllerTest.java
  19. 69 0
      jetlinks-manager/authentication-manager/src/test/java/org/jetlinks/community/auth/web/ThirdPartyUserControllerTest.java
  20. 121 0
      jetlinks-manager/authentication-manager/src/test/java/org/jetlinks/community/auth/web/UserDetailControllerTest.java
  21. 82 0
      jetlinks-manager/authentication-manager/src/test/java/org/jetlinks/community/auth/web/UserSettingControllerTest.java
  22. 225 0
      jetlinks-manager/authentication-manager/src/test/java/org/jetlinks/community/auth/web/WebFluxUserControllerTest.java

+ 0 - 1
jetlinks-components/common-component/src/main/java/org/jetlinks/community/micrometer/MeterRegistryManager.java

@@ -23,7 +23,6 @@ public class MeterRegistryManager {
 
 
     private Map<String, MeterRegistry> meterRegistryMap = new ConcurrentHashMap<>();
     private Map<String, MeterRegistry> meterRegistryMap = new ConcurrentHashMap<>();
 
 
-    @Autowired
     private List<MeterRegistrySupplier> suppliers;
     private List<MeterRegistrySupplier> suppliers;
 
 
     private MeterRegistry createMeterRegistry(String metric, String... tagKeys) {
     private MeterRegistry createMeterRegistry(String metric, String... tagKeys) {

+ 1 - 0
jetlinks-components/pom.xml

@@ -27,6 +27,7 @@
         <module>script-component</module>
         <module>script-component</module>
         <module>protocol-component</module>
         <module>protocol-component</module>
         <module>relation-component</module>
         <module>relation-component</module>
+        <module>test-component</module>
     </modules>
     </modules>
 
 
     <artifactId>jetlinks-components</artifactId>
     <artifactId>jetlinks-components</artifactId>

+ 130 - 0
jetlinks-components/test-component/pom.xml

@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>jetlinks-components</artifactId>
+        <groupId>org.jetlinks.community</groupId>
+        <version>2.0.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <description>测试基础模块,引入后请设置scope为test</description>
+
+    <artifactId>test-component</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>org.jetlinks</groupId>
+            <artifactId>rule-engine-support</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.jetlinks</groupId>
+            <artifactId>jetlinks-supports</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-aspects</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+            <!--            <scope>test</scope>-->
+        </dependency>
+
+        <dependency>
+            <groupId>org.hswebframework.web</groupId>
+            <artifactId>hsweb-commons-crud</artifactId>
+            <version>${hsweb.framework.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.hswebframework.web</groupId>
+            <artifactId>hsweb-starter</artifactId>
+            <version>${hsweb.framework.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-webflux</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-r2dbc</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.r2dbc</groupId>
+            <artifactId>r2dbc-h2</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.r2dbc</groupId>
+            <artifactId>r2dbc-postgresql</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-junit-jupiter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>testcontainers</artifactId>
+            <version>1.15.3</version>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>junit-jupiter</artifactId>
+            <version>1.15.3</version>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>io.lettuce</groupId>
+            <artifactId>lettuce-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>uk.co.jemos.podam</groupId>
+            <artifactId>podam</artifactId>
+            <version>7.2.7.RELEASE</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 29 - 0
jetlinks-components/test-component/src/main/java/org/jetlinks/community/test/spring/TestJetLinksApplication.java

@@ -0,0 +1,29 @@
+package org.jetlinks.community.test.spring;
+
+import org.hswebframework.web.crud.annotation.EnableEasyormRepository;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
+import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration;
+import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+@SpringBootApplication(
+    scanBasePackages = {
+        "org.jetlinks.community",
+    },
+    exclude = {
+        DataSourceAutoConfiguration.class,
+        KafkaAutoConfiguration.class,
+        RabbitAutoConfiguration.class,
+        ElasticsearchRestClientAutoConfiguration.class,
+        ElasticsearchDataAutoConfiguration.class
+    }
+)
+@EnableEasyormRepository("org.jetlinks.**.entity")
+public class TestJetLinksApplication {
+
+
+}

+ 86 - 0
jetlinks-components/test-component/src/main/java/org/jetlinks/community/test/spring/TestJetLinksController.java

@@ -0,0 +1,86 @@
+package org.jetlinks.community.test.spring;
+
+import org.hswebframework.web.authorization.Authentication;
+import org.hswebframework.web.authorization.ReactiveAuthenticationHolder;
+import org.hswebframework.web.authorization.ReactiveAuthenticationSupplier;
+import org.hswebframework.web.crud.configuration.EasyormConfiguration;
+import org.hswebframework.web.crud.configuration.R2dbcSqlExecutorConfiguration;
+import org.jetlinks.community.test.utils.ContainerUtils;
+import org.jetlinks.community.test.web.TestAuthentication;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
+import org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration;
+import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
+import org.springframework.boot.autoconfigure.r2dbc.R2dbcTransactionManagerAutoConfiguration;
+import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration;
+import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.reactive.server.WebTestClient;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import reactor.core.publisher.Mono;
+
+@ImportAutoConfiguration({
+    EasyormConfiguration.class,
+    R2dbcSqlExecutorConfiguration.class, R2dbcAutoConfiguration.class,
+    R2dbcTransactionManagerAutoConfiguration.class, TransactionAutoConfiguration.class,
+    WebClientAutoConfiguration.class, RedisAutoConfiguration.class, RedisReactiveAutoConfiguration.class
+})
+@EnableTransactionManagement(proxyTargetClass = true)
+@EnableAutoConfiguration
+@EnableAspectJAutoProxy(proxyTargetClass = true)
+@Testcontainers
+@ComponentScan("org.jetlinks.community")
+public class TestJetLinksController {
+
+    static {
+
+        System.setProperty("spring.r2dbc.url", "r2dbc:h2:mem:///./data/h2db/jetlinks");
+        System.setProperty("spring.r2dbc.username", "sa");
+
+    }
+
+    @Container
+    static GenericContainer<?> redis = ContainerUtils.newRedis();
+
+    @Autowired
+    protected WebTestClient client;
+
+    @BeforeAll
+    static void initAll() {
+        System.setProperty("spring.redis.port", String.valueOf(redis.getMappedPort(6379)));
+        System.setProperty("spring.redis.host", "127.0.0.1");
+    }
+
+    @BeforeEach
+    void init() {
+        ReactiveAuthenticationHolder.setSupplier(new ReactiveAuthenticationSupplier() {
+            @Override
+            public Mono<Authentication> get() {
+                TestAuthentication authentication = new TestAuthentication("test");
+                initAuth(authentication);
+                return Mono.just(authentication);
+            }
+
+            @Override
+            public Mono<Authentication> get(String userId) {
+                TestAuthentication authentication = new TestAuthentication(userId);
+                initAuth(authentication);
+                return Mono.just(authentication);
+            }
+        });
+    }
+
+    protected void initAuth(TestAuthentication authentication) {
+
+    }
+}

+ 52 - 0
jetlinks-components/test-component/src/main/java/org/jetlinks/community/test/utils/ContainerUtils.java

@@ -0,0 +1,52 @@
+package org.jetlinks.community.test.utils;
+
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.utility.DockerImageName;
+
+public class ContainerUtils {
+
+    public static GenericContainer<?> newRedis() {
+        return new GenericContainer<>(DockerImageName.parse("redis:5"))
+            .withEnv("TZ", "Asia/Shanghai")
+            .withExposedPorts(6379)
+            .waitingFor(Wait.forListeningPort());
+    }
+
+    public static GenericContainer<?> newMysql() {
+        return new GenericContainer<>(DockerImageName.parse("mysql:" + System.getProperty("test.mysql.version", "5.7")))
+            .withEnv("TZ", "Asia/Shanghai")
+            .withEnv("MYSQL_ROOT_PASSWORD", "password")
+            .withEnv("MYSQL_DATABASE", "jetlinks")
+            .withCommand("--character-set-server=utf8mb4")
+            .withExposedPorts(3306)
+            .waitingFor(Wait.forListeningPort());
+    }
+
+    public static GenericContainer<?> newPostgresql() {
+        return new GenericContainer<>(DockerImageName.parse("postgres:" + System.getProperty("test.postgres.version", "11")) + "-alpine")
+            .withEnv("TZ", "Asia/Shanghai")
+            .withEnv("POSTGRES_PASSWORD", "password")
+            .withEnv("POSTGRES_DB", "jetlinks")
+            .withCommand("postgres", "-c", "max_connections=500")
+            .withExposedPorts(5432)
+            .waitingFor(Wait.forListeningPort());
+    }
+
+    public static GenericContainer<?> newElasticSearch() {
+        return newElasticSearch(System.getProperty("test.elasticsearch.version", "7.9.0"));
+    }
+
+    public static GenericContainer<?> newElasticSearch(String version) {
+        return new GenericContainer<>(DockerImageName.parse("elasticsearch:" + version))
+            .withEnv("TZ", "Asia/Shanghai")
+            .withEnv("ES_JAVA_OPTS","-Djava.net.preferIPv4Stack=true -Xms1g -Xmx1g")
+//            .withSharedMemorySize(DataSize.ofGigabytes(1).toBytes())
+            .withEnv("transport.host", "0.0.0.0")
+            .withEnv("discovery.type", "single-node")
+//            .withEnv("bootstrap.memory_lock", "true")
+            .withExposedPorts(9200)
+            .waitingFor(Wait.forHttp("/").forStatusCode(200));
+    }
+
+}

+ 297 - 0
jetlinks-components/test-component/src/main/java/org/jetlinks/community/test/web/MockReactiveRestServiceServer.java

@@ -0,0 +1,297 @@
+package org.jetlinks.pro.test.web;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.time.Duration;
+import java.util.function.Function;
+
+import lombok.SneakyThrows;
+import org.apache.commons.compress.utils.IOUtils;
+import org.springframework.test.web.client.*;
+import reactor.core.Exceptions;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.core.io.buffer.DataBufferUtils;
+import org.springframework.core.io.buffer.DefaultDataBufferFactory;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseCookie;
+import org.springframework.http.ResponseCookie.ResponseCookieBuilder;
+import org.springframework.http.client.reactive.ClientHttpConnector;
+import org.springframework.http.client.reactive.ClientHttpRequest;
+import org.springframework.http.client.reactive.ClientHttpResponse;
+import org.springframework.lang.NonNull;
+import org.springframework.mock.http.client.reactive.MockClientHttpRequest;
+import org.springframework.mock.http.client.reactive.MockClientHttpResponse;
+import org.springframework.test.web.client.match.MockRestRequestMatchers;
+import org.springframework.test.web.client.response.MockRestResponseCreators;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+
+/**
+ * A mock REST service server that can be used to verify requests from {@link WebClient} objects, and
+ * stub mock responses to provide to the client.
+ * <p>
+ * This acts as a drop-in replacement for {@link MockRestServiceServer} that provides support for reactive
+ * {@link WebClient} objects. While supporting a reactive client API underneath, the majority of this implementation
+ * works with the same components that {@link MockRestServiceServer} does.
+ * <p>
+ * This is relatively lightweight API to use, so can be initialized per test case if required. This does not open
+ * any network sockets.
+ * <p>
+ * Instances of this server can be created by calling either
+ * {@link #createServer()}, {@link #createServer(boolean)}, or {@link #createServer(RequestExpectationManager)}.
+ * {@link WebClient} instances that will communicate with this server can then be created using the
+ * {@link #createWebClient()} or {@link #createWebClientBuilder()} methods, respectively.
+ *
+ * @author Ashley Scopes
+ * @see RequestMatcher
+ * @see ResponseCreator
+ * @see MockRestRequestMatchers
+ * @see MockRestResponseCreators
+ */
+public final class MockReactiveRestServiceServer {
+	private final RequestExpectationManager expectationManager;
+    static DefaultDataBufferFactory bufferFactory=new DefaultDataBufferFactory();
+	/**
+	 * Initialize the mock server with the desired expectation manager to use.
+	 *
+	 * @param expectationManager the expectation manager to use for requests.
+	 */
+	private MockReactiveRestServiceServer(@NonNull RequestExpectationManager expectationManager) {
+		this.expectationManager = expectationManager;
+	}
+
+	/**
+	 * Register an expectation for a request to occur exactly once. This is an alias for calling
+	 * {@link #expect(ExpectedCount, RequestMatcher)} with {@link ExpectedCount#once()} as the first
+	 * parameter.
+	 *
+	 * @param requestMatcher the request matcher to use.
+	 * @return the response actions builder to use to define how to respond to such requests.
+	 */
+	@NonNull
+	public ResponseActions expect(@NonNull RequestMatcher requestMatcher) {
+		return expect(ExpectedCount.once(), requestMatcher);
+	}
+
+	/**
+	 * Register an expectation for a request to occur a given number of times.
+	 *
+	 * @param count   the number of times the request should be made.
+	 * @param matcher the matcher for the request.
+	 * @return the response actions builder to use to define how to respond to such requests.
+	 */
+	@NonNull
+	public ResponseActions expect(@NonNull ExpectedCount count, @NonNull RequestMatcher matcher) {
+		return this.expectationManager.expectRequest(count, matcher);
+	}
+
+	/**
+	 * Verify that the expected requests have been performed. This is equivalent to calling
+	 * {@link #verify(Duration)} with {@link Duration#ZERO}.
+	 *
+	 * @throws AssertionError if the requests have not been performed as expected.
+	 */
+	public void verify() {
+		this.expectationManager.verify();
+	}
+
+	/**
+	 * Verify that the expected results get performed within the given timeout.
+	 *
+	 * @param timeout the maximum time to wait before failing.
+	 * @throws AssertionError if the request times out before the expected requests occur.
+	 */
+	@NonNull
+	public void verify(@NonNull Duration timeout) {
+		this.expectationManager.verify();
+	}
+
+	/**
+	 * Remove all expectations from this mock server.
+	 */
+	public void reset() {
+		this.expectationManager.reset();
+	}
+
+	/**
+	 * Create a new {@link WebClient} builder that is bound to this mock server.
+	 *
+	 * @return a {@code WebClient} builder that will pipe requests through this mock server instance.
+	 */
+	@NonNull
+	public WebClient.Builder createWebClientBuilder() {
+		return WebClient
+				.builder()
+				.clientConnector(new MockClientHttpConnector());
+	}
+
+	/**
+	 * Create a new {@link WebClient} that is bound to this mock server.
+	 *
+	 * @return a {@code WebClient} with default settings that pipes requests through this mock server instance.
+	 */
+	@NonNull
+	public WebClient createWebClient() {
+		return createWebClientBuilder().build();
+	}
+
+	/**
+	 * Create a new mock reactive REST service server with the given expectation manager.
+	 *
+	 * @param manager the expectation manager to use.
+	 * @return the new mock reactive REST service server.
+	 */
+	@NonNull
+	public static MockReactiveRestServiceServer createServer(@NonNull RequestExpectationManager manager) {
+		return new MockReactiveRestServiceServer(manager);
+	}
+
+	/**
+	 * Create a new mock reactive REST service server.
+	 *
+	 * @param ignoreRequestOrder true to ignore the order of requests, or false to require requests to
+	 *                           be performed in the order that they are registered.
+	 * @return the new mock reactive REST service server.
+	 */
+	@NonNull
+	public static MockReactiveRestServiceServer createServer(boolean ignoreRequestOrder) {
+		RequestExpectationManager manager = ignoreRequestOrder
+				? new UnorderedRequestExpectationManager()
+				: new SimpleRequestExpectationManager();
+
+		return createServer(manager);
+	}
+
+	/**
+	 * Create a new mock reactive REST service server that requires requests to be performed in the order that they
+	 * are registered.
+	 *
+	 * @return the new mock reactive REST service server to use.
+	 */
+	@NonNull
+	public static MockReactiveRestServiceServer createServer() {
+		return createServer(false);
+	}
+
+	/**
+	 * Internal mock connector for {@link WebClient} instances. This deals with converting reactive
+	 * {@link org.springframework.mock.http.client.reactive.MockClientHttpRequest} objects to non-reactive
+	 * {@link org.springframework.mock.http.client.MockClientHttpRequest} objects that the
+	 * {@link MockRestServiceServer} internals are compatible with. This also will handle converting non-reactive
+	 * {@link org.springframework.mock.http.client.MockClientHttpResponse} objects back to reactive
+	 * {@link org.springframework.mock.http.client.reactive.MockClientHttpResponse} objects for the response object
+	 * to use internally.
+	 *
+	 * @author Ashley Scopes
+	 */
+	private final class MockClientHttpConnector implements ClientHttpConnector {
+		@NonNull
+		public Mono<ClientHttpResponse> connect(
+				@NonNull HttpMethod method, @NonNull URI uri,
+				@NonNull Function<? super ClientHttpRequest, Mono<Void>> requestCallback) {
+
+			MockClientHttpRequest request = new MockClientHttpRequest(method, uri);
+
+			return Mono
+					.defer(() -> requestCallback.apply(request))
+					.thenReturn(request)
+					.flatMap(this::createRequest)
+					.map(this::performRequest)
+					.map(this::convertResponse)
+					.checkpoint("Request to " + method + " " + uri + " [MockReactiveRestServiceServer]")
+					.onErrorMap(ex -> ex.getCause() instanceof AssertionError
+							? Exceptions.bubble(ex.getCause())
+							: ex);
+		}
+
+		@NonNull
+		private Mono<org.springframework.http.client.ClientHttpRequest> createRequest(
+				@NonNull MockClientHttpRequest reactiveRequest) {
+
+			org.springframework.mock.http.client.MockClientHttpRequest proceduralRequest =
+					new org.springframework.mock.http.client.MockClientHttpRequest(
+							reactiveRequest.getMethod(), reactiveRequest.getURI());
+
+			proceduralRequest.getHeaders().putAll(reactiveRequest.getHeaders());
+
+			return DataBufferUtils
+					.join(reactiveRequest.getBody())
+					.doOnNext(dataBuffer -> {
+						byte[] arr = new byte[1024];
+						int c;
+
+						try {
+							InputStream input = dataBuffer.asInputStream();
+							OutputStream output = proceduralRequest.getBody();
+
+							while ((c = input.read(arr)) != -1) {
+								output.write(arr, 0, c);
+							}
+						}
+						catch (IOException ex) {
+							throw new RuntimeException(ex);
+						}
+					})
+					.thenReturn(proceduralRequest);
+		}
+
+		@NonNull
+        @SneakyThrows
+		private org.springframework.http.client.ClientHttpResponse performRequest(
+				@NonNull org.springframework.http.client.ClientHttpRequest proceduralRequest) {
+
+				return MockReactiveRestServiceServer
+						.this
+						.expectationManager
+						.validateRequest(proceduralRequest);
+
+		}
+
+
+		@NonNull
+		private ClientHttpResponse convertResponse(
+				@NonNull org.springframework.http.client.ClientHttpResponse proceduralResponse) {
+
+			int status = -1;
+			String statusMessage = "Could not read response status";
+			HttpHeaders headers = null;
+
+			try {
+				status = proceduralResponse.getRawStatusCode();
+				statusMessage = proceduralResponse.getStatusText();
+				headers = proceduralResponse.getHeaders();
+				DataBuffer responseBody = bufferFactory
+						.wrap(IOUtils.toByteArray(proceduralResponse.getBody()));
+
+				MockClientHttpResponse reactiveResponse = new MockClientHttpResponse(status);
+				reactiveResponse.getHeaders().putAll(headers);
+				MultiValueMap<String, ResponseCookie> cookies = reactiveResponse.getCookies();
+
+				headers.getOrEmpty(HttpHeaders.SET_COOKIE)
+						.stream()
+						.filter(cookie -> cookie.contains("="))
+						.map(cookie -> ResponseCookie.fromClientResponse(
+								cookie.substring(0, cookie.indexOf('=')),
+								cookie.substring(cookie.indexOf('=') + 1)
+						))
+						.map(ResponseCookieBuilder::build)
+						.forEach(cookie -> cookies.add(cookie.getName(), cookie));
+
+				reactiveResponse.setBody(Flux.just(responseBody));
+
+				return reactiveResponse;
+			}
+			catch (IOException ex) {
+				throw new WebClientResponseException(status, statusMessage, headers, null, null);
+			}
+		}
+	}
+}

+ 83 - 0
jetlinks-components/test-component/src/main/java/org/jetlinks/community/test/web/TestAuthentication.java

@@ -0,0 +1,83 @@
+package org.jetlinks.community.test.web;
+
+import lombok.Getter;
+import org.hswebframework.web.authorization.Authentication;
+import org.hswebframework.web.authorization.Dimension;
+import org.hswebframework.web.authorization.Permission;
+import org.hswebframework.web.authorization.User;
+import org.hswebframework.web.authorization.simple.SimpleDimension;
+import org.hswebframework.web.authorization.simple.SimpleDimensionType;
+import org.hswebframework.web.authorization.simple.SimplePermission;
+import org.hswebframework.web.authorization.simple.SimpleUser;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.BiPredicate;
+import java.util.function.Predicate;
+
+@Getter
+public class TestAuthentication implements Authentication {
+
+    private final User user;
+
+    private final List<Dimension> dimensions = new ArrayList<>();
+    private final List<Permission> permissions = new ArrayList<>();
+
+    public TestAuthentication(String userId) {
+        this.user = new SimpleUser(userId, userId, "test", "test", null);
+
+    }
+
+    public TestAuthentication addDimension(String type, String id) {
+        return addDimension(type,id,null);
+    }
+
+    public TestAuthentication addDimension(String type, String id,Map<String,Object> options) {
+        dimensions.add(SimpleDimension.of(id, type, SimpleDimensionType.of(type), options));
+        return this;
+    }
+
+
+    public TestAuthentication addTenant(String tenantId,boolean admin) {
+        Map<String,Object> options = new HashMap<>();
+        options.put("tenantId",tenantId);
+        options.put("admin",admin);
+        dimensions.add(SimpleDimension.of(tenantId, tenantId, SimpleDimensionType.of("tenantMember"), options));
+        return this;
+    }
+
+    public TestAuthentication addPermission(String id, String... action) {
+        permissions.add(SimplePermission.builder()
+            .id(id)
+            .name(id).actions(new HashSet<>(Arrays.asList(action)))
+            .build());
+        return this;
+    }
+
+    @Override
+    public <T extends Serializable> Optional<T> getAttribute(String name) {
+        return Optional.empty();
+    }
+
+    @Override
+    public Map<String, Serializable> getAttributes() {
+        return null;
+    }
+
+    @Override
+    public Authentication merge(Authentication source) {
+        return this;
+    }
+
+    @Override
+    public Authentication copy(BiPredicate<Permission, String> permissionFilter,
+                               Predicate<Dimension> dimension) {
+        return this;
+    }
+}

+ 36 - 0
jetlinks-components/test-component/src/test/java/org/jetlinks/community/test/utils/ContainerUtilsTest.java

@@ -0,0 +1,36 @@
+package org.jetlinks.community.test.utils;
+
+import org.junit.jupiter.api.Test;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@Testcontainers
+class ContainerUtilsTest {
+
+    @Container
+    GenericContainer<?> redis = ContainerUtils.newRedis();
+
+    @Container
+    GenericContainer<?> mysql = ContainerUtils.newMysql();
+
+    @Container
+    GenericContainer<?> postgresql = ContainerUtils.newPostgresql();
+
+    @Container
+    GenericContainer<?> elasticsearch = ContainerUtils.newElasticSearch();
+
+
+    @Test
+    void test(){
+
+        assertTrue(redis.isRunning());
+        assertTrue(mysql.isRunning());
+        assertTrue(postgresql.isRunning());
+        assertTrue(elasticsearch.isRunning());
+
+    }
+
+}

+ 7 - 0
jetlinks-manager/authentication-manager/pom.xml

@@ -76,6 +76,13 @@
             <scope>compile</scope>
             <scope>compile</scope>
         </dependency>
         </dependency>
 
 
+        <dependency>
+            <groupId>org.jetlinks.community</groupId>
+            <artifactId>test-component</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+
     </dependencies>
     </dependencies>
 
 
 </project>
 </project>

+ 22 - 0
jetlinks-manager/authentication-manager/src/test/java/org/jetlinks/community/auth/AuthenticationTestConfiguration.java

@@ -0,0 +1,22 @@
+package org.jetlinks.community.auth;
+
+import org.hswebframework.web.authorization.simple.DefaultAuthorizationAutoConfiguration;
+import org.hswebframework.web.starter.jackson.CustomCodecsAutoConfiguration;
+import org.hswebframework.web.system.authorization.defaults.configuration.AuthorizationServiceAutoConfiguration;
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration;
+import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ImportAutoConfiguration({
+    AuthorizationServiceAutoConfiguration.class,
+    CodecsAutoConfiguration.class,
+    JacksonAutoConfiguration.class,
+    CustomCodecsAutoConfiguration.class,
+    DefaultAuthorizationAutoConfiguration.class
+})
+public class AuthenticationTestConfiguration {
+
+
+}

+ 88 - 0
jetlinks-manager/authentication-manager/src/test/java/org/jetlinks/community/auth/relation/UserRelationObjectProviderTest.java

@@ -0,0 +1,88 @@
+package org.jetlinks.community.auth.relation;
+
+import org.hswebframework.ezorm.core.StaticMethodReferenceColumn;
+import org.hswebframework.ezorm.rdb.mapping.ReactiveRepository;
+import org.hswebframework.ezorm.rdb.mapping.defaults.DefaultReactiveQuery;
+import org.jetlinks.community.auth.entity.ThirdPartyUserBindEntity;
+import org.jetlinks.community.auth.entity.UserDetail;
+import org.jetlinks.community.auth.service.UserDetailService;
+import org.jetlinks.community.relation.RelationConstants;
+import org.jetlinks.core.things.relation.PropertyOperation;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+
+class UserRelationObjectProviderTest {
+
+    @Test
+    void test() {
+        UserDetailService service = Mockito.mock(UserDetailService.class);
+        ReactiveRepository<ThirdPartyUserBindEntity, String> repository = Mockito.mock(ReactiveRepository.class);
+        UserDetail detail = new UserDetail();
+        detail.setName("Admin");
+        detail.setUsername("admin");
+        detail.setId("admin");
+        detail.setEmail("admin@hsweb.me");
+        {
+
+            Mockito.when(service.findUserDetail("admin"))
+                   .thenReturn(Mono.just(detail));
+
+        }
+        ThirdPartyUserBindEntity entity = new ThirdPartyUserBindEntity();
+        entity.setType("wx");
+        entity.setThirdPartyUserId("third-admin");
+        entity.setProvider("dingtalk");
+        entity.setUserId("admin");
+        {
+
+
+
+            DefaultReactiveQuery query = Mockito.mock(DefaultReactiveQuery.class);
+
+            Mockito.when(query.where(Mockito.any(StaticMethodReferenceColumn.class),Mockito.any()))
+                       .thenReturn(query);
+
+            Mockito.when(query.and(Mockito.any(StaticMethodReferenceColumn.class),Mockito.any()))
+                   .thenReturn(query);
+
+            Mockito.when(query.fetch())
+                   .thenReturn(Flux.just(
+                       entity
+                   ));
+
+            Mockito.when(repository.createQuery())
+                   .thenReturn(query);
+        }
+
+
+        UserRelationObjectProvider provider = new UserRelationObjectProvider(service, repository);
+
+        PropertyOperation operation = provider.properties("admin");
+
+        operation.get(RelationConstants.UserProperty.name)
+                 .as(StepVerifier::create)
+                 .expectNext(detail.getName())
+                 .verifyComplete();
+
+        operation.get(RelationConstants.UserProperty.email)
+                 .as(StepVerifier::create)
+                 .expectNext(detail.getEmail())
+                 .verifyComplete();
+
+        operation.get("null")
+                 .as(StepVerifier::create)
+                 .expectComplete()
+                 .verify();
+
+        operation.get(RelationConstants.UserProperty.thirdParty(entity.getType(),entity.getProvider()))
+            .as(StepVerifier::create)
+            .expectNext(entity.getThirdPartyUserId())
+            .verifyComplete();
+
+    }
+
+
+}

+ 151 - 0
jetlinks-manager/authentication-manager/src/test/java/org/jetlinks/community/auth/service/OrganizationServiceTest.java

@@ -0,0 +1,151 @@
+package org.jetlinks.community.auth.service;
+
+import org.hswebframework.ezorm.core.StaticMethodReferenceColumn;
+import org.hswebframework.ezorm.core.param.QueryParam;
+import org.hswebframework.ezorm.rdb.mapping.ReactiveDelete;
+import org.hswebframework.ezorm.rdb.mapping.ReactiveQuery;
+import org.hswebframework.ezorm.rdb.mapping.ReactiveRepository;
+import org.hswebframework.ezorm.rdb.mapping.defaults.SaveResult;
+import org.hswebframework.web.crud.events.EntityDeletedEvent;
+import org.hswebframework.web.system.authorization.api.entity.DimensionUserEntity;
+import org.hswebframework.web.system.authorization.defaults.service.DefaultDimensionUserService;
+import org.jetlinks.community.auth.entity.OrganizationEntity;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import org.reactivestreams.Publisher;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.BiFunction;
+
+/**
+ * 输入描述.
+ *
+ * @author zhangji
+ * @version 1.11 2021/10/19
+ */
+public class OrganizationServiceTest {
+    public static final String ORG_ID      = "org-test";
+    public static final String TENANT_ID_1 = "tenant-1";
+    public static final String TENANT_ID_2 = "tenant-2";
+    public static final String USER_ID_1   = "user-1";
+    public static final String USER_ID_2   = "user-2";
+    public static final String USER_ID_3   = "user-3";
+
+    @Test
+    void test() {
+        // 模拟DefaultDimensionUserService
+        ReactiveRepository<DimensionUserEntity, String> dimensionUserrepository = Mockito.mock(ReactiveRepository.class);
+        ReactiveDelete reactiveDelete = Mockito.mock(ReactiveDelete.class);
+        ReactiveQuery<DimensionUserEntity> dimensionUserQuery = Mockito.mock(ReactiveQuery.class);
+        Mockito.when(dimensionUserrepository.save(Mockito.any(Publisher.class)))
+            .thenReturn(Mono.just(SaveResult.of(3, 0)));
+        Mockito.when(dimensionUserrepository.createDelete())
+            .thenReturn(reactiveDelete);
+        Mockito.when(reactiveDelete.where(Mockito.any(StaticMethodReferenceColumn.class), Mockito.any()))
+            .thenReturn(reactiveDelete);
+        Mockito.when(reactiveDelete.in(Mockito.any(StaticMethodReferenceColumn.class), Mockito.anyCollection()))
+            .thenReturn(reactiveDelete);
+        Mockito.when(reactiveDelete.and(Mockito.any(StaticMethodReferenceColumn.class), Mockito.any()))
+            .thenReturn(reactiveDelete);
+        Mockito.when(reactiveDelete.execute())
+            .thenReturn(Mono.just(1));
+        Mockito.when(reactiveDelete.onExecute(Mockito.any(BiFunction.class)))
+            .thenReturn(reactiveDelete);
+        Mockito.when(dimensionUserrepository.createQuery())
+            .thenReturn(dimensionUserQuery);
+        Mockito.when(dimensionUserQuery.select(Mockito.any(StaticMethodReferenceColumn.class)))
+            .thenReturn(dimensionUserQuery);
+        Mockito.when(dimensionUserQuery.setParam(Mockito.any(QueryParam.class)))
+            .thenReturn(dimensionUserQuery);
+        Mockito.when(dimensionUserQuery.fetch())
+            .thenReturn(Flux.fromIterable(dimensionUserEntityList()));
+
+        DefaultDimensionUserService dimensionUserService = new DefaultDimensionUserService() {
+            @Override
+            public ReactiveRepository<DimensionUserEntity, String> getRepository() {
+                return dimensionUserrepository;
+            }
+
+            @Override
+            public Mono<SaveResult> save(Publisher<DimensionUserEntity> entityPublisher) {
+                return Flux.from(entityPublisher)
+                    .count()
+                    .map(Long::intValue)
+                    .map(i -> SaveResult.of(i, 0));
+            }
+        };
+
+
+        // 模拟repostory
+        ReactiveRepository<OrganizationEntity, String> repository = Mockito.mock(ReactiveRepository.class);
+        Mockito.when(repository.findById(Mockito.anyString()))
+            .thenReturn(Mono.just(organizationEntity()));
+
+        // 构建OrganizationService
+        OrganizationService organizationService = new OrganizationService(dimensionUserService) {
+            @Override
+            public ReactiveRepository<OrganizationEntity, String> getRepository() {
+                return repository;
+            }
+        };
+
+        // 绑定用户
+        organizationService.bindUser(
+            ORG_ID, Arrays.asList(USER_ID_1, USER_ID_2, USER_ID_3))
+            .as(StepVerifier::create)
+            .expectNext(3)
+            .verifyComplete();
+
+        // 绑定用户-不指定租户ID
+        organizationService.bindUser(ORG_ID, Arrays.asList(USER_ID_1, USER_ID_2, USER_ID_3))
+            .as(StepVerifier::create)
+            .expectNext(3)
+            .verifyComplete();
+
+        // 解绑用户
+        organizationService.unbindUser(ORG_ID, Arrays.asList(USER_ID_1))
+            .as(StepVerifier::create)
+            .expectNext(1)
+            .verifyComplete();
+
+        // 解绑用户-不指定租户ID
+        organizationService.unbindUser(ORG_ID, Arrays.asList(USER_ID_1))
+            .as(StepVerifier::create)
+            .expectNext(1)
+            .verifyComplete();
+
+        EntityDeletedEvent<OrganizationEntity> event = new EntityDeletedEvent<>(
+            Arrays.asList(organizationEntity()), OrganizationEntity.class
+        );
+        event.getAsync()
+            .as(StepVerifier::create)
+            .expectComplete();
+    }
+
+    private OrganizationEntity organizationEntity() {
+        OrganizationEntity organizationEntity = new OrganizationEntity();
+        organizationEntity.setId(ORG_ID);
+        organizationEntity.setName("测试机构");
+
+        return organizationEntity;
+    }
+
+    private List<DimensionUserEntity> dimensionUserEntityList() {
+        DimensionUserEntity dimensionUserEntity1 = new DimensionUserEntity();
+        dimensionUserEntity1.setId("1");
+        dimensionUserEntity1.setDimensionId(TENANT_ID_1);
+        dimensionUserEntity1.setUserId("test");
+
+        DimensionUserEntity dimensionUserEntity2 = new DimensionUserEntity();
+        dimensionUserEntity2.setId("2");
+        dimensionUserEntity2.setDimensionId(TENANT_ID_2);
+        dimensionUserEntity2.setUserId("test");
+
+        return Arrays.asList(dimensionUserEntity1, dimensionUserEntity2);
+    }
+
+}

+ 34 - 0
jetlinks-manager/authentication-manager/src/test/java/org/jetlinks/community/auth/service/terms/ThirdPartyUserTermBuilderTest.java

@@ -0,0 +1,34 @@
+package org.jetlinks.community.auth.service.terms;
+
+import org.hswebframework.ezorm.core.param.Term;
+import org.hswebframework.ezorm.rdb.executor.SqlRequest;
+import org.hswebframework.ezorm.rdb.metadata.RDBColumnMetadata;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class ThirdPartyUserTermBuilderTest {
+
+
+    @Test
+    void test() {
+        ThirdPartyUserTermBuilder builder = new ThirdPartyUserTermBuilder();
+
+        RDBColumnMetadata column = new RDBColumnMetadata();
+        column.setName("id");
+        Term term = new Term();
+        term.setColumn("id");
+        term.getOptions().add("wx");
+        term.setValue("providerId");
+
+        SqlRequest request = builder
+            .createFragments("id", column, term)
+            .toRequest();
+        System.out.printf(request.toNativeSql());
+        assertEquals(
+            "exists(select 1 from s_third_party_user_bind _bind where _bind.user_id = id and _bind.type = 'wx' and _bind.provider in ( 'providerId' ) )",
+            request.toNativeSql());
+        assertArrayEquals(request.getParameters(), new Object[]{"wx", "providerId"});
+    }
+}

+ 73 - 0
jetlinks-manager/authentication-manager/src/test/java/org/jetlinks/community/auth/web/AuthorizationSettingDetailControllerTest.java

@@ -0,0 +1,73 @@
+package org.jetlinks.community.auth.web;
+
+import org.jetlinks.community.auth.web.request.AuthorizationSettingDetail;
+import org.jetlinks.community.test.spring.TestJetLinksController;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
+import reactor.core.publisher.Flux;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+/**
+ * 输入描述.
+ *
+ * @author zhangji
+ * @version 1.11 2021/9/28
+ */
+@DisplayName("权限分配:AuthorizationSettingDetailController")
+@WebFluxTest(AuthorizationSettingDetailController.class)
+class AuthorizationSettingDetailControllerTest extends TestJetLinksController {
+
+    public static final String BASE_URI    = "/autz-setting/detail";
+    public static final String TARGET_TYPE = "user";
+    public static final String TARGET      = "test-operator";
+
+    @Test
+    void test() {
+        Boolean result = client.post()
+            .uri(BASE_URI + "/_save")
+            .body(Flux.just(authSettingDetail()), AuthorizationSettingDetail.class)
+            .exchange()
+            .expectBody(Boolean.class)
+            .returnResult()
+            .getResponseBody();
+
+        assertEquals(true, result);
+
+        AuthorizationSettingDetail authSettingDetail = client.get()
+            .uri(BASE_URI + String.format("/%s/%s", TARGET_TYPE, TARGET))
+            .exchange()
+            .expectBody(AuthorizationSettingDetail.class)
+            .returnResult()
+            .getResponseBody();
+
+        assertNotNull(authSettingDetail);
+        assertEquals(TARGET_TYPE, authSettingDetail.getTargetType());
+        assertEquals(TARGET, authSettingDetail.getTargetId());
+    }
+
+    AuthorizationSettingDetail authSettingDetail() {
+        AuthorizationSettingDetail authSettingDetail = new AuthorizationSettingDetail();
+        authSettingDetail.setTargetType(TARGET_TYPE);
+        authSettingDetail.setTargetId(TARGET);
+        authSettingDetail.setMerge(true);
+        authSettingDetail.setPriority(1);
+        authSettingDetail.setPermissionList(Arrays.asList(
+            AuthorizationSettingDetail.PermissionInfo.of("1", new HashSet<>(Arrays.asList("query"))),
+            AuthorizationSettingDetail.PermissionInfo.of("2", new HashSet<>(Arrays.asList("query", "update")))
+        ));
+        return authSettingDetail;
+    }
+
+
+//    @Override
+//    protected void initAuth(TestAuthentication authentication) {
+//        super.initAuth(authentication);
+//        authentication.addDimension(TARGET_TYPE, TARGET);
+//    }
+}

+ 266 - 0
jetlinks-manager/authentication-manager/src/test/java/org/jetlinks/community/auth/web/MenuControllerTest.java

@@ -0,0 +1,266 @@
+package org.jetlinks.community.auth.web;
+
+import org.hswebframework.web.system.authorization.api.entity.PermissionEntity;
+import org.hswebframework.web.system.authorization.api.service.reactive.ReactiveUserService;
+import org.hswebframework.web.system.authorization.defaults.service.DefaultPermissionService;
+import org.jetlinks.community.auth.entity.MenuButtonInfo;
+import org.jetlinks.community.auth.entity.MenuEntity;
+import org.jetlinks.community.auth.entity.MenuView;
+import org.jetlinks.community.auth.entity.PermissionInfo;
+import org.jetlinks.community.auth.service.request.MenuGrantRequest;
+import org.jetlinks.community.auth.web.request.AuthorizationSettingDetail;
+import org.jetlinks.community.test.spring.TestJetLinksController;
+import org.jetlinks.community.test.web.TestAuthentication;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
+import org.springframework.http.MediaType;
+import reactor.test.StepVerifier;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+@WebFluxTest(value = MenuController.class, properties = {
+    "menu.allow-all-menus-users=test"
+})
+class MenuControllerTest extends TestJetLinksController {
+
+
+    @Autowired
+    private DefaultPermissionService permissionService;
+
+    @Autowired
+    private ReactiveUserService userService;
+
+    @Test
+    void testCrud() {
+        MenuEntity sys = new MenuEntity();
+        sys.setName("系统设置");
+        sys.setUrl("/xxx");
+        sys.setId("sys-crud");
+        MenuEntity menu = new MenuEntity();
+        menu.setId("menu-crud");
+        menu.setName("菜单管理");
+        menu.setUrl("/xxx");
+        menu.setParentId(sys.getId());
+        menu.setButtons(Arrays.asList(
+            MenuButtonInfo.of("create", "创建菜单", "menu", "add"),
+            MenuButtonInfo.of("delete", "删除菜单", "menu", "remove")
+        ));
+
+        sys.setChildren(Collections.singletonList(menu));
+
+        client.patch()
+            .uri("/menu")
+            .bodyValue(sys)
+            .exchange()
+            .expectStatus()
+            .is2xxSuccessful();
+
+        client.delete()
+            .uri("/menu/{id}", sys.getId())
+            .exchange()
+            .expectStatus()
+            .is2xxSuccessful();
+
+        client.get()
+            .uri("/menu/_query/no-paging")
+            .exchange()
+            .expectBodyList(MenuEntity.class)
+            .hasSize(0);
+    }
+
+    @Test
+    void testGrant() {
+
+        MenuEntity sys = new MenuEntity();
+        sys.setName("系统设置");
+        sys.setUrl("/xxx");
+        sys.setId("sys");
+
+        MenuEntity menu = new MenuEntity();
+        menu.setId("menu");
+        menu.setName("菜单管理");
+        menu.setUrl("/xxx");
+        menu.setParentId(sys.getId());
+        menu.setButtons(Arrays.asList(
+            MenuButtonInfo.of("create", "创建菜单", "menu", "add"),
+            MenuButtonInfo.of("delete", "删除菜单", "menu", "remove")
+        ));
+
+
+        sys.setChildren(Collections.singletonList(menu));
+
+        client.patch()
+            .uri("/menu")
+            .bodyValue(sys)
+            .exchange()
+            .expectStatus()
+            .is2xxSuccessful();
+
+        MenuGrantRequest request = new MenuGrantRequest();
+        request.setTargetType("user");
+        request.setTargetId("test2");
+        MenuView menuView = new MenuView();
+        menuView.setId(menu.getId());
+        menuView.setButtons(Arrays.asList(MenuView.ButtonView.of("create", "创建菜单", "", null)));
+        menuView.setGranted(true);
+        request.setMenus(Arrays.asList(menuView));
+
+        client
+            .put()
+            .uri("/menu/user/test2/_grant")
+            .bodyValue(request)
+            .exchange()
+            .expectStatus()
+            .is2xxSuccessful();
+
+        List<MenuView> tree = client
+            .get()
+            .uri("/menu/user/test2/_grant/tree")
+            .exchange()
+            .expectBodyList(MenuView.class)
+            .returnResult()
+            .getResponseBody();
+
+        assertNotNull(tree);
+        assertFalse(tree.isEmpty());
+
+        assertEquals(tree.get(0).getId(), sys.getId());
+        assertEquals(1, tree.get(0).getChildren().size());
+        assertEquals(menu.getId(), tree.get(0).getChildren().get(0).getId());
+
+        assertEquals(2, tree.get(0).getChildren().get(0).getButtons().size());
+
+        assertTrue(tree
+            .get(0)
+            .getChildren()
+            .get(0)
+            .getButton("create")
+            .map(MenuView.ButtonView::isGranted)
+            .orElse(false));
+        assertFalse(tree
+            .get(0)
+            .getChildren()
+            .get(0)
+            .getButton("delete")
+            .map(MenuView.ButtonView::isGranted)
+            .orElse(true));
+
+        client.delete()
+            .uri("/menu/{id}", sys.getId())
+            .exchange()
+            .expectStatus()
+            .is2xxSuccessful();
+    }
+
+    @Test
+    void testAssetTypes() {
+        //菜单
+        MenuEntity sys = new MenuEntity();
+        sys.setName("系统设置");
+        sys.setUrl("/xxx");
+        sys.setPermissions(Arrays.asList(PermissionInfo.of("test", new HashSet<>(Arrays.asList("save")))));
+        sys.setId("asset-test");
+
+        client.patch()
+            .uri("/menu")
+            .bodyValue(sys)
+            .exchange()
+            .expectStatus()
+            .is2xxSuccessful();
+
+        //权限
+        PermissionEntity permissionEntity = new PermissionEntity();
+        permissionEntity.setId("test");
+        permissionEntity.setName("Test");
+        permissionEntity.setActions(new ArrayList<>());
+
+        permissionService
+            .save(permissionEntity)
+            .then()
+            .as(StepVerifier::create)
+            .expectComplete()
+            .verify();
+
+        //资产
+        List<MenuController.AssetTypeView> view = client
+            .post()
+            .uri("/menu/asset-types")
+            .contentType(MediaType.APPLICATION_JSON)
+            .bodyValue("[{\"id\":\"asset-test\"}]")
+            .exchange()
+            .expectStatus()
+            .is2xxSuccessful()
+            .expectBodyList(MenuController.AssetTypeView.class)
+            .returnResult()
+            .getResponseBody();
+
+        assertNotNull(view);
+        assertEquals(1, view.size());
+
+    }
+
+
+    @Test
+    void testPermissions() {
+        //菜单
+        MenuEntity sys = new MenuEntity();
+        sys.setName("系统设置");
+        sys.setUrl("/xxx");
+        sys.setPermissions(Arrays.asList(PermissionInfo.of("test", new HashSet<>(Arrays.asList("save")))));
+        sys.setId("asset-test");
+
+        client.patch()
+            .uri("/menu")
+            .bodyValue(sys)
+            .exchange()
+            .expectStatus()
+            .is2xxSuccessful();
+
+        //权限
+        PermissionEntity permissionEntity = new PermissionEntity();
+        permissionEntity.setId("test");
+        permissionEntity.setName("Test");
+        permissionEntity.setActions(new ArrayList<>());
+
+        permissionService
+            .save(permissionEntity)
+            .then()
+            .as(StepVerifier::create)
+            .expectComplete()
+            .verify();
+
+
+        List<AuthorizationSettingDetail.PermissionInfo> view = client
+            .post()
+            .uri("/menu/permissions")
+            .contentType(MediaType.APPLICATION_JSON)
+            .bodyValue("[{\"id\":\"asset-test\"}]")
+            .exchange()
+            .expectStatus()
+            .is2xxSuccessful()
+            .expectBodyList(AuthorizationSettingDetail.PermissionInfo.class)
+            .returnResult()
+            .getResponseBody();
+
+        assertNotNull(view);
+        assertEquals(1, view.size());
+        assertEquals("test", view.get(0).getId());
+
+    }
+
+    @Override
+    protected void initAuth(TestAuthentication authentication) {
+        super.initAuth(authentication);
+        authentication.addPermission("menu", "add");
+    }
+}

+ 201 - 0
jetlinks-manager/authentication-manager/src/test/java/org/jetlinks/community/auth/web/OrganizationControllerTest.java

@@ -0,0 +1,201 @@
+package org.jetlinks.community.auth.web;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.jetlinks.community.auth.entity.OrganizationEntity;
+import org.jetlinks.community.test.spring.TestJetLinksController;
+import org.jetlinks.community.test.web.TestAuthentication;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
+import org.springframework.core.ParameterizedTypeReference;
+import reactor.core.publisher.Flux;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+/**
+ * 机构管理.
+ *
+ * @author zhangji
+ * @version 1.11 2021/9/28
+ */
+@DisplayName("机构管理:OrganizationController")
+@WebFluxTest(OrganizationController.class)
+class OrganizationControllerTest extends TestJetLinksController {
+    public static final String BASE_URI = "/organization";
+
+
+    @Override
+    protected void initAuth(TestAuthentication authentication) {
+        super.initAuth(authentication);
+    }
+
+    @Test
+    void test() {
+        // 创建
+        for (OrganizationEntity organizationEntity : orgList()) {
+            client.post()
+                .uri(BASE_URI)
+                .bodyValue(organizationEntity)
+                .exchange()
+                .expectStatus()
+                .is2xxSuccessful();
+        }
+
+        // 查询全部-树结构
+        List<OrganizationEntity> orgTreeList = client
+            .get()
+            .uri(BASE_URI + "/_all/tree")
+            .exchange()
+            .expectBodyList(OrganizationEntity.class)
+            .returnResult()
+            .getResponseBody();
+
+        assertFalse(CollectionUtils.isEmpty(orgTreeList));
+        OrganizationEntity top = orgTreeList.get(0);
+        assertEquals("1", top.getId());
+
+        List<OrganizationEntity> children = top.getChildren();
+        assertFalse(CollectionUtils.isEmpty(children));
+        assertEquals("2", children.get(0).getId());
+        assertEquals("3", children.get(1).getId());
+        assertFalse(CollectionUtils.isEmpty(children.get(0).getChildren()));
+        assertEquals("4", children.get(0).getChildren().get(0).getId());
+
+        // 查询全部-列表
+        // 根据sortIndex字段排序
+        List<OrganizationEntity> orgList = client
+            .get()
+            .uri(BASE_URI + "/_all")
+            .exchange()
+            .expectBodyList(OrganizationEntity.class)
+            .returnResult()
+            .getResponseBody();
+
+        assertFalse(CollectionUtils.isEmpty(orgList));
+        assertEquals("1", orgList.get(0).getId());
+        assertEquals("2", orgList.get(1).getId());
+        assertEquals("3", orgList.get(2).getId());
+        assertEquals("4", orgList.get(3).getId());
+
+        // 查询机构列表(包含子机构)树结构
+        List<OrganizationEntity> orgIncludeChildrenTreeList = client
+            .get()
+            .uri(BASE_URI + "/_query/_children/tree?where=code = 1001&orgerBy=id")
+            .exchange()
+            .expectBodyList(OrganizationEntity.class)
+            .returnResult()
+            .getResponseBody();
+
+        assertFalse(CollectionUtils.isEmpty(orgIncludeChildrenTreeList));
+        assertEquals(2, orgIncludeChildrenTreeList.get(0).getChildren().size());
+
+        // 查询机构列表(包含子机构)
+        List<OrganizationEntity> orgIncludeChildrenList = client
+            .get()
+            .uri(BASE_URI + "/_query/_children?where=code = 1001&orgerBy=id")
+            .exchange()
+            .expectBodyList(OrganizationEntity.class)
+            .returnResult()
+            .getResponseBody();
+
+        assertFalse(CollectionUtils.isEmpty(orgIncludeChildrenList));
+        assertEquals(4, orgIncludeChildrenList.size());
+
+        // 绑定用户到机构
+        client.post()
+            .uri(BASE_URI + "/{id}/users/_bind", "1")
+            .body(Flux.just("test").collectList(), new ParameterizedTypeReference<List<String>>() {
+            })
+            .exchange()
+            .expectStatus()
+            .is2xxSuccessful();
+
+        // 从机构解绑用户
+        client.post()
+            .uri(BASE_URI + "/{id}/users/_unbind", "1")
+            .body(Flux.just("test").collectList(), new ParameterizedTypeReference<List<String>>() {
+            })
+            .exchange()
+            .expectStatus()
+            .is2xxSuccessful();
+
+        client.delete()
+            .uri(BASE_URI + "/{id}", "1")
+            .exchange()
+            .expectStatus()
+            .is2xxSuccessful();
+
+    }
+
+    List<OrganizationEntity> orgList() {
+        List<OrganizationEntity> orgList = new ArrayList<>();
+        OrganizationEntity orgEntity = new OrganizationEntity();
+        orgEntity.setId("1");
+        orgEntity.setCode("1001");
+        orgEntity.setName("总部");
+        orgEntity.setType("默认");
+        orgEntity.setDescribe("组织的最上级");
+        Map<String, Object> properties = new HashMap<>();
+        properties.put("province", "北京");
+        properties.put("alias", "top");
+        orgEntity.setProperties(properties);
+        orgEntity.setParentId("");
+        orgEntity.setPath("1001");
+        orgEntity.setLevel(1);
+        orgEntity.setSortIndex(1L);
+        orgList.add(orgEntity);
+
+        OrganizationEntity orgChildrenEntity1 = new OrganizationEntity();
+        orgChildrenEntity1.setId("2");
+        orgChildrenEntity1.setCode("1002");
+        orgChildrenEntity1.setName("重庆分部");
+        orgChildrenEntity1.setType("默认");
+        Map<String, Object> childrenProperties1 = new HashMap<>();
+        childrenProperties1.put("province", "重庆");
+        childrenProperties1.put("alias", "child-1");
+        orgChildrenEntity1.setProperties(childrenProperties1);
+        orgChildrenEntity1.setParentId("1");
+        orgChildrenEntity1.setPath("1001-1002");
+        orgChildrenEntity1.setLevel(2);
+        orgChildrenEntity1.setSortIndex(2L);
+        orgList.add(orgChildrenEntity1);
+
+        OrganizationEntity orgChildrenEntity2 = new OrganizationEntity();
+        orgChildrenEntity2.setId("3");
+        orgChildrenEntity2.setCode("1003");
+        orgChildrenEntity2.setName("成都分部");
+        orgChildrenEntity2.setType("默认");
+        Map<String, Object> childrenProperties2 = new HashMap<>();
+        childrenProperties2.put("province", "成都");
+        childrenProperties2.put("alias", "child-2");
+        orgChildrenEntity2.setProperties(childrenProperties2);
+        orgChildrenEntity2.setParentId("1");
+        orgChildrenEntity2.setPath("1001-1003");
+        orgChildrenEntity2.setLevel(2);
+        orgChildrenEntity2.setSortIndex(3L);
+        orgList.add(orgChildrenEntity2);
+
+        OrganizationEntity orgChildrenEntity3 = new OrganizationEntity();
+        orgChildrenEntity3.setId("4");
+        orgChildrenEntity3.setCode("1004");
+        orgChildrenEntity3.setName("沙坪坝办事点");
+        orgChildrenEntity3.setType("默认");
+        Map<String, Object> childrenProperties3 = new HashMap<>();
+        childrenProperties3.put("province", "重庆");
+        childrenProperties3.put("alias", "child-3");
+        orgChildrenEntity3.setProperties(childrenProperties3);
+        orgChildrenEntity3.setParentId("2");
+        orgChildrenEntity3.setPath("1001-1002-1004");
+        orgChildrenEntity3.setLevel(3);
+        orgChildrenEntity3.setSortIndex(4L);
+        orgList.add(orgChildrenEntity3);
+
+        return orgList;
+    }
+}

+ 81 - 0
jetlinks-manager/authentication-manager/src/test/java/org/jetlinks/community/auth/web/RoleControllerTest.java

@@ -0,0 +1,81 @@
+package org.jetlinks.community.auth.web;
+
+import org.hswebframework.web.authorization.DefaultDimensionType;
+import org.hswebframework.web.authorization.ReactiveAuthenticationInitializeService;
+import org.hswebframework.web.system.authorization.api.entity.UserEntity;
+import org.hswebframework.web.system.authorization.api.service.reactive.ReactiveUserService;
+import org.jetlinks.community.auth.entity.RoleEntity;
+import org.jetlinks.community.test.spring.TestJetLinksController;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
+import org.springframework.http.MediaType;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+
+import java.util.Collections;
+
+
+@WebFluxTest(RoleController.class)
+class RoleControllerTest extends TestJetLinksController {
+
+    @Autowired
+    private ReactiveAuthenticationInitializeService initializeService;
+
+    @Autowired
+    private ReactiveUserService userService;
+
+    @Test
+    void testBind() {
+        RoleEntity role = new RoleEntity();
+        role.setId("test");
+        role.setName("Test");
+
+        UserEntity user = new UserEntity();
+        user.setName("Test");
+        user.setUsername("test");
+        user.setPassword("Test123456");
+
+        userService.saveUser(Mono.just(user))
+            .block();
+
+        client
+            .post()
+            .uri("/role")
+            .bodyValue(role)
+            .exchange()
+            .expectStatus()
+            .is2xxSuccessful();
+
+        client
+            .post()
+            .uri("/role/{id}/users/_bind", role.getId())
+            .contentType(MediaType.APPLICATION_JSON)
+            .bodyValue(Collections.singletonList(user.getId()))
+            .exchange()
+            .expectStatus()
+            .is2xxSuccessful();
+
+        initializeService
+            .initUserAuthorization(user.getId())
+            .as(StepVerifier::create)
+            .expectNextMatches(auth -> auth.hasDimension(DefaultDimensionType.role, role.getId()))
+            .verifyComplete();
+
+        client
+            .post()
+            .uri("/role/{id}/users/_unbind", role.getId())
+            .contentType(MediaType.APPLICATION_JSON)
+            .bodyValue(Collections.singletonList(user.getId()))
+            .exchange()
+            .expectStatus()
+            .is2xxSuccessful();
+
+        initializeService
+            .initUserAuthorization(user.getId())
+            .as(StepVerifier::create)
+            .expectNextMatches(auth -> !auth.hasDimension(DefaultDimensionType.role, role.getId()))
+            .verifyComplete();
+    }
+
+}

+ 69 - 0
jetlinks-manager/authentication-manager/src/test/java/org/jetlinks/community/auth/web/ThirdPartyUserControllerTest.java

@@ -0,0 +1,69 @@
+package org.jetlinks.community.auth.web;
+
+import org.jetlinks.community.auth.entity.ThirdPartyUserBindEntity;
+import org.jetlinks.community.auth.web.request.ThirdPartyBindUserInfo;
+import org.jetlinks.community.test.spring.TestJetLinksController;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
+import org.springframework.http.MediaType;
+
+@WebFluxTest(ThirdPartyUserController.class)
+class ThirdPartyUserControllerTest extends TestJetLinksController {
+
+
+    @Test
+    void test() {
+
+        String type = "wx-corp";
+        String provider = "corp1";
+
+        client.patch()
+              .uri("/user/third-party/{type}/{provider}", type, provider)
+              .contentType(MediaType.APPLICATION_JSON)
+              .bodyValue(ThirdPartyBindUserInfo.of(
+                  "test","test", "用户1", "u1"
+              ))
+              .exchange()
+              .expectStatus()
+              .is2xxSuccessful();
+
+        client.get()
+              .uri("/user/third-party/{type}/{provider}", type, provider)
+              .exchange()
+              .expectStatus()
+              .is2xxSuccessful()
+              .expectBody()
+              .jsonPath("$[0].userId").isEqualTo("test")
+              .jsonPath("$[0].providerName").isEqualTo("用户1")
+              .jsonPath("$[0].thirdPartyUserId").isEqualTo("u1");
+
+        client.get()
+              .uri("/user/third-party/me")
+              .exchange()
+              .expectStatus()
+              .is2xxSuccessful()
+              .expectBody()
+              .jsonPath("$[0].userId").isEqualTo("test")
+              .jsonPath("$[0].providerName").isEqualTo("用户1")
+              .jsonPath("$[0].thirdPartyUserId").isEqualTo("u1")
+        ;
+
+        client.delete()
+              .uri("/user/third-party/me/{id}", ThirdPartyUserBindEntity.generateId(
+                  type, provider, "u1"
+              ))
+              .exchange()
+              .expectStatus()
+              .is2xxSuccessful();
+
+        client.get()
+              .uri("/user/third-party/me")
+              .exchange()
+              .expectStatus()
+              .is2xxSuccessful()
+              .expectBodyList(ThirdPartyUserBindEntity.class)
+              .hasSize(0)
+        ;
+    }
+
+}

+ 121 - 0
jetlinks-manager/authentication-manager/src/test/java/org/jetlinks/community/auth/web/UserDetailControllerTest.java

@@ -0,0 +1,121 @@
+package org.jetlinks.community.auth.web;
+
+import org.hswebframework.web.authorization.Authentication;
+import org.hswebframework.web.authorization.simple.SimpleUser;
+import org.hswebframework.web.system.authorization.api.entity.UserEntity;
+import org.jetlinks.community.auth.entity.UserDetail;
+import org.jetlinks.community.auth.service.request.SaveUserDetailRequest;
+import org.jetlinks.community.test.spring.TestJetLinksController;
+import org.jetlinks.community.test.web.TestAuthentication;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
+import reactor.test.StepVerifier;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 用户信息接口.
+ *
+ * @author zhangji
+ * @version 1.11 2021/9/29
+ */
+@DisplayName("用户信息接口:UserDetailController")
+@WebFluxTest({UserDetailController.class, WebFluxUserController.class})
+class UserDetailControllerTest extends TestJetLinksController {
+    public static final String BASE_URI = "/user/detail";
+    private             String userId;
+
+    @Override
+    protected void initAuth(TestAuthentication authentication) {
+        super.initAuth(authentication);
+
+        // 初始化用户ID
+        UserEntity userEntity = new UserEntity();
+        userEntity.setUsername(authentication.getUser().getUsername());
+        userEntity.generateId();
+        userId = userEntity.getId();
+
+        // 修改当前用户的ID(和数据库保持一致)
+        ((SimpleUser) authentication.getUser()).setId(userEntity.getId());
+    }
+
+    /**
+     * 保存用户
+     */
+    @BeforeEach
+    void addUser() {
+        Map<String, String> userMap = new HashMap<>();
+        userMap.put("name", "test");
+        userMap.put("username", "test");
+        userMap.put("password", "password123456");
+        userMap.put("type", "test");
+
+        Boolean result = client.patch()
+            .uri("/user")
+            .bodyValue(userMap)
+            .exchange()
+            .expectBody(Boolean.class)
+            .returnResult()
+            .getResponseBody();
+        Assertions.assertNotNull(result);
+        Assertions.assertTrue(result);
+    }
+
+    @Test
+    void test() {
+        // 保存当前用户详情
+        SaveUserDetailRequest saveRequest = new SaveUserDetailRequest();
+        saveRequest.setName("测试用户");
+        saveRequest.setDescription("单元测试-保存");
+        saveRequest.setEmail("test@email.com");
+        saveRequest.setTelephone("023-88888888");
+
+        client.put()
+            .uri(BASE_URI)
+            .bodyValue(saveRequest)
+            .exchange()
+            .expectStatus()
+            .is2xxSuccessful();
+
+        // 获取当前登录用户详情
+        client.get()
+            .uri(BASE_URI)
+            .exchange()
+            .expectBody()
+            .jsonPath("$.name").isEqualTo(saveRequest.getName())
+            .jsonPath("$.email").isEqualTo(saveRequest.getEmail())
+            .jsonPath("$.telephone").isEqualTo(saveRequest.getTelephone())
+            .jsonPath("$.description").isEqualTo(saveRequest.getDescription());
+
+        // 删除用户
+        Boolean deleteResult = client.delete()
+            .uri("/user/{id:.+}", userId)
+            .exchange()
+            .expectStatus()
+            .is2xxSuccessful()
+            .expectBody(Boolean.class)
+            .returnResult()
+            .getResponseBody();
+        Assertions.assertNotNull(deleteResult);
+        Assertions.assertTrue(deleteResult);
+
+        // 获取当前登录用户详情(用户数据不存在,返回默认数据)
+        UserDetail userDetail = client.get()
+            .uri(BASE_URI)
+            .exchange()
+            .expectBody(UserDetail.class)
+            .returnResult()
+            .getResponseBody();
+        Assertions.assertNotNull(userDetail);
+        Authentication
+            .currentReactive()
+            .map(i -> i.getUser().getName().equals(userDetail.getName()))
+            .as(StepVerifier::create)
+            .expectNext(true)
+            .verifyComplete();
+    }
+}

+ 82 - 0
jetlinks-manager/authentication-manager/src/test/java/org/jetlinks/community/auth/web/UserSettingControllerTest.java

@@ -0,0 +1,82 @@
+package org.jetlinks.community.auth.web;
+
+import org.jetlinks.community.auth.entity.UserSettingEntity;
+import org.jetlinks.community.test.spring.TestJetLinksController;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
+import org.springframework.http.MediaType;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+@WebFluxTest(UserSettingController.class)
+class UserSettingControllerTest extends TestJetLinksController {
+
+
+    @Test
+    void testCrud() {
+        String type = "user-search";
+
+        UserSettingEntity newSetting = new UserSettingEntity();
+        newSetting.setName("test");
+        newSetting.setContent("test-content");
+        client
+            .post()
+            .uri("/user/settings/{type}", type)
+            .contentType(MediaType.APPLICATION_JSON)
+            .bodyValue(newSetting)
+            .exchange()
+            .expectStatus()
+            .is2xxSuccessful();
+
+        List<UserSettingEntity> entities = client
+            .get()
+            .uri("/user/settings/{type}", type)
+            .exchange()
+            .expectStatus()
+            .is2xxSuccessful()
+            .expectBodyList(UserSettingEntity.class)
+            .returnResult()
+            .getResponseBody();
+
+        assertNotNull(entities);
+        assertEquals(1, entities.size());
+
+        UserSettingEntity entity = client
+            .get()
+            .uri("/user/settings/{type}/{key}", type, entities.get(0).getKey())
+            .exchange()
+            .expectStatus()
+            .is2xxSuccessful()
+            .expectBody(UserSettingEntity.class)
+            .returnResult()
+            .getResponseBody();
+        assertNotNull(entity);
+        assertEquals(entity.getContent(), newSetting.getContent());
+
+        client
+            .delete()
+            .uri("/user/settings/{type}/{key}", type, entity.getKey())
+            .exchange()
+            .expectStatus()
+            .is2xxSuccessful();
+
+
+        UserSettingEntity entity1 = client
+            .get()
+            .uri("/user/settings/{type}/{key}", type, entity.getKey())
+            .exchange()
+            .expectStatus()
+            .is2xxSuccessful()
+            .expectBody(UserSettingEntity.class)
+            .returnResult()
+            .getResponseBody();
+        assertNull(entity1);
+
+
+    }
+
+}

+ 225 - 0
jetlinks-manager/authentication-manager/src/test/java/org/jetlinks/community/auth/web/WebFluxUserControllerTest.java

@@ -0,0 +1,225 @@
+package org.jetlinks.community.auth.web;
+
+import org.hswebframework.web.api.crud.entity.PagerResult;
+import org.hswebframework.web.api.crud.entity.QueryParamEntity;
+import org.hswebframework.web.authorization.simple.SimpleUser;
+import org.hswebframework.web.system.authorization.api.entity.UserEntity;
+import org.jetlinks.community.test.spring.TestJetLinksController;
+import org.jetlinks.community.test.web.TestAuthentication;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.util.CollectionUtils;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 输入描述.
+ *
+ * @author zhangji
+ * @version 1.11 2021/9/29
+ */
+@DisplayName("用户管理:WebFluxUserController")
+@WebFluxTest(WebFluxUserController.class)
+public class WebFluxUserControllerTest extends TestJetLinksController {
+    public static final String BASE_URI  = "/user";
+    public              String userId;
+    public static final String TENANT_ID = "tenant-test";
+
+    @Override
+    protected void initAuth(TestAuthentication authentication) {
+        super.initAuth(authentication);
+
+        // 添加租户
+//        authentication.addDimension(TenantDimensionType.tenant.getId(), TENANT_ID);
+
+        // 初始化用户ID
+        UserEntity userEntity = new UserEntity();
+        userEntity.setUsername(authentication.getUser().getUsername());
+        userEntity.generateId();
+        userId = userEntity.getId();
+
+        // 修改当前用户的ID(和数据库保持一致)
+        ((SimpleUser) authentication.getUser()).setId(userEntity.getId());
+    }
+
+    /**
+     * 保存用户
+     */
+    @BeforeEach
+    void addUser() {
+        Map<String, String> userMap = new HashMap<>();
+        userMap.put("name", "test");
+        userMap.put("username", "test");
+        userMap.put("password", "password123456");
+        userMap.put("type", "test");
+
+
+        Boolean result = client.patch()
+            .uri("/user")
+            .bodyValue(userMap)
+            .exchange()
+            .expectBody(Boolean.class)
+            .returnResult()
+            .getResponseBody();
+        Assertions.assertNotNull(result);
+        Assertions.assertTrue(result);
+    }
+
+    /**
+     * 删除用户
+     */
+    @AfterEach
+    void deleteUser() {
+        Boolean deleteResult = client.delete()
+            .uri("/user/{id:.+}", userId)
+            .exchange()
+            .expectStatus()
+            .is2xxSuccessful()
+            .expectBody(Boolean.class)
+            .returnResult()
+            .getResponseBody();
+        Assertions.assertNotNull(deleteResult);
+        Assertions.assertTrue(deleteResult);
+    }
+
+
+    @Test
+    void testUsernameValidate() {
+        client.post()
+            .uri("/user/username/_validate")
+            .bodyValue("test")
+            .exchange()
+            .expectBody()
+            .jsonPath("$.passed")
+            .isEqualTo(false);
+    }
+
+    @Test
+    void testPasswordValidate() {
+        client.post()
+            .uri("/user/password/_validate")
+            .bodyValue("1111")
+            .exchange()
+            .expectBody()
+            .jsonPath("$.passed")
+            .isEqualTo(false);
+    }
+
+    @Test
+    void testPasswordReset() {
+        client.post()
+            .uri("/user/" + userId + "/password/_reset")
+            .bodyValue("pwd1qaz2wsx")
+            .exchange()
+            .expectBody(Integer.class)
+            .isEqualTo(1);
+    }
+
+    @Test
+    void test() {
+        // 修改用户
+        Map<String, String> userMap = new HashMap<>();
+        userMap.put("id", userId);
+        userMap.put("name", "test-update");
+
+        Boolean result = client.patch()
+            .uri("/user")
+            .bodyValue(userMap)
+            .exchange()
+            .expectBody(Boolean.class)
+            .returnResult()
+            .getResponseBody();
+        Assertions.assertNotNull(result);
+        Assertions.assertTrue(result);
+
+        // 使用POST方式分页动态查询(不返回总数)
+        List<UserEntity> userList = client.post()
+            .uri(BASE_URI + "/_query/no-paging")
+            .bodyValue(QueryParamEntity.of())
+            .exchange()
+            .expectBodyList(UserEntity.class)
+            .returnResult()
+            .getResponseBody();
+        Assertions.assertFalse(CollectionUtils.isEmpty(userList));
+
+        // 使用GET方式分页动态查询(不返回总数)
+        List<UserEntity> userList2 = client.get()
+            .uri(BASE_URI + "/_query/no-paging")
+            .exchange()
+            .expectBodyList(UserEntity.class)
+            .returnResult()
+            .getResponseBody();
+        Assertions.assertFalse(CollectionUtils.isEmpty(userList2));
+
+        // 使用POST方式分页动态查询
+        PagerResult<UserEntity> userPager = client.post()
+            .uri(BASE_URI + "/_query")
+            .bodyValue(QueryParamEntity.of())
+            .exchange()
+            .expectBody(new ParameterizedTypeReference<PagerResult<UserEntity>>() {
+            })
+            .returnResult()
+            .getResponseBody();
+        Assertions.assertNotNull(userPager);
+        Assertions.assertFalse(CollectionUtils.isEmpty(userPager.getData()));
+
+        // 使用GET方式分页动态查询
+        PagerResult<UserEntity> userPager2 = client.get()
+            .uri(BASE_URI + "/_query")
+            .exchange()
+            .expectBody(new ParameterizedTypeReference<PagerResult<UserEntity>>() {
+            })
+            .returnResult()
+            .getResponseBody();
+        Assertions.assertNotNull(userPager2);
+        Assertions.assertFalse(CollectionUtils.isEmpty(userPager2.getData()));
+
+        // 使用POST方式查询总数
+        Integer total = client.post()
+            .uri(BASE_URI + "/_count")
+            .bodyValue(QueryParamEntity.of())
+            .exchange()
+            .expectBody(Integer.class)
+            .returnResult()
+            .getResponseBody();
+        Assertions.assertNotNull(total);
+        Assertions.assertTrue(total > 0);
+
+        // 使用GET方式查询总数
+        Integer total2 = client.get()
+            .uri(BASE_URI + "/_count")
+            .exchange()
+            .expectBody(Integer.class)
+            .returnResult()
+            .getResponseBody();
+        Assertions.assertNotNull(total2);
+        Assertions.assertTrue(total2 > 0);
+
+        // 修改用户状态
+        Boolean stateResult = client.put()
+            .uri(BASE_URI + "/{id:.+}/{state}", userId, "0")
+            .exchange()
+            .expectBody(Boolean.class)
+            .returnResult()
+            .getResponseBody();
+        Assertions.assertNotNull(stateResult);
+        Assertions.assertTrue(stateResult);
+
+        // 根据ID查询
+        UserEntity user = client.get()
+            .uri(BASE_URI + "/{id:.+}", userId)
+            .exchange()
+            .expectBody(UserEntity.class)
+            .returnResult()
+            .getResponseBody();
+        Assertions.assertNotNull(user);
+        Assertions.assertEquals(userId, user.getId());
+    }
+}