mirror of
https://gitee.com/dcren/initializr.git
synced 2025-09-19 01:58:16 +08:00
Initiate initializr documentation
This commit commit adds restdocs and stub generators and initiate a reference guide for Initializr. Most of the controller tests now use MockMvc via a custom version of the MockMvcClientHttpRequestFactory (from spring-test). The snippet names are auto-generated in the form <HttpMethod>/<path>[/queries(/<name-value)*][/headers](/name-value)*] when there is a comma-separated value in a header it is abbreviated as <first-value>.MORE. Wiremock stubs are generated in the same form under snippets/stubs (with ".json" as the file extension). The controller tests that stayed as full stack use a different base class AbstractFullStackInitializrIntegrationTests. A long JSON body can be broken out into separate snippets for each field (or rather a list of fields supplied by the user). This feature was already used with hard-coded snippets in the wiki. See gh-295
This commit is contained in:

committed by
Stephane Nicoll

parent
6efcef1186
commit
b7d8d5c813
@@ -61,6 +61,11 @@
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-wiremock</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.spring.initializr</groupId>
|
||||
<artifactId>initializr-generator</artifactId>
|
||||
@@ -72,6 +77,11 @@
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.restdocs</groupId>
|
||||
<artifactId>spring-restdocs-mockmvc</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>xmlunit</groupId>
|
||||
<artifactId>xmlunit</artifactId>
|
||||
@@ -99,6 +109,18 @@
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-dependencies</artifactId>
|
||||
<version>Camden.RELEASE</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
|
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2012-2016 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
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.spring.initializr.web
|
||||
|
||||
import io.spring.initializr.web.AbstractInitializrIntegrationTests.Config
|
||||
|
||||
import org.junit.runner.RunWith
|
||||
import org.springframework.boot.context.embedded.LocalServerPort
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.test.context.junit4.SpringRunner
|
||||
|
||||
import static org.junit.Assert.assertTrue
|
||||
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT
|
||||
|
||||
/**
|
||||
* @author Stephane Nicoll
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = Config.class, webEnvironment = RANDOM_PORT)
|
||||
abstract class AbstractFullStackInitializrIntegrationTests extends AbstractInitializrIntegrationTests {
|
||||
|
||||
@LocalServerPort
|
||||
int port
|
||||
|
||||
String host = "localhost"
|
||||
|
||||
String createUrl(String context) {
|
||||
"http://${host}:${port}" + (context.startsWith('/') ? context : '/' + context)
|
||||
}
|
||||
|
||||
}
|
@@ -16,213 +16,49 @@
|
||||
|
||||
package io.spring.initializr.web
|
||||
|
||||
import java.nio.charset.Charset
|
||||
import io.spring.initializr.web.test.MockMvcClientHttpRequestFactory
|
||||
import io.spring.initializr.web.test.MockMvcClientHttpRequestFactoryTestExecutionListener
|
||||
|
||||
import io.spring.initializr.metadata.InitializrMetadata
|
||||
import io.spring.initializr.metadata.InitializrMetadataBuilder
|
||||
import io.spring.initializr.metadata.InitializrMetadataProvider
|
||||
import io.spring.initializr.metadata.InitializrProperties
|
||||
import io.spring.initializr.test.generator.ProjectAssert
|
||||
import io.spring.initializr.web.mapper.InitializrMetadataVersion
|
||||
import io.spring.initializr.web.support.DefaultInitializrMetadataProvider
|
||||
import org.json.JSONObject
|
||||
import org.junit.Rule
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.runner.RunWith
|
||||
import org.skyscreamer.jsonassert.JSONAssert
|
||||
import org.skyscreamer.jsonassert.JSONCompareMode
|
||||
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
|
||||
import org.springframework.boot.context.embedded.LocalServerPort
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.beans.factory.BeanFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
|
||||
import org.springframework.boot.web.client.RestTemplateCustomizer
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.core.io.ClassPathResource
|
||||
import org.springframework.http.HttpEntity
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.http.HttpMethod
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.test.context.junit4.SpringRunner
|
||||
import org.springframework.util.StreamUtils
|
||||
import org.springframework.web.client.RestTemplate
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import org.springframework.test.context.TestExecutionListeners
|
||||
import org.springframework.test.context.TestExecutionListeners.MergeMode
|
||||
|
||||
import static org.junit.Assert.assertTrue
|
||||
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT
|
||||
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment
|
||||
|
||||
/**
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = Config.class, webEnvironment = RANDOM_PORT)
|
||||
abstract class AbstractInitializrControllerIntegrationTests {
|
||||
@ContextConfiguration(classes = RestTemplateConfig)
|
||||
@TestExecutionListeners(mergeMode = MergeMode.MERGE_WITH_DEFAULTS, listeners = MockMvcClientHttpRequestFactoryTestExecutionListener)
|
||||
@AutoConfigureMockMvc
|
||||
@AutoConfigureRestDocs(outputDir="target/snippets", uriPort=80, uriHost="start.spring.io")
|
||||
abstract class AbstractInitializrControllerIntegrationTests extends AbstractInitializrIntegrationTests {
|
||||
|
||||
static final MediaType CURRENT_METADATA_MEDIA_TYPE = InitializrMetadataVersion.V2_1.mediaType
|
||||
|
||||
|
||||
@Rule
|
||||
public final TemporaryFolder folder = new TemporaryFolder()
|
||||
|
||||
@LocalServerPort
|
||||
protected int port
|
||||
|
||||
final RestTemplate restTemplate = new RestTemplate()
|
||||
String host = "start.spring.io"
|
||||
|
||||
@Autowired
|
||||
MockMvcClientHttpRequestFactory requests
|
||||
|
||||
String createUrl(String context) {
|
||||
"http://localhost:$port$context"
|
||||
}
|
||||
|
||||
String htmlHome() {
|
||||
def headers = new HttpHeaders()
|
||||
headers.setAccept([MediaType.TEXT_HTML])
|
||||
restTemplate.exchange(createUrl('/'), HttpMethod.GET, new HttpEntity<Void>(headers), String).body
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the 'Content-Type' header of the specified response.
|
||||
*/
|
||||
protected void validateContentType(ResponseEntity<String> response, MediaType expected) {
|
||||
def actual = response.headers.getContentType()
|
||||
assertTrue "Non compatible media-type, expected $expected, got $actual",
|
||||
actual.isCompatibleWith(expected)
|
||||
}
|
||||
|
||||
|
||||
protected void validateMetadata(ResponseEntity<String> response, MediaType mediaType,
|
||||
String version, JSONCompareMode compareMode) {
|
||||
validateContentType(response, mediaType)
|
||||
def json = new JSONObject(response.body)
|
||||
def expected = readMetadataJson(version)
|
||||
JSONAssert.assertEquals(expected, json, compareMode)
|
||||
}
|
||||
|
||||
protected void validateCurrentMetadata(ResponseEntity<String> response) {
|
||||
validateContentType(response, CURRENT_METADATA_MEDIA_TYPE)
|
||||
validateCurrentMetadata(new JSONObject(response.body))
|
||||
}
|
||||
|
||||
protected void validateCurrentMetadata(JSONObject json) {
|
||||
def expected = readMetadataJson('2.1.0')
|
||||
JSONAssert.assertEquals(expected, json, JSONCompareMode.STRICT)
|
||||
}
|
||||
|
||||
private JSONObject readMetadataJson(String version) {
|
||||
readJsonFrom("metadata/test-default-$version" + ".json")
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link ProjectAssert} for the following archive content.
|
||||
*/
|
||||
protected ProjectAssert zipProjectAssert(byte[] content) {
|
||||
projectAssert(content, ArchiveType.ZIP)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link ProjectAssert} for the following TGZ archive.
|
||||
*/
|
||||
protected ProjectAssert tgzProjectAssert(byte[] content) {
|
||||
projectAssert(content, ArchiveType.TGZ)
|
||||
}
|
||||
|
||||
protected ProjectAssert downloadZip(String context) {
|
||||
def body = downloadArchive(context)
|
||||
zipProjectAssert(body)
|
||||
}
|
||||
|
||||
protected ProjectAssert downloadTgz(String context) {
|
||||
def body = downloadArchive(context)
|
||||
tgzProjectAssert(body)
|
||||
}
|
||||
|
||||
protected byte[] downloadArchive(String context) {
|
||||
restTemplate.getForObject(createUrl(context), byte[])
|
||||
}
|
||||
|
||||
protected ResponseEntity<String> invokeHome(String userAgentHeader, String... acceptHeaders) {
|
||||
execute('/', String, userAgentHeader, acceptHeaders)
|
||||
}
|
||||
|
||||
protected <T> ResponseEntity<T> execute(String contextPath, Class<T> responseType,
|
||||
String userAgentHeader, String... acceptHeaders) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
if (userAgentHeader) {
|
||||
headers.set("User-Agent", userAgentHeader);
|
||||
}
|
||||
if (acceptHeaders) {
|
||||
List<MediaType> mediaTypes = new ArrayList<>()
|
||||
for (String acceptHeader : acceptHeaders) {
|
||||
mediaTypes.add(MediaType.parseMediaType(acceptHeader))
|
||||
}
|
||||
headers.setAccept(mediaTypes)
|
||||
} else {
|
||||
headers.setAccept(Collections.emptyList())
|
||||
}
|
||||
return restTemplate.exchange(createUrl(contextPath),
|
||||
HttpMethod.GET, new HttpEntity<Void>(headers), responseType)
|
||||
}
|
||||
|
||||
protected ProjectAssert projectAssert(byte[] content, ArchiveType archiveType) {
|
||||
def archiveFile = writeArchive(content)
|
||||
|
||||
def project = folder.newFolder()
|
||||
switch (archiveType) {
|
||||
case ArchiveType.ZIP:
|
||||
new AntBuilder().unzip(dest: project, src: archiveFile)
|
||||
break
|
||||
case ArchiveType.TGZ:
|
||||
new AntBuilder().untar(dest: project, src: archiveFile, compression: 'gzip')
|
||||
break
|
||||
}
|
||||
new ProjectAssert(project)
|
||||
}
|
||||
|
||||
protected File writeArchive(byte[] body) {
|
||||
def archiveFile = folder.newFile()
|
||||
def stream = new FileOutputStream(archiveFile)
|
||||
try {
|
||||
stream.write(body)
|
||||
} finally {
|
||||
stream.close()
|
||||
}
|
||||
archiveFile
|
||||
}
|
||||
|
||||
protected JSONObject readJsonFrom(String path) {
|
||||
def resource = new ClassPathResource(path)
|
||||
def stream = resource.inputStream
|
||||
try {
|
||||
def json = StreamUtils.copyToString(stream, Charset.forName('UTF-8'))
|
||||
|
||||
// Let's parse the port as it is random
|
||||
def content = json.replaceAll('@port@', String.valueOf(this.port))
|
||||
new JSONObject(content)
|
||||
} finally {
|
||||
stream.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private enum ArchiveType {
|
||||
ZIP,
|
||||
|
||||
TGZ
|
||||
context.startsWith('/') ? context : '/' + context
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
static class Config {
|
||||
static class RestTemplateConfig {
|
||||
|
||||
@Bean
|
||||
InitializrMetadataProvider initializrMetadataProvider(InitializrProperties properties) {
|
||||
new DefaultInitializrMetadataProvider(
|
||||
InitializrMetadataBuilder.fromInitializrProperties(properties).build(),
|
||||
new RestTemplate()) {
|
||||
@Override
|
||||
protected void updateInitializrMetadata(InitializrMetadata metadata) {
|
||||
null // Disable metadata fetching from spring.io
|
||||
}
|
||||
RestTemplateCustomizer mockMvcCustomizer(BeanFactory beanFactory) {
|
||||
{ template ->
|
||||
template.setRequestFactory(beanFactory.getBean(MockMvcClientHttpRequestFactory))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* Copyright 2012-2016 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
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.spring.initializr.web
|
||||
|
||||
import io.spring.initializr.metadata.InitializrMetadata
|
||||
import io.spring.initializr.metadata.InitializrMetadataBuilder
|
||||
import io.spring.initializr.metadata.InitializrMetadataProvider
|
||||
import io.spring.initializr.metadata.InitializrProperties
|
||||
import io.spring.initializr.test.generator.ProjectAssert
|
||||
import io.spring.initializr.web.mapper.InitializrMetadataVersion
|
||||
import io.spring.initializr.web.support.DefaultInitializrMetadataProvider
|
||||
|
||||
import org.json.JSONObject
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.runner.RunWith
|
||||
import org.skyscreamer.jsonassert.JSONAssert
|
||||
import org.skyscreamer.jsonassert.JSONCompareMode
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.core.io.ClassPathResource
|
||||
import org.springframework.http.HttpEntity
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.http.HttpMethod
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.test.context.junit4.SpringRunner
|
||||
import org.springframework.util.StreamUtils
|
||||
import org.springframework.web.client.RestTemplate
|
||||
|
||||
import java.nio.charset.Charset
|
||||
|
||||
import static org.junit.Assert.assertTrue
|
||||
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT
|
||||
|
||||
/**
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = Config.class)
|
||||
abstract class AbstractInitializrIntegrationTests {
|
||||
|
||||
static final MediaType CURRENT_METADATA_MEDIA_TYPE = InitializrMetadataVersion.V2_1.mediaType
|
||||
|
||||
@Rule
|
||||
public final TemporaryFolder folder = new TemporaryFolder()
|
||||
|
||||
@Autowired
|
||||
private RestTemplateBuilder restTemplateBuilder
|
||||
|
||||
RestTemplate restTemplate
|
||||
|
||||
@Before
|
||||
void before() {
|
||||
restTemplate = restTemplateBuilder.build()
|
||||
}
|
||||
|
||||
abstract String createUrl(String context)
|
||||
|
||||
String htmlHome() {
|
||||
def headers = new HttpHeaders()
|
||||
headers.setAccept([MediaType.TEXT_HTML])
|
||||
restTemplate.exchange(createUrl('/'), HttpMethod.GET, new HttpEntity<Void>(headers), String).body
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the 'Content-Type' header of the specified response.
|
||||
*/
|
||||
protected void validateContentType(ResponseEntity<String> response, MediaType expected) {
|
||||
def actual = response.headers.getContentType()
|
||||
assertTrue "Non compatible media-type, expected $expected, got $actual",
|
||||
actual.isCompatibleWith(expected)
|
||||
}
|
||||
|
||||
|
||||
protected void validateMetadata(ResponseEntity<String> response, MediaType mediaType,
|
||||
String version, JSONCompareMode compareMode) {
|
||||
validateContentType(response, mediaType)
|
||||
def json = new JSONObject(response.body)
|
||||
def expected = readMetadataJson(version)
|
||||
JSONAssert.assertEquals(expected, json, compareMode)
|
||||
}
|
||||
|
||||
protected void validateCurrentMetadata(ResponseEntity<String> response) {
|
||||
validateContentType(response, CURRENT_METADATA_MEDIA_TYPE)
|
||||
validateCurrentMetadata(new JSONObject(response.body))
|
||||
}
|
||||
|
||||
protected void validateCurrentMetadata(JSONObject json) {
|
||||
def expected = readMetadataJson('2.1.0')
|
||||
JSONAssert.assertEquals(expected, json, JSONCompareMode.STRICT)
|
||||
}
|
||||
|
||||
private JSONObject readMetadataJson(String version) {
|
||||
readJsonFrom("metadata/test-default-$version" + ".json")
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link ProjectAssert} for the following archive content.
|
||||
*/
|
||||
protected ProjectAssert zipProjectAssert(byte[] content) {
|
||||
projectAssert(content, ArchiveType.ZIP)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link ProjectAssert} for the following TGZ archive.
|
||||
*/
|
||||
protected ProjectAssert tgzProjectAssert(byte[] content) {
|
||||
projectAssert(content, ArchiveType.TGZ)
|
||||
}
|
||||
|
||||
protected ProjectAssert downloadZip(String context) {
|
||||
def body = downloadArchive(context)
|
||||
zipProjectAssert(body)
|
||||
}
|
||||
|
||||
protected ProjectAssert downloadTgz(String context) {
|
||||
def body = downloadArchive(context)
|
||||
tgzProjectAssert(body)
|
||||
}
|
||||
|
||||
protected byte[] downloadArchive(String context) {
|
||||
restTemplate.getForObject(createUrl(context), byte[])
|
||||
}
|
||||
|
||||
protected ResponseEntity<String> invokeHome(String userAgentHeader, String... acceptHeaders) {
|
||||
execute('/', String, userAgentHeader, acceptHeaders)
|
||||
}
|
||||
|
||||
protected <T> ResponseEntity<T> execute(String contextPath, Class<T> responseType,
|
||||
String userAgentHeader, String... acceptHeaders) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
if (userAgentHeader) {
|
||||
headers.set("User-Agent", userAgentHeader);
|
||||
}
|
||||
if (acceptHeaders) {
|
||||
List<MediaType> mediaTypes = new ArrayList<>()
|
||||
for (String acceptHeader : acceptHeaders) {
|
||||
mediaTypes.add(MediaType.parseMediaType(acceptHeader))
|
||||
}
|
||||
headers.setAccept(mediaTypes)
|
||||
} else {
|
||||
headers.setAccept(Collections.emptyList())
|
||||
}
|
||||
return restTemplate.exchange(createUrl(contextPath),
|
||||
HttpMethod.GET, new HttpEntity<Void>(headers), responseType)
|
||||
}
|
||||
|
||||
protected ProjectAssert projectAssert(byte[] content, ArchiveType archiveType) {
|
||||
def archiveFile = writeArchive(content)
|
||||
|
||||
def project = folder.newFolder()
|
||||
switch (archiveType) {
|
||||
case ArchiveType.ZIP:
|
||||
new AntBuilder().unzip(dest: project, src: archiveFile)
|
||||
break
|
||||
case ArchiveType.TGZ:
|
||||
new AntBuilder().untar(dest: project, src: archiveFile, compression: 'gzip')
|
||||
break
|
||||
}
|
||||
new ProjectAssert(project)
|
||||
}
|
||||
|
||||
protected File writeArchive(byte[] body) {
|
||||
def archiveFile = folder.newFile()
|
||||
def stream = new FileOutputStream(archiveFile)
|
||||
try {
|
||||
stream.write(body)
|
||||
} finally {
|
||||
stream.close()
|
||||
}
|
||||
archiveFile
|
||||
}
|
||||
|
||||
protected JSONObject readJsonFrom(String path) {
|
||||
def resource = new ClassPathResource(path)
|
||||
def stream = resource.inputStream
|
||||
try {
|
||||
def json = StreamUtils.copyToString(stream, Charset.forName('UTF-8'))
|
||||
String placeholder = ""
|
||||
if (this.hasProperty("host")) {
|
||||
placeholder = "$host"
|
||||
}
|
||||
if (this.hasProperty("port")) {
|
||||
placeholder = "$host:$port"
|
||||
}
|
||||
// Let's parse the port as it is random
|
||||
// TODO: put the port back somehow so it appears in stubs
|
||||
def content = json.replaceAll('@host@', placeholder)
|
||||
new JSONObject(content)
|
||||
} finally {
|
||||
stream.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private enum ArchiveType {
|
||||
ZIP,
|
||||
|
||||
TGZ
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
InitializrMetadataProvider initializrMetadataProvider(InitializrProperties properties) {
|
||||
new DefaultInitializrMetadataProvider(
|
||||
InitializrMetadataBuilder.fromInitializrProperties(properties).build(),
|
||||
new RestTemplate()) {
|
||||
@Override
|
||||
protected void updateInitializrMetadata(InitializrMetadata metadata) {
|
||||
null // Disable metadata fetching from spring.io
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -45,7 +45,7 @@ class MainControllerEnvIntegrationTests extends AbstractInitializrControllerInte
|
||||
void doNotForceSsl() {
|
||||
ResponseEntity<String> response = invokeHome('curl/1.2.4', "*/*")
|
||||
String body = response.getBody()
|
||||
assertTrue "Must not force https", body.contains("http://localhost:$port/")
|
||||
assertTrue "Must not force https", body.contains("http://start.spring.io/")
|
||||
assertFalse "Must not force https", body.contains('https://')
|
||||
}
|
||||
|
||||
|
@@ -74,7 +74,7 @@ class MainControllerIntegrationTests extends AbstractInitializrControllerIntegra
|
||||
@Test
|
||||
void dependencyInRange() {
|
||||
def biz = new Dependency(id: 'biz', groupId: 'org.acme',
|
||||
artifactId: 'biz', version: '1.3.5', scope: 'runtime')
|
||||
artifactId: 'biz', version: '1.3.5', scope: 'runtime')
|
||||
downloadTgz('/starter.tgz?style=org.acme:biz&bootVersion=1.2.1.RELEASE').isJavaProject().isMavenProject()
|
||||
.hasStaticAndTemplatesResources(false).pomAssert()
|
||||
.hasDependenciesCount(2)
|
||||
@@ -153,7 +153,8 @@ class MainControllerIntegrationTests extends AbstractInitializrControllerIntegra
|
||||
}
|
||||
|
||||
@Test
|
||||
void metadataWithNoAcceptHeader() { // rest template sets application/json by default
|
||||
void metadataWithNoAcceptHeader() {
|
||||
// rest template sets application/json by default
|
||||
ResponseEntity<String> response = invokeHome(null, '*/*')
|
||||
validateCurrentMetadata(response)
|
||||
}
|
||||
@@ -167,6 +168,9 @@ class MainControllerIntegrationTests extends AbstractInitializrControllerIntegra
|
||||
|
||||
@Test
|
||||
void metadataWithV2AcceptHeader() {
|
||||
requests.setFields("_links.maven-project", "dependencies.values[0]", "type.values[0]",
|
||||
"javaVersion.values[0]", "packaging.values[0]",
|
||||
"bootVersion.values[0]", "language.values[0]");
|
||||
ResponseEntity<String> response = invokeHome(null, 'application/vnd.initializr.v2+json')
|
||||
validateMetadata(response, InitializrMetadataVersion.V2.mediaType, '2.0.0', JSONCompareMode.STRICT)
|
||||
}
|
||||
|
@@ -16,15 +16,16 @@
|
||||
|
||||
package io.spring.initializr.web.project
|
||||
|
||||
import static org.junit.Assert.assertEquals
|
||||
import io.spring.initializr.metadata.InitializrMetadata
|
||||
import io.spring.initializr.metadata.InitializrMetadataBuilder
|
||||
import io.spring.initializr.metadata.InitializrMetadataProvider
|
||||
import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests
|
||||
import io.spring.initializr.web.AbstractFullStackInitializrIntegrationTests
|
||||
|
||||
import org.json.JSONObject
|
||||
import org.junit.Test
|
||||
import org.skyscreamer.jsonassert.JSONAssert
|
||||
import org.skyscreamer.jsonassert.JSONCompareMode
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.core.io.UrlResource
|
||||
import org.springframework.http.HttpStatus
|
||||
@@ -33,13 +34,11 @@ import org.springframework.http.ResponseEntity
|
||||
import org.springframework.test.context.ActiveProfiles
|
||||
import org.springframework.web.client.HttpClientErrorException
|
||||
|
||||
import static org.junit.Assert.assertEquals
|
||||
|
||||
/**
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
@ActiveProfiles('test-default')
|
||||
class MainControllerServiceMetadataIntegrationTests extends AbstractInitializrControllerIntegrationTests {
|
||||
class MainControllerServiceMetadataIntegrationTests extends AbstractFullStackInitializrIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
private InitializrMetadataProvider metadataProvider
|
||||
|
@@ -18,7 +18,7 @@ package io.spring.initializr.web.project
|
||||
|
||||
import geb.Browser
|
||||
import io.spring.initializr.test.generator.ProjectAssert
|
||||
import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests
|
||||
import io.spring.initializr.web.AbstractFullStackInitializrIntegrationTests
|
||||
import io.spring.initializr.web.project.test.HomePage
|
||||
import org.junit.After
|
||||
import org.junit.Assume
|
||||
@@ -40,7 +40,7 @@ import static org.junit.Assert.assertTrue
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
@ActiveProfiles('test-default')
|
||||
class ProjectGenerationSmokeTests extends AbstractInitializrControllerIntegrationTests {
|
||||
class ProjectGenerationSmokeTests extends AbstractFullStackInitializrIntegrationTests {
|
||||
|
||||
private File downloadDir
|
||||
private WebDriver driver
|
||||
|
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2014-2015 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
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.spring.initializr.web.test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A path that identifies a field in a JSON payload.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Jeremy Rickard
|
||||
*
|
||||
*/
|
||||
//Copied from RestDocs to make it visible
|
||||
final class JsonFieldPath {
|
||||
|
||||
private static final Pattern BRACKETS_AND_ARRAY_PATTERN = Pattern
|
||||
.compile("\\[\'(.+?)\'\\]|\\[([0-9]+|\\*){0,1}\\]");
|
||||
|
||||
private static final Pattern ARRAY_INDEX_PATTERN = Pattern
|
||||
.compile("\\[([0-9]+|\\*){0,1}\\]");
|
||||
|
||||
private final String rawPath;
|
||||
|
||||
private final List<String> segments;
|
||||
|
||||
private final boolean precise;
|
||||
|
||||
private final boolean array;
|
||||
|
||||
private JsonFieldPath(String rawPath, List<String> segments, boolean precise,
|
||||
boolean array) {
|
||||
this.rawPath = rawPath;
|
||||
this.segments = segments;
|
||||
this.precise = precise;
|
||||
this.array = array;
|
||||
}
|
||||
|
||||
boolean isPrecise() {
|
||||
return this.precise;
|
||||
}
|
||||
|
||||
boolean isArray() {
|
||||
return this.array;
|
||||
}
|
||||
|
||||
List<String> getSegments() {
|
||||
return this.segments;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.rawPath;
|
||||
}
|
||||
|
||||
static JsonFieldPath compile(String path) {
|
||||
List<String> segments = extractSegments(path);
|
||||
return new JsonFieldPath(path, segments, matchesSingleValue(segments),
|
||||
isArraySegment(segments.get(segments.size() - 1)));
|
||||
}
|
||||
|
||||
static boolean isArraySegment(String segment) {
|
||||
return ARRAY_INDEX_PATTERN.matcher(segment).matches();
|
||||
}
|
||||
|
||||
static boolean matchesSingleValue(List<String> segments) {
|
||||
Iterator<String> iterator = segments.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
if (isArraySegment(iterator.next()) && iterator.hasNext()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static List<String> extractSegments(String path) {
|
||||
Matcher matcher = BRACKETS_AND_ARRAY_PATTERN.matcher(path);
|
||||
|
||||
int previous = 0;
|
||||
|
||||
List<String> segments = new ArrayList<>();
|
||||
while (matcher.find()) {
|
||||
if (previous != matcher.start()) {
|
||||
segments.addAll(extractDotSeparatedSegments(
|
||||
path.substring(previous, matcher.start())));
|
||||
}
|
||||
if (matcher.group(1) != null) {
|
||||
segments.add(matcher.group(1));
|
||||
}
|
||||
else {
|
||||
segments.add(matcher.group());
|
||||
}
|
||||
previous = matcher.end(0);
|
||||
}
|
||||
|
||||
if (previous < path.length()) {
|
||||
segments.addAll(extractDotSeparatedSegments(path.substring(previous)));
|
||||
}
|
||||
|
||||
return segments;
|
||||
}
|
||||
|
||||
private static List<String> extractDotSeparatedSegments(String path) {
|
||||
List<String> segments = new ArrayList<>();
|
||||
for (String segment : path.split("\\.")) {
|
||||
if (segment.length() > 0) {
|
||||
segments.add(segment);
|
||||
}
|
||||
}
|
||||
return segments;
|
||||
}
|
||||
}
|
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* Copyright 2014-2016 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
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.spring.initializr.web.test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* A {@code JsonFieldProcessor} processes a payload's fields, allowing them to be
|
||||
* extracted and removed.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*
|
||||
*/
|
||||
// Copied from RestDocs to make it visible
|
||||
final class JsonFieldProcessor {
|
||||
|
||||
boolean hasField(JsonFieldPath fieldPath, Object payload) {
|
||||
final AtomicReference<Boolean> hasField = new AtomicReference<>(false);
|
||||
traverse(new ProcessingContext(payload, fieldPath), new MatchCallback() {
|
||||
|
||||
@Override
|
||||
public void foundMatch(Match match) {
|
||||
hasField.set(true);
|
||||
}
|
||||
|
||||
});
|
||||
return hasField.get();
|
||||
}
|
||||
|
||||
Object extract(JsonFieldPath path, Object payload) {
|
||||
final List<Object> matches = new ArrayList<>();
|
||||
traverse(new ProcessingContext(payload, path), new MatchCallback() {
|
||||
|
||||
@Override
|
||||
public void foundMatch(Match match) {
|
||||
matches.add(match.getValue());
|
||||
}
|
||||
|
||||
});
|
||||
if (matches.isEmpty()) {
|
||||
throw new IllegalArgumentException("Field does not exist: " + path);
|
||||
}
|
||||
if ((!path.isArray()) && path.isPrecise()) {
|
||||
return matches.get(0);
|
||||
}
|
||||
else {
|
||||
return matches;
|
||||
}
|
||||
}
|
||||
|
||||
void remove(final JsonFieldPath path, Object payload) {
|
||||
traverse(new ProcessingContext(payload, path), new MatchCallback() {
|
||||
|
||||
@Override
|
||||
public void foundMatch(Match match) {
|
||||
match.remove();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private void traverse(ProcessingContext context, MatchCallback matchCallback) {
|
||||
final String segment = context.getSegment();
|
||||
if (JsonFieldPath.isArraySegment(segment)) {
|
||||
if (context.getPayload() instanceof List) {
|
||||
handleListPayload(context, matchCallback);
|
||||
}
|
||||
}
|
||||
else if (context.getPayload() instanceof Map
|
||||
&& ((Map<?, ?>) context.getPayload()).containsKey(segment)) {
|
||||
handleMapPayload(context, matchCallback);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleListPayload(ProcessingContext context,
|
||||
MatchCallback matchCallback) {
|
||||
List<?> list = context.getPayload();
|
||||
final Iterator<?> items = list.iterator();
|
||||
if (context.isLeaf()) {
|
||||
while (items.hasNext()) {
|
||||
Object item = items.next();
|
||||
matchCallback.foundMatch(
|
||||
new ListMatch(items, list, item, context.getParentMatch()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
while (items.hasNext()) {
|
||||
Object item = items.next();
|
||||
traverse(
|
||||
context.descend(item,
|
||||
new ListMatch(items, list, item, context.parent)),
|
||||
matchCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleMapPayload(ProcessingContext context,
|
||||
MatchCallback matchCallback) {
|
||||
Map<?, ?> map = context.getPayload();
|
||||
Object item = map.get(context.getSegment());
|
||||
MapMatch mapMatch = new MapMatch(item, map, context.getSegment(),
|
||||
context.getParentMatch());
|
||||
if (context.isLeaf()) {
|
||||
matchCallback.foundMatch(mapMatch);
|
||||
}
|
||||
else {
|
||||
traverse(context.descend(item, mapMatch), matchCallback);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class MapMatch implements Match {
|
||||
|
||||
private final Object item;
|
||||
|
||||
private final Map<?, ?> map;
|
||||
|
||||
private final String segment;
|
||||
|
||||
private final Match parent;
|
||||
|
||||
private MapMatch(Object item, Map<?, ?> map, String segment, Match parent) {
|
||||
this.item = item;
|
||||
this.map = map;
|
||||
this.segment = segment;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue() {
|
||||
return this.item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
this.map.remove(this.segment);
|
||||
if (this.map.isEmpty() && this.parent != null) {
|
||||
this.parent.remove();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class ListMatch implements Match {
|
||||
|
||||
private final Iterator<?> items;
|
||||
|
||||
private final List<?> list;
|
||||
|
||||
private final Object item;
|
||||
|
||||
private final Match parent;
|
||||
|
||||
private ListMatch(Iterator<?> items, List<?> list, Object item, Match parent) {
|
||||
this.items = items;
|
||||
this.list = list;
|
||||
this.item = item;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue() {
|
||||
return this.item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
this.items.remove();
|
||||
if (this.list.isEmpty() && this.parent != null) {
|
||||
this.parent.remove();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private interface MatchCallback {
|
||||
|
||||
void foundMatch(Match match);
|
||||
|
||||
}
|
||||
|
||||
private interface Match {
|
||||
|
||||
Object getValue();
|
||||
|
||||
void remove();
|
||||
}
|
||||
|
||||
private static final class ProcessingContext {
|
||||
|
||||
private final Object payload;
|
||||
|
||||
private final List<String> segments;
|
||||
|
||||
private final Match parent;
|
||||
|
||||
private final JsonFieldPath path;
|
||||
|
||||
private ProcessingContext(Object payload, JsonFieldPath path) {
|
||||
this(payload, path, null, null);
|
||||
}
|
||||
|
||||
private ProcessingContext(Object payload, JsonFieldPath path,
|
||||
List<String> segments, Match parent) {
|
||||
this.payload = payload;
|
||||
this.path = path;
|
||||
this.segments = segments == null ? path.getSegments() : segments;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
private String getSegment() {
|
||||
return this.segments.get(0);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T getPayload() {
|
||||
return (T) this.payload;
|
||||
}
|
||||
|
||||
private boolean isLeaf() {
|
||||
return this.segments.size() == 1;
|
||||
}
|
||||
|
||||
private Match getParentMatch() {
|
||||
return this.parent;
|
||||
}
|
||||
|
||||
private ProcessingContext descend(Object payload, Match match) {
|
||||
return new ProcessingContext(payload, this.path,
|
||||
this.segments.subList(1, this.segments.size()), match);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.spring.initializr.web.test;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.client.ClientHttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.mock.http.client.MockClientHttpRequest;
|
||||
import org.springframework.mock.http.client.MockClientHttpResponse;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.restdocs.snippet.Snippet;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.ResultActions;
|
||||
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.RequestDispatcher;
|
||||
|
||||
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
|
||||
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse;
|
||||
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class MockMvcClientHttpRequestFactory implements ClientHttpRequestFactory {
|
||||
|
||||
private final MockMvc mockMvc;
|
||||
|
||||
private String label = "UNKNOWN";
|
||||
|
||||
private List<String> fields = new ArrayList<>();
|
||||
|
||||
public MockMvcClientHttpRequestFactory(MockMvc mockMvc) {
|
||||
Assert.notNull(mockMvc, "MockMvc must not be null");
|
||||
this.mockMvc = mockMvc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientHttpRequest createRequest(final URI uri, final HttpMethod httpMethod)
|
||||
throws IOException {
|
||||
return new MockClientHttpRequest(httpMethod, uri) {
|
||||
@Override
|
||||
public ClientHttpResponse executeInternal() throws IOException {
|
||||
try {
|
||||
MockHttpServletRequestBuilder requestBuilder = request(httpMethod,
|
||||
uri.toString());
|
||||
requestBuilder.content(getBodyAsBytes());
|
||||
requestBuilder.headers(getHeaders());
|
||||
MockHttpServletResponse servletResponse = actions(requestBuilder)
|
||||
.andReturn().getResponse();
|
||||
HttpStatus status = HttpStatus.valueOf(servletResponse.getStatus());
|
||||
if (status.value() >= 400) {
|
||||
requestBuilder = request(HttpMethod.GET, "/error")
|
||||
.requestAttr(RequestDispatcher.ERROR_STATUS_CODE,
|
||||
status.value())
|
||||
.requestAttr(RequestDispatcher.ERROR_REQUEST_URI,
|
||||
uri.toString());
|
||||
if (servletResponse.getErrorMessage() != null) {
|
||||
requestBuilder.requestAttr(RequestDispatcher.ERROR_MESSAGE,
|
||||
servletResponse.getErrorMessage());
|
||||
}
|
||||
// Overwrites the snippets from the first request
|
||||
servletResponse = actions(requestBuilder).andReturn()
|
||||
.getResponse();
|
||||
}
|
||||
byte[] body = servletResponse.getContentAsByteArray();
|
||||
HttpHeaders headers = getResponseHeaders(servletResponse);
|
||||
MockClientHttpResponse clientResponse = new MockClientHttpResponse(
|
||||
body, status);
|
||||
clientResponse.getHeaders().putAll(headers);
|
||||
return clientResponse;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
private ResultActions actions(MockHttpServletRequestBuilder requestBuilder)
|
||||
throws Exception {
|
||||
ResultActions actions = MockMvcClientHttpRequestFactory.this.mockMvc
|
||||
.perform(requestBuilder);
|
||||
List<Snippet> snippets = new ArrayList<>();
|
||||
for (String field : this.fields) {
|
||||
snippets.add(new ResponseFieldSnippet(field));
|
||||
}
|
||||
actions.andDo(document(label, preprocessResponse(prettyPrint()), snippets.toArray(new Snippet[0])));
|
||||
this.fields = new ArrayList<>();
|
||||
return actions;
|
||||
}
|
||||
|
||||
private HttpHeaders getResponseHeaders(MockHttpServletResponse response) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
for (String name : response.getHeaderNames()) {
|
||||
List<String> values = response.getHeaders(name);
|
||||
for (String value : values) {
|
||||
headers.add(name, value);
|
||||
}
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
public void setTest(Class<?> testClass, Method testMethod) {
|
||||
this.label = testMethod.getName();
|
||||
}
|
||||
|
||||
public void setFields(String... fields) {
|
||||
this.fields = Arrays.asList(fields);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.spring.initializr.web.test;
|
||||
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.test.context.TestContext;
|
||||
import org.springframework.test.context.support.AbstractTestExecutionListener;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public final class MockMvcClientHttpRequestFactoryTestExecutionListener
|
||||
extends AbstractTestExecutionListener {
|
||||
|
||||
private MockMvcClientHttpRequestFactory factory;
|
||||
|
||||
@Override
|
||||
public void beforeTestClass(TestContext testContext) throws Exception {
|
||||
ConfigurableBeanFactory beanFactory = (ConfigurableBeanFactory) testContext
|
||||
.getApplicationContext().getAutowireCapableBeanFactory();
|
||||
if (!beanFactory.containsBean("mockMvcClientHttpRequestFactory")) {
|
||||
factory = new MockMvcClientHttpRequestFactory(
|
||||
beanFactory.getBean(MockMvc.class));
|
||||
beanFactory.registerSingleton("mockMvcClientHttpRequestFactory",
|
||||
this.factory);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTestMethod(TestContext testContext) throws Exception {
|
||||
if (factory != null) {
|
||||
this.factory.setTest(testContext.getTestClass(), testContext.getTestMethod());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.spring.initializr.web.test;
|
||||
|
||||
import org.springframework.restdocs.RestDocumentationContext;
|
||||
import org.springframework.restdocs.operation.Operation;
|
||||
import org.springframework.restdocs.snippet.TemplatedSnippet;
|
||||
import org.springframework.restdocs.snippet.WriterResolver;
|
||||
import org.springframework.restdocs.templates.TemplateEngine;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
/**
|
||||
* Creates a separate snippet for a single field in a larger payload. The output comes in
|
||||
* a sub-directory ("response-fields") of one containing the request and response
|
||||
* snippets, with a file name the same as the path. An exception to the last rule is if
|
||||
* you pick a single array element by using a path like `foo.bar[0]`, the snippet file
|
||||
* name is then just the array name (because asciidoctor cannot import snippets with
|
||||
* brackets in the name).
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class ResponseFieldSnippet extends TemplatedSnippet {
|
||||
|
||||
private String path;
|
||||
|
||||
private final JsonFieldProcessor fieldProcessor = new JsonFieldProcessor();
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private final Integer index;
|
||||
|
||||
private final String file;
|
||||
|
||||
public ResponseFieldSnippet(String path) {
|
||||
super("response-fields", Collections.emptyMap());
|
||||
String file = path;
|
||||
if (path.endsWith("]")) {
|
||||
// In this project we actually only need snippets whose last segment is an
|
||||
// array index, so we can deal with it as a special case here. Ideally the
|
||||
// restdocs implementation of JsonField would support this use case as well.
|
||||
String index = path.substring(path.lastIndexOf("[") + 1);
|
||||
index = index.substring(0, index.length() - 1);
|
||||
this.index = Integer.valueOf(index);
|
||||
path = path.substring(0, path.lastIndexOf("["));
|
||||
file = file.replace("]", "").replace("[", ".");
|
||||
} else {
|
||||
this.index = null;
|
||||
}
|
||||
this.file = file;
|
||||
this.path = path;
|
||||
objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* Copy of super class method, but changes the path of the output file to include the
|
||||
* path
|
||||
*/
|
||||
@Override
|
||||
public void document(Operation operation) throws IOException {
|
||||
RestDocumentationContext context = (RestDocumentationContext) operation
|
||||
.getAttributes().get(RestDocumentationContext.class.getName());
|
||||
WriterResolver writerResolver = (WriterResolver) operation.getAttributes()
|
||||
.get(WriterResolver.class.getName());
|
||||
try (Writer writer = writerResolver
|
||||
.resolve(operation.getName() + "/" + getSnippetName(), file, context)) {
|
||||
Map<String, Object> model = createModel(operation);
|
||||
model.putAll(getAttributes());
|
||||
TemplateEngine templateEngine = (TemplateEngine) operation.getAttributes()
|
||||
.get(TemplateEngine.class.getName());
|
||||
writer.append(templateEngine.compileTemplate(getSnippetName()).render(model));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, Object> createModel(Operation operation) {
|
||||
String value = "{}";
|
||||
try {
|
||||
Object object = objectMapper.readValue(
|
||||
operation.getResponse().getContentAsString(), Object.class);
|
||||
Object field = fieldProcessor.extract(JsonFieldPath.compile(path), object);
|
||||
if (field instanceof List && index != null) {
|
||||
field = ((List<?>) field).get(index);
|
||||
}
|
||||
value = objectMapper.writeValueAsString(field);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
return Collections.singletonMap("value", value);
|
||||
}
|
||||
|
||||
}
|
@@ -17,11 +17,11 @@
|
||||
package io.spring.initializr.web.ui
|
||||
|
||||
import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests
|
||||
|
||||
import org.json.JSONObject
|
||||
import org.junit.Test
|
||||
import org.skyscreamer.jsonassert.JSONAssert
|
||||
import org.skyscreamer.jsonassert.JSONCompareMode
|
||||
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.test.context.ActiveProfiles
|
||||
@@ -50,5 +50,4 @@ class UiControllerIntegrationTests extends AbstractInitializrControllerIntegrati
|
||||
def expected = readJsonFrom("metadata/ui/test-dependencies-$version" + ".json")
|
||||
JSONAssert.assertEquals(expected, actual, JSONCompareMode.STRICT)
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"_links": {
|
||||
"maven-build": {
|
||||
"href": "https://localhost:@port@/pom.xml?type=maven-build{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName}",
|
||||
"href": "https://@host@/pom.xml?type=maven-build{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName}",
|
||||
"templated": true
|
||||
},
|
||||
"maven-project": {
|
||||
"href": "https://localhost:@port@/starter.zip?type=maven-project{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName}",
|
||||
"href": "https://@host@/starter.zip?type=maven-project{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName}",
|
||||
"templated": true
|
||||
},
|
||||
"gradle-build": {
|
||||
"href": "https://localhost:@port@/build.gradle?type=gradle-build{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName}",
|
||||
"href": "https://@host@/build.gradle?type=gradle-build{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName}",
|
||||
"templated": true
|
||||
},
|
||||
"gradle-project": {
|
||||
"href": "https://localhost:@port@/starter.zip?type=gradle-project{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName}",
|
||||
"href": "https://@host@/starter.zip?type=gradle-project{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName}",
|
||||
"templated": true
|
||||
}
|
||||
},
|
||||
|
@@ -1,23 +1,23 @@
|
||||
{
|
||||
"_links": {
|
||||
"dependencies": {
|
||||
"href": "https://localhost:@port@/dependencies{?bootVersion}",
|
||||
"href": "https://@host@/dependencies{?bootVersion}",
|
||||
"templated": true
|
||||
},
|
||||
"maven-build": {
|
||||
"href": "https://localhost:@port@/pom.xml?type=maven-build{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName}",
|
||||
"href": "https://@host@/pom.xml?type=maven-build{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName}",
|
||||
"templated": true
|
||||
},
|
||||
"maven-project": {
|
||||
"href": "https://localhost:@port@/starter.zip?type=maven-project{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName}",
|
||||
"href": "https://@host@/starter.zip?type=maven-project{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName}",
|
||||
"templated": true
|
||||
},
|
||||
"gradle-build": {
|
||||
"href": "https://localhost:@port@/build.gradle?type=gradle-build{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName}",
|
||||
"href": "https://@host@/build.gradle?type=gradle-build{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName}",
|
||||
"templated": true
|
||||
},
|
||||
"gradle-project": {
|
||||
"href": "https://localhost:@port@/starter.zip?type=gradle-project{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName}",
|
||||
"href": "https://@host@/starter.zip?type=gradle-project{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName}",
|
||||
"templated": true
|
||||
}
|
||||
},
|
||||
|
@@ -0,0 +1,4 @@
|
||||
[source,json,options="nowrap"]
|
||||
----
|
||||
{{value}}
|
||||
----
|
Reference in New Issue
Block a user