Explicit support for curl

Add an explicit support for curl by returning a text description of the
service instead of the raw meta-data. curl users can still discover the
json metadata by setting the appropriate Accept header.

Also support for explicit "text/plain" if the user requires it. In this
case a generic text description is returned.

Closes gh-67
This commit is contained in:
Stephane Nicoll
2015-01-19 17:36:11 +01:00
parent cb0c2b2e4e
commit 272db6ef7a
16 changed files with 594 additions and 44 deletions

View File

@@ -7,6 +7,9 @@ order.
=== Release 1.0.0 (In progress)
* https://github.com/spring-io/initializr/issues/67[#67]: explicit support for curl by returning a text
description of the service instead of the raw meta-data. curl users can still discover the json metadata
by setting the appropriate Accept header.
* https://github.com/spring-io/initializr/issues/65[#65]: project archives generated by the web UI now contain
a base directory equals to the `artifactId`. By default, no base directory gets created if the project is
generated by another means; use the new `baseDir` to control that behaviour.

View File

@@ -118,6 +118,7 @@ initializr:
types:
- name: Maven POM
id: maven-build
description: Generate a Maven pom.xml
sts-id: pom.xml
tags:
build: maven
@@ -126,6 +127,7 @@ initializr:
action: /pom.xml
- name: Maven Project
id: maven-project
description: Generate a Maven based project archive
sts-id: starter.zip
tags:
build: maven
@@ -134,6 +136,7 @@ initializr:
action: /starter.zip
- name: Gradle Config
id: gradle-build
description: Generate a Gradle based project archive
sts-id: build.gradle
tags:
build: gradle
@@ -142,6 +145,7 @@ initializr:
action: /build.gradle
- name: Gradle Project
id: gradle-project
description: Generate a Gradle build file
sts-id: gradle.zip
tags:
build: gradle

View File

@@ -53,6 +53,12 @@
<artifactId>groovy-json</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>

View File

@@ -0,0 +1,104 @@
/*
* 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
import static io.spring.initializr.support.GroovyTemplate.template
/**
* Generate help pages for command-line clients.
*
* @author Stephane Nicoll
* @since 1.0
*/
class CommandLineHelpGenerator {
private static final String logo = '''
. ____ _ __ _ _
/\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\
( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\
\\\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\\__, | / / / /
=========|_|==============|___/=/_/_/_/
'''
/**
* Generate the capabilities of the service as a generic plain text
* document. Used when no particular agent was detected.
*/
String generateGenericCapabilities(InitializrMetadata metadata, String serviceUrl) {
def model = initializeModel(metadata, serviceUrl)
model['hasExamples'] = false
doGenerateCapabilities(model)
}
/**
* Generate the capabilities of the service using "curl" as a plain text
* document.
*/
String generateCurlCapabilities(InitializrMetadata metadata, String serviceUrl) {
def model = initializeModel(metadata, serviceUrl)
model['examples'] = template 'curl-examples.txt', model
model['hasExamples'] = true
doGenerateCapabilities(model)
}
private doGenerateCapabilities(def model) {
template 'cli-capabilities.txt', model
}
private Map initializeModel(InitializrMetadata metadata, serviceUrl) {
Map model = [:]
model['logo'] = logo
model['serviceUrl'] = serviceUrl
Map dependencies = [:]
new ArrayList(metadata.allDependencies).sort { a, b -> a.id <=> b.id }.each {
String description = it.name
if (it.description) {
description += ": $it.description"
}
dependencies[it.id] = description
}
model['dependencies'] = dependencies
Map types = [:]
new ArrayList<>(metadata.types).sort { a, b -> a.id <=> b.id }.each {
String description = it.description
if (!description) {
description = it.name
}
types[it.id] = description
}
model['types'] = types
Map defaults = [:]
metadata.defaults.properties.sort().each {
if (!(it.key in ['class', 'metaClass', 'DEFAULT_NAME'])) {
defaults[it.key] = it.value
}
}
defaults['applicationName'] = ProjectRequest.generateApplicationName(metadata.defaults.name,
ProjectRequest.DEFAULT_APPLICATION_NAME)
model['defaults'] = defaults
model
}
}

View File

@@ -1,10 +1,26 @@
/*
* 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
import java.util.concurrent.ConcurrentMap
import java.util.concurrent.TimeUnit
import com.google.common.cache.CacheBuilder
import io.spring.initializr.web.MainController
import io.spring.initializr.web.WebConfig
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.actuate.metrics.CounterService
@@ -37,6 +53,11 @@ class InitializrAutoConfiguration {
@Autowired
private CounterService counterService
@Bean
WebConfig webConfig() {
new WebConfig()
}
@Bean
@ConditionalOnMissingBean(MainController)
MainController initializrMainController() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2014 the original author or authors.
* 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.
@@ -75,6 +75,13 @@ class InitializrMetadata {
indexedDependencies[id]
}
/**
* Return all dependencies as a flat collection
*/
Collection<Dependency> getAllDependencies() {
indexedDependencies.values()
}
/**
* Return the {@link Type} with the specified id or {@code null} if no
* such type exists.
@@ -267,6 +274,8 @@ class InitializrMetadata {
static class Type extends DefaultIdentifiableElement {
String description
@JsonIgnore
@Deprecated
String stsId

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2014 the original author or authors.
* 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.
@@ -17,6 +17,7 @@
package io.spring.initializr.web
import groovy.util.logging.Slf4j
import io.spring.initializr.CommandLineHelpGenerator
import io.spring.initializr.InitializrMetadata
import io.spring.initializr.ProjectGenerator
import io.spring.initializr.ProjectRequest
@@ -24,11 +25,14 @@ import io.spring.initializr.ProjectRequest
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.ModelAttribute
import org.springframework.web.bind.annotation.RequestHeader
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.ResponseBody
import org.springframework.web.context.request.WebRequest
import org.springframework.web.servlet.support.ServletUriComponentsBuilder
/**
@@ -44,9 +48,14 @@ import org.springframework.web.servlet.support.ServletUriComponentsBuilder
@Slf4j
class MainController extends AbstractInitializrController {
public static final MediaType META_DATA_V2 = MediaType.parseMediaType("application/vnd.initializr.v2+json")
@Autowired
private ProjectGenerator projectGenerator
private CommandLineHelpGenerator commandLineHelpGenerator = new CommandLineHelpGenerator()
@ModelAttribute
ProjectRequest projectRequest() {
def request = new ProjectRequest()
@@ -61,11 +70,26 @@ class MainController extends AbstractInitializrController {
metadataProvider.get()
}
@RequestMapping(value = "/", produces = ["application/vnd.initializr.v2+json","application/json"])
@ResponseBody
String metadata() {
@RequestMapping(value = "/", produces = ["text/plain"])
ResponseEntity<String> serviceCapabilities(
@RequestHeader(value = HttpHeaders.USER_AGENT, required = false) String userAgent) {
String appUrl = ServletUriComponentsBuilder.fromCurrentServletMapping().build()
metadataProvider.get().generateJson(appUrl)
def metadata = metadataProvider.get()
def builder = ResponseEntity.ok().contentType(MediaType.TEXT_PLAIN)
if (userAgent && userAgent.startsWith(WebConfig.CURL_USER_AGENT_PREFIX)) {
builder.body(commandLineHelpGenerator.generateCurlCapabilities(metadata,appUrl))
}
else {
builder.body(commandLineHelpGenerator.generateGenericCapabilities(metadata, appUrl))
}
}
@RequestMapping(value = "/", produces = ["application/vnd.initializr.v2+json", "application/json"])
ResponseEntity<String> serviceCapabilities() {
String appUrl = ServletUriComponentsBuilder.fromCurrentServletMapping().build()
def content = metadataProvider.get().generateJson(appUrl)
return ResponseEntity.ok().contentType(META_DATA_V2).body(content)
}
@RequestMapping(value = '/', produces = 'text/html')

View File

@@ -0,0 +1,71 @@
/*
* 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
import javax.servlet.http.HttpServletRequest
import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType
import org.springframework.web.HttpMediaTypeNotAcceptableException
import org.springframework.web.accept.ContentNegotiationStrategy
import org.springframework.web.context.request.NativeWebRequest
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
import org.springframework.web.util.UrlPathHelper
/**
* Spring Initializr web configuration.
*
* @author Stephane Nicoll
* @since 1.0
*/
class WebConfig extends WebMvcConfigurerAdapter {
static final String CURL_USER_AGENT_PREFIX = 'curl'
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentTypeStrategy(new CommandLineContentNegotiationStrategy())
}
/**
* A command-line aware {@link ContentNegotiationStrategy} that forces the media type
* to "text/plain" for compatible agents.
*/
private static class CommandLineContentNegotiationStrategy implements ContentNegotiationStrategy {
private final UrlPathHelper urlPathHelper = new UrlPathHelper();
@Override
List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
String path = urlPathHelper.getPathWithinApplication(
request.getNativeRequest(HttpServletRequest.class));
if (!path || !path.equals("/")) { // Only care about "/"
return Collections.emptyList()
}
String userAgent = request.getHeader(HttpHeaders.USER_AGENT)
if (userAgent) {
if (userAgent.startsWith(CURL_USER_AGENT_PREFIX)) {
return Collections.singletonList(MediaType.TEXT_PLAIN)
}
}
return Collections.singletonList(MediaType.APPLICATION_JSON)
}
}
}

View File

@@ -0,0 +1,48 @@
${logo}
:: Spring Initializr :: ${serviceUrl}
This service generates quickstart projects and can be customized
in many ways; the dependencies, java version, build system or
build structure can be namely customized easily.
The services uses a HAL based hypermedia format to expose a set of
resources to interact with. If you access this root resource
requesting application/json as media type the response will contain
the following links:
<% types.each {key, value -> %>
* ${key} - ${value} <% } %>
The URI templates take a set of parameters to customize the result of
a request to the linked resource.
+-----------------+------------------------------------------+-----------------
| Parameter | Description | Default
|-----------------+------------------------------------------+-----------------
| groupId | project coordinates | ${defaults.groupId}
| artifactId | project coordinates (infer archive name) | ${defaults.artifactId}
| version | project version | ${defaults.version}
| name | project name (infer application name) | ${defaults.name}
| description | project description | ${defaults.description}
| packageName | root package | ${defaults.packageName}
| applicationName | application name | ${defaults.applicationName}
| dependencies | dependency identifiers (comma separated) | none
| type | project type | ${defaults.type}
| packaging | project packaging | ${defaults.packaging}
| language | programming language | ${defaults.language}
| javaVersion | language level | ${defaults.javaVersion}
| bootVersion | spring boot version | ${defaults.bootVersion}
| baseDir | base directory to create in the archive | no base dir
+-----------------+------------------------------------------+-----------------
The following section has a list of supported identifiers for the comma separated
list of "dependencies".
<% dependencies.each {key, value -> %>
${key} - ${value} <% } %>
<% if (hasExamples) { %>
Examples:
${examples}
<% } %>

View File

@@ -0,0 +1,13 @@
To create a default demo.zip:
\$ curl ${serviceUrl}/starter.zip -o demo.zip
To create a web project using Java 8:
\$ curl ${serviceUrl}/starter.zip -d dependencies=web \\
-d javaVersion=1.8 -o demo.zip
To create a web/data-jpa gradle project unpacked:
\$ curl ${serviceUrl}/starter.tgz -d dependencies=web,data-jpa \\
-d type=gradle-project -d baseDir=my-dir | tar -xzvf -
To generate a Maven POM with war packaging:
\$ curl ${serviceUrl}/pom.xml -d packaging=war -o pom.xml

View File

@@ -0,0 +1,77 @@
/*
* 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
import io.spring.initializr.test.InitializrMetadataBuilder
import org.junit.Test
import static org.hamcrest.CoreMatchers.containsString
import static org.hamcrest.core.IsNot.not
import static org.junit.Assert.assertThat
/**
* @author Stephane Nicoll
*/
class CommandLineHelpGeneratorTest {
private CommandLineHelpGenerator generator = new CommandLineHelpGenerator()
@Test
void generateGenericCapabilities() {
def metadata = InitializrMetadataBuilder.withDefaults().addDependencyGroup("test",
createDependency('id-b', 'depB'),
createDependency('id-a', 'depA', 'and some description')).validateAndGet()
String content = generator.generateGenericCapabilities(metadata, "https://fake-service")
assertThat content, containsString('id-a - depA: and some description')
assertThat content, containsString('id-b - depB')
assertThat content, containsString("https://fake-service")
assertThat content, not(containsString('Examples:'))
assertThat content, not(containsString('curl'))
}
@Test
void generateCapabilitiesWithTypeDescription() {
def metadata = InitializrMetadataBuilder.withDefaults()
.addType(new InitializrMetadata.Type(id: 'foo', name: 'foo-name', description: 'foo-desc'))
.validateAndGet()
String content = generator.generateGenericCapabilities(metadata, "https://fake-service")
assertThat content, containsString('foo - foo-desc')
}
@Test
void generateCurlCapabilities() {
def metadata = InitializrMetadataBuilder.withDefaults().addDependencyGroup("test",
createDependency('id-b', 'depB'),
createDependency('id-a', 'depA', 'and some description')).validateAndGet()
String content = generator.generateCurlCapabilities(metadata, "https://fake-service")
assertThat content, containsString('id-a - depA: and some description')
assertThat content, containsString('id-b - depB')
assertThat content, containsString("https://fake-service")
assertThat content, containsString('Examples:')
assertThat content, containsString('curl')
}
private static def createDependency(String id, String name) {
createDependency(id, name, null)
}
private static def createDependency(String id, String name, String description) {
new InitializrMetadata.Dependency(id: id, name: name, description: description)
}
}

View File

@@ -86,6 +86,10 @@ class InitializrMetadataBuilder {
if (format) {
type.tags['format'] = format
}
addType(type)
}
InitializrMetadataBuilder addType(InitializrMetadata.Type type) {
metadata.types.add(type)
this
}

View File

@@ -63,6 +63,13 @@ class ProjectAssert {
new PomAssert(file('pom.xml').text)
}
/**
* Return a {@link GradleBuildAssert} for this project.
*/
GradleBuildAssert gradleBuildAssert() {
new GradleBuildAssert(file('build.gradle').text)
}
/**
* Return a {@link SourceCodeAssert} for the specified source code.
*/

View File

@@ -33,10 +33,14 @@ 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.SpringJUnit4ClassRunner
import org.springframework.test.context.web.WebAppConfiguration
import org.springframework.web.client.RestTemplate
import static org.junit.Assert.assertEquals
import static org.junit.Assert.assertTrue
/**
* @author Stephane Nicoll
*/
@@ -64,6 +68,17 @@ abstract class AbstractInitializrControllerIntegrationTests {
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)
assertEquals 'All text content should be UTF-8 encoded',
'UTF-8', actual.getParameter('charset')
}
/**
* Return a {@link ProjectAssert} for the following archive content.
*/
@@ -78,7 +93,40 @@ abstract class AbstractInitializrControllerIntegrationTests {
projectAssert(content, ArchiveType.TGZ)
}
ProjectAssert projectAssert(byte[] content, ArchiveType archiveType) {
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 acceptHeader) {
execute('/', String, userAgentHeader, acceptHeader)
}
protected <T> ResponseEntity<T> execute(String contextPath, Class<T> responseType,
String userAgentHeader, String acceptHeader) {
HttpHeaders headers = new HttpHeaders();
if (userAgentHeader) {
headers.set("User-Agent", userAgentHeader);
}
if (acceptHeader) {
headers.setAccept(Collections.singletonList(MediaType.parseMediaType(acceptHeader)))
} 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()

View File

@@ -0,0 +1,73 @@
/*
* 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
import io.spring.initializr.test.PomAssert
import org.junit.Test
import org.springframework.test.context.ActiveProfiles
/**
* Validate that the "raw" HTTP commands that are described in the
* command-line help works. If anything needs to be updated here, please
* double check the "curl-examples.txt" as it may need an update
* as well. This is also a good indicator of a non backward compatible
* change.
*
* @author Stephane Nicoll
*/
@ActiveProfiles('test-default')
class CommandLineExampleIntegrationTests extends AbstractInitializrControllerIntegrationTests {
@Test
void generateDefaultProject() {
downloadZip('/starter.zip').isJavaProject()
.isMavenProject().hasStaticAndTemplatesResources(false).pomAssert()
.hasSpringBootStarterRootDependency()
.hasSpringBootStarterDependency('test')
.hasDependenciesCount(2)
}
@Test
void generateWebProjectWithJava8() {
downloadZip('/starter.zip?dependencies=web&javaVersion=1.8').isJavaProject()
.isMavenProject().hasStaticAndTemplatesResources(true).pomAssert()
.hasJavaVersion('1.8')
.hasSpringBootStarterDependency('web')
.hasSpringBootStarterDependency('test')
.hasDependenciesCount(2)
}
@Test
void generateWebDataJpaGradleProject() {
downloadTgz('/starter.tgz?dependencies=web,data-jpa&type=gradle-project&baseDir=my-dir')
.hasBaseDir('my-dir')
.isJavaProject()
.isGradleProject().hasStaticAndTemplatesResources(true)
.gradleBuildAssert()
.contains('spring-boot-starter-web')
.contains('spring-boot-starter-data-jpa')
}
@Test
void generateMavenPomWithWarPackaging() {
def response = restTemplate.getForEntity(createUrl('/pom.xml?packaging=war'), String)
PomAssert pomAssert = new PomAssert(response.body)
pomAssert.hasPackaging('war')
}
}

View File

@@ -19,7 +19,6 @@ package io.spring.initializr.web
import java.nio.charset.Charset
import groovy.json.JsonSlurper
import io.spring.initializr.test.ProjectAssert
import org.json.JSONObject
import org.junit.Test
import org.skyscreamer.jsonassert.JSONAssert
@@ -36,6 +35,9 @@ import org.springframework.test.context.ActiveProfiles
import org.springframework.util.StreamUtils
import org.springframework.web.client.HttpClientErrorException
import static org.hamcrest.CoreMatchers.allOf
import static org.hamcrest.CoreMatchers.containsString
import static org.hamcrest.core.IsNot.not
import static org.junit.Assert.*
/**
@@ -132,29 +134,92 @@ class MainControllerIntegrationTests extends AbstractInitializrControllerIntegra
@Test
void metadataWithNoAcceptHeader() { // rest template sets application/json by default
ResponseEntity<String> response = getMetadata(null, '*/*')
assertTrue response.headers.getContentType().isCompatibleWith(CURRENT_METADATA_MEDIA_TYPE)
validateCurrentMetadata(new JSONObject(response.body))
ResponseEntity<String> response = invokeHome(null, '*/*')
validateCurrentMetadata(response)
}
@Test
void metadataWithCurrentAcceptHeader() {
ResponseEntity<String> response = getMetadata(null, 'application/vnd.initializr.v2+json')
assertTrue response.headers.getContentType().isCompatibleWith(CURRENT_METADATA_MEDIA_TYPE)
ResponseEntity<String> response = invokeHome(null, 'application/vnd.initializr.v2+json')
validateContentType(response, CURRENT_METADATA_MEDIA_TYPE)
validateCurrentMetadata(new JSONObject(response.body))
}
@Test
void curlReceivesTextByDefault() {
ResponseEntity<String> response = invokeHome('curl/1.2.4', "*/*")
validateCurlHelpContent(response)
}
@Test
void curlCanStillDownloadZipArchive() {
ResponseEntity<byte[]> response = execute('/starter.zip', byte[], 'curl/1.2.4', "*/*")
zipProjectAssert(response.body).isMavenProject().isJavaProject()
}
@Test
void curlCanStillDownloadTgzArchive() {
ResponseEntity<byte[]> response = execute('/starter.tgz', byte[], 'curl/1.2.4', "*/*")
tgzProjectAssert(response.body).isMavenProject().isJavaProject()
}
@Test // make sure curl can still receive metadata with json
void curlWithAcceptHeaderJson() {
ResponseEntity<String> response = invokeHome('curl/1.2.4', "application/json")
validateContentType(response, CURRENT_METADATA_MEDIA_TYPE)
validateCurrentMetadata(new JSONObject(response.body))
}
@Test
void curlWithAcceptHeaderTextPlain() {
ResponseEntity<String> response = invokeHome('curl/1.2.4', "text/plain")
validateCurlHelpContent(response)
}
@Test
void unknownAgentReceivesJsonByDefault() {
ResponseEntity<String> response = invokeHome('foo/1.0', "*/*")
validateCurrentMetadata(response)
}
@Test
void unknownCliWithTextPlain() {
ResponseEntity<String> response = invokeHome(null, "text/plain")
validateGenericHelpContent(response)
}
@Test // Test that the current output is exactly what we expect
void validateCurrentProjectMetadata() {
def json = getMetadataJson()
validateCurrentMetadata(json)
}
private void validateCurrentMetadata(ResponseEntity<String> response) {
validateContentType(response, CURRENT_METADATA_MEDIA_TYPE)
validateCurrentMetadata(new JSONObject(response.body))
}
private void validateCurrentMetadata(JSONObject json) {
def expected = readJson('2.0.0')
JSONAssert.assertEquals(expected, json, JSONCompareMode.STRICT)
}
private void validateCurlHelpContent(ResponseEntity<String> response) {
validateContentType(response, MediaType.TEXT_PLAIN)
assertThat(response.body, allOf(
containsString("Spring Initializr"),
containsString('Examples:'),
containsString("curl")))
}
private void validateGenericHelpContent(ResponseEntity<String> response) {
validateContentType(response, MediaType.TEXT_PLAIN)
assertThat(response.body, allOf(
containsString("Spring Initializr"),
not(containsString('Examples:')),
not(containsString("curl"))))
}
@Test // Test that the current code complies exactly with 1.1.0
void validateProjectMetadata110() {
JSONObject json = getMetadataJson("SpringBootCli/1.2.0.RC1", null)
@@ -208,7 +273,7 @@ class MainControllerIntegrationTests extends AbstractInitializrControllerIntegra
@Test
void missingDependencyProperException() {
try {
invoke('/starter.zip?style=foo:bar')
downloadArchive('/starter.zip?style=foo:bar')
} catch (HttpClientErrorException ex) {
def error = parseJson(ex.responseBodyAsString)
assertEquals HttpStatus.BAD_REQUEST, ex.getStatusCode()
@@ -236,7 +301,7 @@ class MainControllerIntegrationTests extends AbstractInitializrControllerIntegra
@Test
void homeIsJson() {
def body = restTemplate.getForObject(createUrl('/'), String)
def body = invokeHome(null, null).body
assertTrue("Wrong body:\n$body", body.contains('"dependencies"'))
}
@@ -293,37 +358,10 @@ class MainControllerIntegrationTests extends AbstractInitializrControllerIntegra
}
private JSONObject getMetadataJson(String userAgentHeader, String acceptHeader) {
String json = getMetadata(userAgentHeader, acceptHeader).body
String json = invokeHome(userAgentHeader, acceptHeader).body
return new JSONObject(json)
}
private ResponseEntity<String> getMetadata(String userAgentHeader, String acceptHeader) {
HttpHeaders headers = new HttpHeaders();
if (userAgentHeader) {
headers.set("User-Agent", userAgentHeader);
}
if (acceptHeader) {
headers.setAccept(Collections.singletonList(MediaType.parseMediaType(acceptHeader)))
}
return restTemplate.exchange(createUrl('/'),
HttpMethod.GET, new HttpEntity<Void>(headers), String.class)
}
private byte[] invoke(String context) {
restTemplate.getForObject(createUrl(context), byte[])
}
private ProjectAssert downloadZip(String context) {
def body = invoke(context)
zipProjectAssert(body)
}
private ProjectAssert downloadTgz(String context) {
def body = invoke(context)
tgzProjectAssert(body)
}
private JSONObject readJson(String version) {
def resource = new ClassPathResource("metadata/test-default-$version" + ".json")
def stream = resource.inputStream