Add Integration Tests testing the (auto) configuration of custom import and export Resources via HTTP to a REST service.

Resolves gh-92.
This commit is contained in:
John Blum
2020-07-22 23:04:37 -07:00
parent 73af4f0ccd
commit 0cd2f4df1b

View File

@@ -0,0 +1,395 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package org.springframework.geode.boot.autoconfigure.data;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.StreamSupport;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.client.ClientRegionShortcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Profile;
import org.springframework.core.io.Resource;
import org.springframework.data.gemfire.GemfireTemplate;
import org.springframework.data.gemfire.config.annotation.EnableEntityDefinedRegions;
import org.springframework.data.gemfire.tests.mock.annotation.EnableGemFireMockObjects;
import org.springframework.data.gemfire.util.CollectionUtils;
import org.springframework.geode.core.io.ResourceReadException;
import org.springframework.geode.core.io.ResourceReader;
import org.springframework.geode.core.io.ResourceWriteException;
import org.springframework.geode.core.io.ResourceWriter;
import org.springframework.geode.data.CacheDataImporterExporter;
import org.springframework.geode.data.support.ResourceCapableCacheDataImporterExporter.AbstractExportResourceResolver;
import org.springframework.geode.data.support.ResourceCapableCacheDataImporterExporter.AbstractImportResourceResolver;
import org.springframework.geode.data.support.ResourceCapableCacheDataImporterExporter.ImportResourceResolver;
import org.springframework.geode.pdx.PdxInstanceWrapper;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import example.app.golf.model.Golfer;
/**
* Integration Tests testing custom {@link ResourceReader} and {@link ResourceWriter} to import/export data from/to
* a web service in a cloud environment.
*
* @author John Blum
* @see org.junit.Test
* @see org.springframework.boot.autoconfigure.SpringBootApplication
* @see org.springframework.boot.test.context.SpringBootTest
* @see org.springframework.boot.web.server.LocalServerPort
* @see org.springframework.context.annotation.Bean
* @see org.springframework.context.annotation.Profile
* @see org.springframework.core.io.Resource
* @see org.springframework.geode.config.annotation.EnableClusterAware
* @see org.springframework.geode.data.CacheDataImporterExporter
* @see org.springframework.test.context.ActiveProfiles
* @see org.springframework.test.context.junit4.SpringRunner
* @since 1.3.1
*/
@ActiveProfiles("NET-IMPORT-EXPORT")
@RunWith(SpringRunner.class)
@SpringBootTest(
properties = {
"spring.boot.data.gemfire.cache.data.export.enabled=true",
"spring.boot.data.gemfire.cache.data.export.resource.location=/cache/#{#regionName}/data/export",
"spring.boot.data.gemfire.cache.data.import.active-profiles=NET-IMPORT-EXPORT",
"spring.boot.data.gemfire.cache.data.import.resource.location=/cache/#{#regionName}/data/import",
"spring.session.store-type=NONE",
//"spring.boot.data.gemfire.cache.data.import.phase=2147483647",
//"spring.boot.data.gemfire.cache.data.export.resource.location=http://localhost:#{#env['local.server.port']}/cache/#{#regionName}/data/export",
//"spring.boot.data.gemfire.cache.data.import.resource.location=http://localhost:#{#env['local.server.port']}/cache/#{#regionName}/data/import",
},
webEnvironment = SpringBootTest.WebEnvironment.MOCK
)
@AutoConfigureMockMvc
@SuppressWarnings("unused")
public class RestServiceCacheDataImportExportIntegrationTests {
private static final boolean DEBUG = false;
//@LocalServerPort
//private int httpServerPort;
@Autowired
private CacheDataImporterExporter importerExporter;
@Autowired
@Qualifier("golfersTemplate")
private GemfireTemplate golfersTemplate;
private static void assertGolfer(Golfer golfer, long id, String name, int handicap) {
assertThat(golfer).isNotNull();
assertThat(golfer.getId()).isEqualTo(id);
assertThat(golfer.getName()).isEqualTo(name);
assertThat(golfer.getHandicap()).isEqualTo(handicap);
}
private static Golfer findById(Iterable<Golfer> golfers, long id) {
return StreamSupport.stream(CollectionUtils.nullSafeIterable(golfers).spliterator(), false)
.filter(golfer -> golfer.getId().equals(id))
.findFirst()
.orElse(null);
}
private static void log(String message, Object... args) {
if (DEBUG) {
System.err.printf(message, args);
System.err.flush();
}
}
private Golfer findById(long id) {
return this.golfersTemplate.get(id);
}
private Golfer save(Golfer golfer) {
this.golfersTemplate.put(golfer.getId(), golfer);
return golfer;
}
@Before
public void setup() {
//log("Local Server Port [%d]%n", this.httpServerPort);
//log("environment['local.server.port'] = %s%n", this.environment.getProperty("local.server.port"));
assertThat(this.golfersTemplate).isNotNull();
assertThat(this.golfersTemplate.getRegion()).isNotNull();
assertThat(this.golfersTemplate.getRegion().getName()).isEqualTo("Golfers");
assertThat(this.importerExporter).isNotNull();
//assertThat(this.httpServerPort).isNotZero();
//assertThat(this.environment.containsProperty("local.server.port")).isTrue();
//assertThat(this.environment.getProperty("local.server.port", Integer.class, -80))
// .isEqualTo(this.httpServerPort);
}
@After
public void tearDown() {
this.importerExporter.exportFrom(this.golfersTemplate.getRegion());
assertThat(CacheDataRestService.exportCalled.get()).isTrue();
}
@Test
public void golfersRegionContainsGolfers() {
assertThat(this.golfersTemplate.getRegion()).hasSize(2);
Golfer johnBlum = findById(1L);
assertGolfer(johnBlum, 1L, "John Blum", 12);
save(Golfer.newGolfer(johnBlum.getId(), "Jon Bloom").withHandicap(21));
Golfer moeHaroon = findById(2L);
assertGolfer(moeHaroon, 2L, "Moe Haroon", 10);
save(Golfer.newGolfer(moeHaroon.getId(), "Moiz Harpoon").withHandicap(1));
}
@Profile("NET-IMPORT-EXPORT")
@SpringBootApplication
@EnableGemFireMockObjects
@EnableEntityDefinedRegions(basePackageClasses = Golfer.class, clientRegionShortcut = ClientRegionShortcut.LOCAL)
static class TestGeodeConfiguration implements WebMvcConfigurer {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
CollectionUtils.nullSafeList(converters).stream()
.filter(AbstractJackson2HttpMessageConverter.class::isInstance)
.map(AbstractJackson2HttpMessageConverter.class::cast)
.map(AbstractJackson2HttpMessageConverter::getObjectMapper)
.forEach(objectMapper -> objectMapper.addMixIn(Golfer.class, ObjectTypeMetadataMixin.class));
}
@Bean
BeanPostProcessor mockGolfersRegionBeanPostProcessor() {
return new BeanPostProcessor() {
@Override
public @Nullable Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof Region && "Golfers".equals(beanName)) {
Region<?, ?> golfers = (Region<?, ?>) bean;
doAnswer(invocation -> Arrays.asList(golfers.get(1L), golfers.get(2L))).when(golfers).values();
}
return bean;
}
};
}
@Bean
CacheDataRestService cacheDataRestService() {
return new CacheDataRestService();
}
@Bean
ImportResourceResolver restServiceImportResourceResolver() {
return new RestServiceImportResourceResolver();
}
@Bean
ResourceReader restServiceResourceReader(MockMvc mvc) {
return new RestServiceResourceReader(mvc);
}
@Bean
ResourceWriter restServiceResourceWriter(MockMvc mvc) {
return new RestServiceResourceWriter(mvc);
}
}
@RestController
static class CacheDataRestService {
static final AtomicBoolean exportCalled = new AtomicBoolean(false);
/* /cache/#{#regionName}/data/import */
@GetMapping("/cache/{regionName}/data/import")
public List<Golfer> doOnImport(@PathVariable("regionName") String regionName) {
assertThat(regionName).isEqualTo("golfers");
return Arrays.asList(
Golfer.newGolfer(1L, "John Blum").withHandicap(12),
Golfer.newGolfer(2L, "Moe Haroon").withHandicap(10)
);
}
@PostMapping("/cache/{regionName}/data/export")
public void doOnExport(@PathVariable("regionName") String regionName, @RequestBody List<Golfer> golfers) {
assertThat(regionName).isEqualTo("golfers");
assertThat(golfers).hasSize(2);
Golfer jonBloom = findById(golfers, 1L);
assertGolfer(jonBloom, 1L, "Jon Bloom", 21);
Golfer moizHarpoon = findById(golfers, 2L);
assertGolfer(moizHarpoon, 2L, "Moiz Harpoon", 1);
exportCalled.set(true);
}
}
@JsonTypeInfo(
use = JsonTypeInfo.Id.CLASS,
include = JsonTypeInfo.As.PROPERTY,
property = PdxInstanceWrapper.AT_TYPE_FIELD_NAME
)
@SuppressWarnings("all")
interface ObjectTypeMetadataMixin { }
static class RestServiceExportResourceResolver extends AbstractExportResourceResolver {
@Override
protected @NonNull String getResourcePath() {
throw new UnsupportedOperationException("Not Implemented");
}
}
static class RestServiceImportResourceResolver extends AbstractImportResourceResolver {
@Override
protected Resource postProcess(Resource resource) {
Resource resourceSpy = spy(super.postProcess(resource));
doReturn(true).when(resourceSpy).exists();
doReturn(true).when(resourceSpy).isReadable();
return resourceSpy;
}
@Override
protected @NonNull String getResourcePath() {
throw new UnsupportedOperationException("Not Implemented");
}
}
static class RestServiceResourceReader implements ResourceReader {
private final MockMvc mvc;
RestServiceResourceReader(MockMvc mvc) {
Assert.notNull(mvc, "MockMvc must not be null");
this.mvc = mvc;
}
@Override
public @NonNull byte[] read(@NonNull Resource resource) {
try {
//String json = this.mvc.perform(get(resource.getURI()).accept(MediaType.APPLICATION_JSON))
String json = this.mvc.perform(get("/cache/{regionName}/data/import", "golfers")
.accept(MediaType.APPLICATION_JSON))
.andReturn()
.getResponse()
.getContentAsString();
return json.getBytes();
}
catch (Exception cause) {
throw new ResourceReadException(String.format("Failed to read from resource at location [%s]",
resource.getDescription()), cause);
}
}
}
static class RestServiceResourceWriter implements ResourceWriter {
private final MockMvc mvc;
RestServiceResourceWriter(MockMvc mvc) {
Assert.notNull(mvc, "MockMvc must not be null");
this.mvc = mvc;
}
@Override
public void write(@NonNull Resource resource, byte[] data) {
try {
String json = new String(data);
//this.mvc.perform(post(resource.getURI()).content(data).contentType(MediaType.APPLICATION_JSON))
this.mvc.perform(post("/cache/{regionName}/data/export", "golfers")
.contentType(MediaType.APPLICATION_JSON)
.content(data))
.andExpect(status().isOk());
}
catch (Exception cause) {
throw new ResourceWriteException(String.format("Failed to write to resource at location [%s]",
resource.getDescription()), cause);
}
}
}
}