1. Added datasource validator function in the plugin interface which simply returns true for all the current plugins.

2. Added static method getPluginExecutor as a util function which removes duplicate code from across different files.
This commit is contained in:
Trisha Anand 2019-11-14 08:50:02 +00:00
parent 98662714dc
commit 31cf94f08e
18 changed files with 199 additions and 61 deletions

View File

@ -31,4 +31,6 @@ public interface PluginExecutor extends ExtensionPoint {
* @param connection
*/
void datasourceDestroy(Object connection);
Boolean isDatasourceValid(DatasourceConfiguration datasourceConfiguration);
}

View File

@ -125,6 +125,11 @@ public class MongoPlugin extends BasePlugin {
}
}
@Override
public Boolean isDatasourceValid(DatasourceConfiguration datasourceConfiguration) {
return true;
}
}
}

View File

@ -116,6 +116,11 @@ public class PostgresPlugin extends BasePlugin {
}
}
@Override
public Boolean isDatasourceValid(DatasourceConfiguration datasourceConfiguration) {
return true;
}
}
}

View File

@ -19,8 +19,10 @@ import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.List;
public class RestApiPlugin extends BasePlugin {
@ -112,6 +114,22 @@ public class RestApiPlugin extends BasePlugin {
}
@Override
public Boolean isDatasourceValid(DatasourceConfiguration datasourceConfiguration) {
if (datasourceConfiguration.getUrl() == null) {
System.out.println("URL is null. Data validation failed");
return false;
}
// Check for URL validity
try {
new URL(datasourceConfiguration.getUrl()).toURI();
return true;
} catch (Exception e) {
System.out.println("URL is invalid. Data validation failed");
return false;
}
}
private void addHeadersToRequest(WebClient.Builder webClientBuilder, List<Property> headers) {
for (Property header : headers) {
if (header.getKey() != null && !header.getKey().isEmpty()) {

View File

@ -168,6 +168,10 @@
<artifactId>rollbar-java</artifactId>
<version>1.5.2</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
</dependency>
</dependencies>

View File

@ -7,5 +7,6 @@ public class FieldName {
public static String PAGEID = "pageId";
public static String LAYOUTID = "layoutId";
public static String APPLICATIONID = "applicationId";
public static String DATASOURCE = "datasource";
public static String CONFIG = "config";
}

View File

@ -22,6 +22,7 @@ public enum AppsmithError {
UNAUTHORIZED_DOMAIN(401, 4012, "Invalid email domain provided. Please sign in with a valid work email ID"),
UNAUTHORIZED_ACCESS(401, 4013, "Unauthorized access"),
INVALID_ACTION_NAME(401, 4014, "Action name is invalid. Please input syntactically correct name"),
INVALID_DATASOURCE_CONFIGURATION(400, 4015, "Datasource configuration is invalid"),
INTERNAL_SERVER_ERROR(500, 5000, "Internal server error while processing request"),
REPOSITORY_SAVE_FAILED(500, 5001, "Repository save failed"),
PLUGIN_INSTALLATION_FAILED_DOWNLOAD_ERROR(500, 5002, "Due to error in downloading the plugin from remote repository, plugin installation has failed. Check the jar location and try again"),

View File

@ -0,0 +1,34 @@
package com.appsmith.server.helpers;
import com.appsmith.external.plugins.PluginExecutor;
import com.appsmith.server.domains.Plugin;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
import org.pf4j.PluginManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import java.util.List;
@Component
public class PluginExecutorHelper {
private PluginManager pluginManager;
@Autowired
public PluginExecutorHelper(PluginManager pluginManager) {
this.pluginManager = pluginManager;
}
public Mono<PluginExecutor> getPluginExecutor(Mono<Plugin> pluginMono) {
return pluginMono.flatMap(plugin -> {
List<PluginExecutor> executorList = pluginManager.getExtensions(PluginExecutor.class, plugin.getExecutorClass());
if (executorList.isEmpty()) {
return Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, "plugin", plugin.getExecutorClass()));
}
return Mono.just(executorList.get(0));
}
);
}
}

View File

@ -7,14 +7,11 @@ import com.appsmith.external.models.Param;
import com.appsmith.external.plugins.PluginExecutor;
import com.appsmith.server.constants.AnalyticsEvents;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.Action;
import com.appsmith.server.domains.Datasource;
import com.appsmith.server.domains.Page;
import com.appsmith.server.domains.PageAction;
import com.appsmith.server.domains.Plugin;
import com.appsmith.server.domains.*;
import com.appsmith.server.dtos.ExecuteActionDTO;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.helpers.PluginExecutorHelper;
import com.appsmith.server.repositories.ActionRepository;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
@ -22,7 +19,6 @@ import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.Mustache;
import com.github.mustachejava.MustacheFactory;
import lombok.extern.slf4j.Slf4j;
import org.pf4j.PluginManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.core.convert.MongoConverter;
@ -37,11 +33,7 @@ import javax.validation.constraints.NotNull;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;
import static com.appsmith.server.helpers.BeanCopyUtils.copyNewFieldValuesIntoOldObject;
@ -56,9 +48,9 @@ public class ActionServiceImpl extends BaseService<ActionRepository, Action, Str
private final DatasourceService datasourceService;
private final PluginService pluginService;
private final PageService pageService;
private final PluginManager pluginManager;
private final ObjectMapper objectMapper;
private final DatasourceContextService datasourceContextService;
private final PluginExecutorHelper pluginExecutorHelper;
@Autowired
public ActionServiceImpl(Scheduler scheduler,
@ -69,18 +61,18 @@ public class ActionServiceImpl extends BaseService<ActionRepository, Action, Str
DatasourceService datasourceService,
PluginService pluginService,
PageService pageService,
PluginManager pluginManager,
AnalyticsService analyticsService,
ObjectMapper objectMapper,
DatasourceContextService datasourceContextService) {
DatasourceContextService datasourceContextService,
PluginExecutorHelper pluginExecutorHelper) {
super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository, analyticsService);
this.repository = repository;
this.datasourceService = datasourceService;
this.pluginService = pluginService;
this.pageService = pageService;
this.pluginManager = pluginManager;
this.objectMapper = objectMapper;
this.datasourceContextService = datasourceContextService;
this.pluginExecutorHelper = pluginExecutorHelper;
}
/**
@ -320,14 +312,7 @@ public class ActionServiceImpl extends BaseService<ActionRepository, Action, Str
.flatMap(datasource -> pluginService.findById(datasource.getPluginId()))
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, "plugin")));
Mono<PluginExecutor> pluginExecutorMono = pluginMono.flatMap(plugin -> {
List<PluginExecutor> executorList = pluginManager.getExtensions(PluginExecutor.class, plugin.getExecutorClass());
if (executorList.isEmpty()) {
return Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, "plugin", plugin.getExecutorClass()));
}
return Mono.just(executorList.get(0));
}
);
Mono<PluginExecutor> pluginExecutorMono = pluginExecutorHelper.getPluginExecutor(pluginMono);
// 4. Execute the query
return actionMono

View File

@ -2,18 +2,15 @@ package com.appsmith.server.services;
import com.appsmith.external.plugins.PluginExecutor;
import com.appsmith.server.domains.Datasource;
import com.appsmith.server.domains.DatasourceContext;
import com.appsmith.server.domains.Plugin;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.domains.DatasourceContext;
import com.appsmith.server.helpers.PluginExecutorHelper;
import lombok.extern.slf4j.Slf4j;
import org.pf4j.PluginManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
@ -23,14 +20,16 @@ public class DatasourceContextServiceImpl implements DatasourceContextService {
//This is DatasourceId mapped to the DatasourceContext
Map<String, DatasourceContext> datasourceContextMap;
private final DatasourceService datasourceService;
private final PluginManager pluginManager;
private final PluginService pluginService;
private final PluginExecutorHelper pluginExecutorHelper;
@Autowired
public DatasourceContextServiceImpl(DatasourceService datasourceService, PluginManager pluginManager, PluginService pluginService) {
public DatasourceContextServiceImpl(DatasourceService datasourceService,
PluginService pluginService,
PluginExecutorHelper pluginExecutorHelper) {
this.datasourceService = datasourceService;
this.pluginManager = pluginManager;
this.pluginService = pluginService;
this.pluginExecutorHelper = pluginExecutorHelper;
this.datasourceContextMap = new HashMap<>();
}
@ -57,14 +56,7 @@ public class DatasourceContextServiceImpl implements DatasourceContextService {
.flatMap(resource -> pluginService.findById(resource.getPluginId()));
//Datasource Context has not been created for this resource on this machine. Create one now.
Mono<PluginExecutor> pluginExecutorMono = pluginMono.flatMap(plugin -> {
List<PluginExecutor> executorList = pluginManager.getExtensions(PluginExecutor.class, plugin.getExecutorClass());
if (executorList.isEmpty()) {
return Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, "plugin", plugin.getExecutorClass()));
}
return Mono.just(executorList.get(0));
}
);
Mono<PluginExecutor> pluginExecutorMono = pluginExecutorHelper.getPluginExecutor(pluginMono);
return Mono.zip(datasourceMono, pluginExecutorMono, ((datasource1, pluginExecutor) -> {
Object connection = pluginExecutor.datasourceCreate(datasource1.getDatasourceConfiguration());
@ -92,14 +84,7 @@ public class DatasourceContextServiceImpl implements DatasourceContextService {
.flatMap(datasource -> pluginService.findById(datasource.getPluginId()));
//Datasource Context has not been created for this resource on this machine. Create one now.
Mono<PluginExecutor> pluginExecutorMono = pluginMono.flatMap(plugin -> {
List<PluginExecutor> executorList = pluginManager.getExtensions(PluginExecutor.class, plugin.getExecutorClass());
if (executorList.isEmpty()) {
return Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, "plugin", plugin.getExecutorClass()));
}
return Mono.just(executorList.get(0));
}
);
Mono<PluginExecutor> pluginExecutorMono = pluginExecutorHelper.getPluginExecutor(pluginMono);
return Mono.zip(datasourceMono, pluginExecutorMono, ((datasource, pluginExecutor) -> {
pluginExecutor.datasourceDestroy(datasourceContext.getConnection());

View File

@ -1,11 +1,13 @@
package com.appsmith.server.services;
import com.appsmith.external.plugins.PluginExecutor;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.Datasource;
import com.appsmith.server.domains.Organization;
import com.appsmith.server.domains.User;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.helpers.PluginExecutorHelper;
import com.appsmith.server.repositories.DatasourceRepository;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
@ -32,6 +34,8 @@ public class DatasourceServiceImpl extends BaseService<DatasourceRepository, Dat
private final OrganizationService organizationService;
private final SessionUserService sessionUserService;
private final ObjectMapper objectMapper;
private final PluginService pluginService;
private final PluginExecutorHelper pluginExecutorHelper;
@Autowired
public DatasourceServiceImpl(Scheduler scheduler,
@ -42,12 +46,16 @@ public class DatasourceServiceImpl extends BaseService<DatasourceRepository, Dat
OrganizationService organizationService,
AnalyticsService analyticsService,
SessionUserService sessionUserService,
ObjectMapper objectMapper) {
ObjectMapper objectMapper,
PluginService pluginService,
PluginExecutorHelper pluginExecutorHelper) {
super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository, analyticsService);
this.repository = repository;
this.organizationService = organizationService;
this.sessionUserService = sessionUserService;
this.objectMapper = objectMapper;
this.pluginService = pluginService;
this.pluginExecutorHelper = pluginExecutorHelper;
}
@Override
@ -61,10 +69,13 @@ public class DatasourceServiceImpl extends BaseService<DatasourceRepository, Dat
if (datasource.getDatasourceConfiguration() == null) {
return Mono.error(new AppsmithException(AppsmithError.NO_CONFIGURATION_FOUND_IN_DATASOURCE));
}
Mono<PluginExecutor> pluginExecutorMono = pluginExecutorHelper.getPluginExecutor(pluginService.findById(datasource.getPluginId()));
Mono<User> userMono = sessionUserService.getCurrentUser();
Mono<Organization> organizationMono = userMono.flatMap(user -> organizationService.findByIdAndPluginsPluginId(user.getCurrentOrganizationId(), datasource.getPluginId()));
Mono<Organization> organizationMono = userMono
.flatMap(user -> organizationService.findByIdAndPluginsPluginId(user.getCurrentOrganizationId(), datasource.getPluginId()))
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.PLUGIN_NOT_INSTALLED, datasource.getPluginId())));
//Add organization id to the datasource.
Mono<Datasource> updatedDatasourceMono = organizationMono
@ -73,12 +84,42 @@ public class DatasourceServiceImpl extends BaseService<DatasourceRepository, Dat
return datasource;
});
return organizationMono
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.PLUGIN_NOT_INSTALLED, datasource.getPluginId())))
.then(updatedDatasourceMono)
return Mono.zip(updatedDatasourceMono, pluginExecutorMono)
.flatMap(tuple -> {
Datasource datasource1 = tuple.getT1();
PluginExecutor pluginExecutor = tuple.getT2();
Boolean isValid = pluginExecutor.isDatasourceValid(datasource1.getDatasourceConfiguration());
if (isValid) {
return Mono.just(datasource1);
}
return Mono.error(new AppsmithException(AppsmithError.INVALID_DATASOURCE_CONFIGURATION));
})
.flatMap(super::create);
}
@Override
public Mono<Datasource> update(String id, Datasource datasource) {
if (datasource.getDatasourceConfiguration() != null) {
Mono<Datasource> datasourceMono = repository.findById(id)
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.DATASOURCE, id)));
Mono<PluginExecutor> pluginExecutorMono = datasourceMono
.flatMap(datasource1 -> pluginExecutorHelper.getPluginExecutor(pluginService.findById(datasource1.getPluginId())));
return pluginExecutorMono
.flatMap(pluginExecutor -> {
Boolean isValid = pluginExecutor.isDatasourceValid(datasource.getDatasourceConfiguration());
if (!isValid) {
return Mono.error(new AppsmithException(AppsmithError.INVALID_DATASOURCE_CONFIGURATION));
}
return Mono.just(datasource);
})
.flatMap(datasource1 -> super.update(id, datasource1));
}
return super.update(id, datasource);
}
@Override
public Mono<Datasource> findByName(String name) {
return repository.findByName(name);

View File

@ -119,7 +119,7 @@ public class SeedMongoData {
})
.flatMap(pageRepository::save)
)
.subscribe(obj -> log.info("Last Saved Object: " + obj));
.blockLast();
};
}
}

View File

@ -10,6 +10,7 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@ -19,6 +20,7 @@ import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
@DirtiesContext
public class ApplicationServiceTest {
@Autowired

View File

@ -1,18 +1,26 @@
package com.appsmith.server.services;
import com.appsmith.external.models.ActionConfiguration;
import com.appsmith.external.models.ActionExecutionResult;
import com.appsmith.external.models.DatasourceConfiguration;
import com.appsmith.external.plugins.PluginExecutor;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.Datasource;
import com.appsmith.server.domains.Plugin;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.helpers.PluginExecutorHelper;
import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit4.SpringRunner;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@ -22,6 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
@DirtiesContext
public class DatasourceServiceTest {
@Autowired
@ -30,12 +39,43 @@ public class DatasourceServiceTest {
@Autowired
PluginService pluginService;
@MockBean
PluginExecutorHelper pluginExecutorHelper;
class TestPluginExecutor implements PluginExecutor {
@Override
public Mono<ActionExecutionResult> execute(Object connection, DatasourceConfiguration datasourceConfiguration, ActionConfiguration actionConfiguration) {
System.out.println("In the execute");
return null;
}
@Override
public Object datasourceCreate(DatasourceConfiguration datasourceConfiguration) {
System.out.println("In the datasourceCreate");
return null;
}
@Override
public void datasourceDestroy(Object connection) {
System.out.println("In the datasourceDestroy");
}
@Override
public Boolean isDatasourceValid(DatasourceConfiguration datasourceConfiguration) {
System.out.println("In the datasourceValidate");
return true;
}
}
@Before
public void setup() {
}
@Test
@WithMockUser(username = "api_user")
@WithMockUser(username = "api_user", roles = "USER")
public void createDatasourceWithNullPluginId() {
Datasource datasource = new Datasource();
Mono<Datasource> datasourceMono = Mono.just(datasource)
@ -48,7 +88,7 @@ public class DatasourceServiceTest {
}
@Test
@WithMockUser(username = "api_user")
@WithMockUser(username = "api_user", roles = "USER")
public void createDatasourceWithId() {
Datasource datasource = new Datasource();
datasource.setId("randomId");
@ -62,12 +102,14 @@ public class DatasourceServiceTest {
}
@Test
@WithMockUser(username = "api_user")
@WithMockUser(username = "api_user", roles = "USER")
public void createDatasourceNotInstalledPlugin() {
Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())).thenReturn(Mono.just(new TestPluginExecutor()));
Mono<Plugin> pluginMono = pluginService.findByName("Not Installed Plugin Name");
Datasource datasource = new Datasource();
DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration();
datasourceConfiguration.setDatabaseName("randomDbname");
datasourceConfiguration.setUrl("http://test.com");
datasource.setDatasourceConfiguration(datasourceConfiguration);
Mono<Datasource> datasourceMono = pluginMono.map(plugin -> {
@ -83,13 +125,16 @@ public class DatasourceServiceTest {
}
@Test
@WithMockUser(username = "api_user")
@WithMockUser(username = "api_user", roles = "USER")
public void createDatasourceValid() {
Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())).thenReturn(Mono.just(new TestPluginExecutor()));
Mono<Plugin> pluginMono = pluginService.findByName("Installed Plugin Name");
Datasource datasource = new Datasource();
datasource.setName("test datasource name");
DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration();
datasourceConfiguration.setDatabaseName("randomDbname");
datasourceConfiguration.setUrl("http://test.com");
datasource.setDatasourceConfiguration(datasourceConfiguration);
Mono<Datasource> datasourceMono = pluginMono.map(plugin -> {
datasource.setPluginId(plugin.getId());

View File

@ -15,6 +15,7 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@ -26,6 +27,7 @@ import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
@DirtiesContext
public class LayoutServiceTest {
@Autowired
LayoutService layoutService;

View File

@ -10,14 +10,17 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit4.SpringRunner;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringRunner.class)
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
@DirtiesContext
public class OrganizationServiceTest {
@Autowired

View File

@ -13,15 +13,18 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit4.SpringRunner;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringRunner.class)
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
@Slf4j
@DirtiesContext
public class PageServiceTest {
@Autowired
PageService pageService;

View File

@ -9,6 +9,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@ -17,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringRunner.class)
@SpringBootTest
@DirtiesContext
public class UserServiceTest {
@Autowired