Add init command to the CLI

This commit adds a new command to the CLI that allows to initialize a new
project from the command line. It uses the Spring initializr service to
actually generate the project.

The command offers two main operations:

1. Listing the capabilities of the service (--list or -l). This basically
dumps the defaults of a given service and the list of dependencies and
project types it supports
2. Generating a project. By default, http://start.spring.io is used and
its configured defaults are applied. Running spring init would therefore
have the same effect as clicking the 'generate project' on the UI without
entering any extra information. No file is overwritten by default.

The generation can be customized with the following options:

* --boot-version (-bv) Spring Boot version the project should use
* --dependencies (-d) comma separated list of dependencies to add to the
generated project
* --java-version (-jv) Java version to use
* --packaging (-p) the packaging for the project (jar, war)
* --target the url of the service to use

The actual type of the project can be defined in several ways:

1. Using the --type (-t) option that identifies a type that is supported
by the service
2. A combination of --build and/or --format that can be used to uniquely
identify matching these tags. Build represents the build system to use
(e.g. maven or gradle) while --format defines the format of the generated
project.

The project is saved on disk with the name provided by the server through
the Content-Disposition header, if any. It is possible to force it with
the --output option. It is possible to overwrite existing files by adding
the --force (-f) flag.

The --extract (-x) option allows to extract the project instead of saving
the zip archive. By default, the project is extracted in the current
working directory but it is possible to specify an alternate directory
using the --output option.

Fixes gh-1751
This commit is contained in:
Stephane Nicoll
2014-10-23 18:10:55 +02:00
parent 4b45ce9e57
commit b2fe2dd912
23 changed files with 2838 additions and 2 deletions

View File

@@ -0,0 +1,183 @@
/*
* Copyright 2012-2014 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 org.springframework.boot.cli.command.init;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicHeader;
import org.hamcrest.Matcher;
import org.json.JSONObject;
import org.mockito.ArgumentMatcher;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.util.StreamUtils;
import static org.mockito.Mockito.*;
/**
*
* @author Stephane Nicoll
*/
public abstract class AbstractHttpClientMockTests {
protected final CloseableHttpClient httpClient = mock(CloseableHttpClient.class);
protected void mockSuccessfulMetadataGet() throws IOException {
mockSuccessfulMetadataGet("1.1.0");
}
protected void mockSuccessfulMetadataGet(String version) throws IOException {
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
Resource resource = new ClassPathResource("metadata/service-metadata-" + version + ".json");
byte[] content = StreamUtils.copyToByteArray(resource.getInputStream());
mockHttpEntity(response, content, "application/json");
mockStatus(response, 200);
when(httpClient.execute(argThat(getForJsonData()))).thenReturn(response);
}
protected void mockSuccessfulProjectGeneration(MockHttpProjectGenerationRequest request) throws IOException {
// Required for project generation as the metadata is read first
mockSuccessfulMetadataGet();
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
mockHttpEntity(response, request.content, request.contentType);
mockStatus(response, 200);
String header = request.fileName != null ? contentDispositionValue(request.fileName) : null;
mockHttpHeader(response, "Content-Disposition", header);
when(httpClient.execute(argThat(getForNonJsonData()))).thenReturn(response);
}
protected void mockProjectGenerationError(int status, String message) throws IOException {
// Required for project generation as the metadata is read first
mockSuccessfulMetadataGet();
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
mockHttpEntity(response, createJsonError(status, message).getBytes(), "application/json");
mockStatus(response, status);
when(httpClient.execute(isA(HttpGet.class))).thenReturn(response);
}
protected void mockMetadataGetError(int status, String message) throws IOException {
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
mockHttpEntity(response, createJsonError(status, message).getBytes(), "application/json");
mockStatus(response, status);
when(httpClient.execute(isA(HttpGet.class))).thenReturn(response);
}
protected HttpEntity mockHttpEntity(CloseableHttpResponse response, byte[] content, String contentType) {
try {
HttpEntity entity = mock(HttpEntity.class);
when(entity.getContent()).thenReturn(new ByteArrayInputStream(content));
Header contentTypeHeader = contentType != null ? new BasicHeader("Content-Type", contentType) : null;
when(entity.getContentType()).thenReturn(contentTypeHeader);
when(response.getEntity()).thenReturn(entity);
return entity;
}
catch (IOException e) {
throw new IllegalStateException("Should not happen", e);
}
}
protected void mockStatus(CloseableHttpResponse response, int status) {
StatusLine statusLine = mock(StatusLine.class);
when(statusLine.getStatusCode()).thenReturn(status);
when(response.getStatusLine()).thenReturn(statusLine);
}
protected void mockHttpHeader(CloseableHttpResponse response, String headerName, String value) {
Header header = value != null ? new BasicHeader(headerName, value) : null;
when(response.getFirstHeader(headerName)).thenReturn(header);
}
protected Matcher<HttpGet> getForJsonData() {
return new HasAcceptHeader("application/json", true);
}
protected Matcher<HttpGet> getForNonJsonData() {
return new HasAcceptHeader("application/json", false);
}
private String contentDispositionValue(String fileName) {
return "attachment; filename=\"" + fileName + "\"";
}
private String createJsonError(int status, String message) {
JSONObject json = new JSONObject();
json.put("status", status);
if (message != null) {
json.put("message", message);
}
return json.toString();
}
protected static class MockHttpProjectGenerationRequest {
String contentType;
String fileName;
byte[] content = new byte[] {0, 0, 0, 0};
public MockHttpProjectGenerationRequest(String contentType, String fileName, byte[] content) {
this.contentType = contentType;
this.fileName = fileName;
this.content = content;
}
public MockHttpProjectGenerationRequest(String contentType, String fileName) {
this(contentType, fileName, new byte[] {0, 0, 0, 0});
}
}
private static class HasAcceptHeader extends ArgumentMatcher<HttpGet> {
private final String value;
private final boolean shouldMatch;
public HasAcceptHeader(String value, boolean shouldMatch) {
this.value = value;
this.shouldMatch = shouldMatch;
}
@Override
public boolean matches(Object argument) {
if (!(argument instanceof HttpGet)) {
return false;
}
HttpGet get = (HttpGet) argument;
Header acceptHeader = get.getFirstHeader(HttpHeaders.ACCEPT);
if (shouldMatch) {
return acceptHeader != null && value.equals(acceptHeader.getValue());
}
else {
return acceptHeader == null || !value.equals(acceptHeader.getValue());
}
}
}
}

View File

@@ -0,0 +1,296 @@
/*
* Copyright 2012-2014 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 org.springframework.boot.cli.command.init;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import joptsimple.OptionSet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.springframework.boot.cli.command.status.ExitStatus;
import static org.junit.Assert.*;
/**
* Tests for {@link InitCommand}
*
* @author Stephane Nicoll
*/
public class InitCommandTests extends AbstractHttpClientMockTests {
@Rule
public final TemporaryFolder folder = new TemporaryFolder();
private final TestableInitCommandOptionHandler handler = new TestableInitCommandOptionHandler(httpClient);
private final InitCommand command = new InitCommand(handler);
@Test
public void listServiceCapabilities() throws Exception {
mockSuccessfulMetadataGet();
command.run("--list", "--target=http://fake-service");
}
@Test
public void generateProject() throws Exception {
String fileName = UUID.randomUUID().toString() + ".zip";
File f = new File(fileName);
assertFalse("file should not exist", f.exists());
MockHttpProjectGenerationRequest mockHttpRequest =
new MockHttpProjectGenerationRequest("application/zip", fileName);
mockSuccessfulProjectGeneration(mockHttpRequest);
try {
assertEquals(ExitStatus.OK, command.run());
assertTrue("file should have been created", f.exists());
}
finally {
assertTrue("failed to delete test file", f.delete());
}
}
@Test
public void generateProjectNoFileNameAvailable() throws Exception {
MockHttpProjectGenerationRequest mockHttpRequest =
new MockHttpProjectGenerationRequest("application/zip", null);
mockSuccessfulProjectGeneration(mockHttpRequest);
assertEquals(ExitStatus.ERROR, command.run());
}
@Test
public void generateProjectAndExtract() throws Exception {
File f = folder.newFolder();
byte[] archive = createFakeZipArchive("test.txt", "Fake content");
MockHttpProjectGenerationRequest mockHttpRequest =
new MockHttpProjectGenerationRequest("application/zip", "demo.zip", archive);
mockSuccessfulProjectGeneration(mockHttpRequest);
assertEquals(ExitStatus.OK, command.run("--extract", "--output=" + f.getAbsolutePath()));
File archiveFile = new File(f, "test.txt");
assertTrue("Archive not extracted properly " + f.getAbsolutePath() + " not found", archiveFile.exists());
}
@Test
public void generateProjectAndExtractUnsupportedArchive() throws Exception {
File f = folder.newFolder();
String fileName = UUID.randomUUID().toString() + ".zip";
File archiveFile = new File(fileName);
assertFalse("file should not exist", archiveFile.exists());
try {
byte[] archive = createFakeZipArchive("test.txt", "Fake content");
MockHttpProjectGenerationRequest mockHttpRequest =
new MockHttpProjectGenerationRequest("application/foobar", fileName, archive);
mockSuccessfulProjectGeneration(mockHttpRequest);
assertEquals(ExitStatus.OK, command.run("--extract", "--output=" + f.getAbsolutePath()));
assertTrue("file should have been saved instead", archiveFile.exists());
}
finally {
assertTrue("failed to delete test file", archiveFile.delete());
}
}
@Test
public void generateProjectAndExtractUnknownContentType() throws Exception {
File f = folder.newFolder();
String fileName = UUID.randomUUID().toString() + ".zip";
File archiveFile = new File(fileName);
assertFalse("file should not exist", archiveFile.exists());
try {
byte[] archive = createFakeZipArchive("test.txt", "Fake content");
MockHttpProjectGenerationRequest mockHttpRequest =
new MockHttpProjectGenerationRequest(null, fileName, archive);
mockSuccessfulProjectGeneration(mockHttpRequest);
assertEquals(ExitStatus.OK, command.run("--extract", "--output=" + f.getAbsolutePath()));
assertTrue("file should have been saved instead", archiveFile.exists());
}
finally {
assertTrue("failed to delete test file", archiveFile.delete());
}
}
@Test
public void fileNotOverwrittenByDefault() throws Exception {
File f = folder.newFile();
long fileLength = f.length();
MockHttpProjectGenerationRequest mockHttpRequest =
new MockHttpProjectGenerationRequest("application/zip", f.getAbsolutePath());
mockSuccessfulProjectGeneration(mockHttpRequest);
assertEquals("Should have failed", ExitStatus.ERROR, command.run());
assertEquals("File should not have changed", fileLength, f.length());
}
@Test
public void overwriteFile() throws Exception {
File f = folder.newFile();
long fileLength = f.length();
MockHttpProjectGenerationRequest mockHttpRequest =
new MockHttpProjectGenerationRequest("application/zip", f.getAbsolutePath());
mockSuccessfulProjectGeneration(mockHttpRequest);
assertEquals("Should not have failed", ExitStatus.OK, command.run("--force"));
assertTrue("File should have changed", fileLength != f.length());
}
@Test
public void fileInArchiveNotOverwrittenByDefault() throws Exception {
File f = folder.newFolder();
File conflict = new File(f, "test.txt");
assertTrue("Should have been able to create file", conflict.createNewFile());
long fileLength = conflict.length();
// also contains test.txt
byte[] archive = createFakeZipArchive("test.txt", "Fake content");
MockHttpProjectGenerationRequest mockHttpRequest =
new MockHttpProjectGenerationRequest("application/zip", "demo.zip", archive);
mockSuccessfulProjectGeneration(mockHttpRequest);
assertEquals(ExitStatus.ERROR, command.run("--extract", "--output=" + f.getAbsolutePath()));
assertEquals("File should not have changed", fileLength, conflict.length());
}
@Test
public void overwriteFileInArchive() throws Exception {
File f = folder.newFolder();
File conflict = new File(f, "test.txt");
assertTrue("Should have been able to create file", conflict.createNewFile());
long fileLength = conflict.length();
// also contains test.txt
byte[] archive = createFakeZipArchive("test.txt", "Fake content");
MockHttpProjectGenerationRequest mockHttpRequest =
new MockHttpProjectGenerationRequest("application/zip", "demo.zip", archive);
mockSuccessfulProjectGeneration(mockHttpRequest);
assertEquals(ExitStatus.OK, command.run("--force", "--extract", "--output=" + f.getAbsolutePath()));
assertTrue("File should have changed", fileLength != conflict.length());
}
@Test
public void parseProjectOptions() throws Exception {
handler.disableProjectGeneration();
command.run("-bv=1.2.0.RELEASE", "-d=web,data-jpa", "-jv=1.9", "-p=war",
"--build=grunt", "--format=web", "-t=ant-project");
assertEquals("1.2.0.RELEASE", handler.lastRequest.getBootVersion());
List<String> dependencies = handler.lastRequest.getDependencies();
assertEquals(2, dependencies.size());
assertTrue(dependencies.contains("web"));
assertTrue(dependencies.contains("data-jpa"));
assertEquals("1.9", handler.lastRequest.getJavaVersion());
assertEquals("war", handler.lastRequest.getPackaging());
assertEquals("grunt", handler.lastRequest.getBuild());
assertEquals("web", handler.lastRequest.getFormat());
assertEquals("ant-project", handler.lastRequest.getType());
}
@Test
public void parseTypeOnly() throws Exception {
handler.disableProjectGeneration();
command.run("-t=ant-project");
assertEquals("maven", handler.lastRequest.getBuild());
assertEquals("project", handler.lastRequest.getFormat());
assertFalse(handler.lastRequest.isDetectType());
assertEquals("ant-project", handler.lastRequest.getType());
}
@Test
public void parseBuildOnly() throws Exception {
handler.disableProjectGeneration();
command.run("--build=ant");
assertEquals("ant", handler.lastRequest.getBuild());
assertEquals("project", handler.lastRequest.getFormat());
assertTrue(handler.lastRequest.isDetectType());
assertNull(handler.lastRequest.getType());
}
@Test
public void parseFormatOnly() throws Exception {
handler.disableProjectGeneration();
command.run("--format=web");
assertEquals("maven", handler.lastRequest.getBuild());
assertEquals("web", handler.lastRequest.getFormat());
assertTrue(handler.lastRequest.isDetectType());
assertNull(handler.lastRequest.getType());
}
@Test
public void parseOutput() throws Exception {
handler.disableProjectGeneration();
command.run("--output=foobar.zip");
assertEquals("foobar.zip", handler.lastRequest.getOutput());
}
private byte[] createFakeZipArchive(String fileName, String content) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ZipOutputStream zos = new ZipOutputStream(out);
try {
ZipEntry entry = new ZipEntry(fileName);
zos.putNextEntry(entry);
zos.write(content.getBytes());
zos.closeEntry();
}
finally {
out.close();
}
return out.toByteArray();
}
private static class TestableInitCommandOptionHandler extends InitCommandOptionHandler {
private boolean disableProjectGeneration;
ProjectGenerationRequest lastRequest;
TestableInitCommandOptionHandler(CloseableHttpClient httpClient) {
super(httpClient);
}
void disableProjectGeneration() {
disableProjectGeneration = true;
}
@Override
protected ExitStatus generateProject(OptionSet options, CloseableHttpClient httpClient) {
lastRequest = createProjectGenerationRequest(options);
if (!disableProjectGeneration) {
return super.generateProject(options, httpClient);
}
return ExitStatus.OK;
}
}
}

View File

@@ -0,0 +1,177 @@
/*
* Copyright 2012-2014 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 org.springframework.boot.cli.command.init;
import java.io.IOException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static junit.framework.TestCase.assertNotNull;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.*;
/**
* Tests for {@link InitializrServiceHttpInvoker}
*
* @author Stephane Nicoll
*/
public class InitializrServiceHttpInvokerTests extends AbstractHttpClientMockTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
private final InitializrServiceHttpInvoker invoker = new InitializrServiceHttpInvoker(httpClient);
@Test
public void loadMetadata() throws IOException {
mockSuccessfulMetadataGet();
InitializrServiceMetadata metadata = invoker.loadMetadata("http://foo/bar");
assertNotNull(metadata);
}
@Test
public void generateSimpleProject() throws IOException {
ProjectGenerationRequest request = new ProjectGenerationRequest();
MockHttpProjectGenerationRequest mockHttpRequest =
new MockHttpProjectGenerationRequest("application/xml", "foo.zip");
ProjectGenerationResponse entity = generateProject(request, mockHttpRequest);
assertProjectEntity(entity, mockHttpRequest.contentType, mockHttpRequest.fileName);
}
@Test
public void generateProjectCustomTargetFilename() throws IOException {
ProjectGenerationRequest request = new ProjectGenerationRequest();
request.setOutput("bar.zip");
MockHttpProjectGenerationRequest mockHttpRequest =
new MockHttpProjectGenerationRequest("application/xml", null);
ProjectGenerationResponse entity = generateProject(request, mockHttpRequest);
assertProjectEntity(entity, mockHttpRequest.contentType, null);
}
@Test
public void generateProjectNoDefaultFileName() throws IOException {
ProjectGenerationRequest request = new ProjectGenerationRequest();
MockHttpProjectGenerationRequest mockHttpRequest =
new MockHttpProjectGenerationRequest("application/xml", null);
ProjectGenerationResponse entity = generateProject(request, mockHttpRequest);
assertProjectEntity(entity, mockHttpRequest.contentType, null);
}
@Test
public void generateProjectBadRequest() throws IOException {
String jsonMessage = "Unknown dependency foo:bar";
mockProjectGenerationError(400, jsonMessage);
ProjectGenerationRequest request = new ProjectGenerationRequest();
request.getDependencies().add("foo:bar");
thrown.expect(ProjectGenerationException.class);
thrown.expectMessage(jsonMessage);
invoker.generate(request);
}
@Test
public void generateProjectBadRequestNoExtraMessage() throws IOException {
mockProjectGenerationError(400, null);
ProjectGenerationRequest request = new ProjectGenerationRequest();
thrown.expect(ProjectGenerationException.class);
thrown.expectMessage("unexpected 400 error");
invoker.generate(request);
}
@Test
public void generateProjectNoContent() throws IOException {
mockSuccessfulMetadataGet();
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
mockStatus(response, 500);
when(httpClient.execute(isA(HttpGet.class))).thenReturn(response);
ProjectGenerationRequest request = new ProjectGenerationRequest();
thrown.expect(ProjectGenerationException.class);
thrown.expectMessage("No content received from server");
invoker.generate(request);
}
@Test
public void loadMetadataBadRequest() throws IOException {
String jsonMessage = "whatever error on the server";
mockMetadataGetError(500, jsonMessage);
ProjectGenerationRequest request = new ProjectGenerationRequest();
thrown.expect(ProjectGenerationException.class);
thrown.expectMessage(jsonMessage);
invoker.generate(request);
}
@Test
public void loadMetadataInvalidJson() throws IOException {
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
mockHttpEntity(response, "Foo-Bar-Not-JSON".getBytes(), "application/json");
mockStatus(response, 200);
when(httpClient.execute(isA(HttpGet.class))).thenReturn(response);
ProjectGenerationRequest request = new ProjectGenerationRequest();
thrown.expect(ProjectGenerationException.class);
thrown.expectMessage("Invalid content received from server");
invoker.generate(request);
}
@Test
public void loadMetadataNoContent() throws IOException {
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
mockStatus(response, 500);
when(httpClient.execute(isA(HttpGet.class))).thenReturn(response);
ProjectGenerationRequest request = new ProjectGenerationRequest();
thrown.expect(ProjectGenerationException.class);
thrown.expectMessage("No content received from server");
invoker.generate(request);
}
private ProjectGenerationResponse generateProject(ProjectGenerationRequest request,
MockHttpProjectGenerationRequest mockRequest) throws IOException {
mockSuccessfulProjectGeneration(mockRequest);
ProjectGenerationResponse entity = invoker.generate(request);
assertArrayEquals("wrong body content", mockRequest.content, entity.getContent());
return entity;
}
private static void assertProjectEntity(ProjectGenerationResponse entity, String mimeType, String fileName) {
if (mimeType == null) {
assertNull("No content type expected", entity.getContentType());
}
else {
assertEquals("wrong mime type", mimeType, entity.getContentType().getMimeType());
}
assertEquals("wrong filename", fileName, entity.getFileName());
}
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright 2012-2014 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 org.springframework.boot.cli.command.init;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import org.json.JSONObject;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.util.StreamUtils;
import static org.junit.Assert.*;
/**
* Tests for {@link InitializrServiceMetadata}
*
* @author Stephane Nicoll
*/
public class InitializrServiceMetadataTests {
@Test
public void parseDefaults() {
InitializrServiceMetadata metadata = createInstance("1.0.0");
assertEquals("maven-project", metadata.getDefaults().get("type"));
assertEquals("jar", metadata.getDefaults().get("packaging"));
assertEquals("java", metadata.getDefaults().get("language"));
}
@Test
public void parseDependencies() {
InitializrServiceMetadata metadata = createInstance("1.0.0");
assertEquals(5, metadata.getDependencies().size());
// Security description
assertEquals("AOP", metadata.getDependency("aop").getName());
assertEquals("Security", metadata.getDependency("security").getName());
assertEquals("Security description", metadata.getDependency("security").getDescription());
assertEquals("JDBC", metadata.getDependency("jdbc").getName());
assertEquals("JPA", metadata.getDependency("data-jpa").getName());
assertEquals("MongoDB", metadata.getDependency("data-mongodb").getName());
}
@Test
public void parseTypesNoTag() {
InitializrServiceMetadata metadata = createInstance("1.0.0");
ProjectType projectType = metadata.getProjectTypes().get("maven-project");
assertNotNull(projectType);
assertEquals(0, projectType.getTags().size());
}
@Test
public void parseTypesWithTags() {
InitializrServiceMetadata metadata = createInstance("1.1.0");
ProjectType projectType = metadata.getProjectTypes().get("maven-project");
assertNotNull(projectType);
assertEquals("maven", projectType.getTags().get("build"));
assertEquals("project", projectType.getTags().get("format"));
}
private static InitializrServiceMetadata createInstance(String version) {
try {
return new InitializrServiceMetadata(readJson(version));
}
catch (IOException e) {
throw new IllegalStateException("Failed to read json", e);
}
}
private static JSONObject readJson(String version) throws IOException {
Resource resource = new ClassPathResource("metadata/service-metadata-" + version + ".json");
InputStream stream = resource.getInputStream();
try {
String json = StreamUtils.copyToString(stream, Charset.forName("UTF-8"));
return new JSONObject(json);
}
finally {
stream.close();
}
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2012-2014 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 org.springframework.boot.cli.command.init;
import java.io.IOException;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Tests for {@link ListMetadataCommand}
*
* @author Stephane Nicoll
*/
public class ListMetadataCommandTests extends AbstractHttpClientMockTests {
private final ListMetadataCommand command = new ListMetadataCommand(httpClient);
@Test
public void listMetadata() throws IOException {
mockSuccessfulMetadataGet();
String content = command.generateReport("http://localhost");
assertTrue(content.contains("aop - AOP"));
assertTrue(content.contains("security - Security: Security description"));
assertTrue(content.contains("type: maven-project"));
assertTrue(content.contains("packaging: jar"));
}
}

View File

@@ -0,0 +1,194 @@
/*
* Copyright 2012-2014 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 org.springframework.boot.cli.command.init;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.Map;
import org.json.JSONObject;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.util.StreamUtils;
import static org.junit.Assert.*;
/**
* Tests for {@link ProjectGenerationRequest}
*
* @author Stephane Nicoll
*/
public class ProjectGenerationRequestTests {
public static final Map<String, String> EMPTY_TAGS = Collections.<String, String>emptyMap();
@Rule
public final ExpectedException thrown = ExpectedException.none();
private final ProjectGenerationRequest request = new ProjectGenerationRequest();
@Test
public void defaultSettings() {
assertEquals(createDefaultUrl("?type=test-type"), request.generateUrl(createDefaultMetadata()));
}
@Test
public void customServer() throws URISyntaxException {
String customServerUrl = "http://foo:8080/initializr";
request.setServiceUrl(customServerUrl);
request.getDependencies().add("security");
assertEquals(new URI(customServerUrl + "/starter.zip?style=security&type=test-type"),
request.generateUrl(createDefaultMetadata()));
}
@Test
public void customBootVersion() {
request.setBootVersion("1.2.0.RELEASE");
assertEquals(createDefaultUrl("?bootVersion=1.2.0.RELEASE&type=test-type"),
request.generateUrl(createDefaultMetadata()));
}
@Test
public void singleDependency() {
request.getDependencies().add("web");
assertEquals(createDefaultUrl("?style=web&type=test-type"),
request.generateUrl(createDefaultMetadata()));
}
@Test
public void multipleDependencies() {
request.getDependencies().add("web");
request.getDependencies().add("data-jpa");
assertEquals(createDefaultUrl("?style=web&style=data-jpa&type=test-type"),
request.generateUrl(createDefaultMetadata()));
}
@Test
public void customJavaVersion() {
request.setJavaVersion("1.8");
assertEquals(createDefaultUrl("?javaVersion=1.8&type=test-type"),
request.generateUrl(createDefaultMetadata()));
}
@Test
public void customPackaging() {
request.setPackaging("war");
assertEquals(createDefaultUrl("?packaging=war&type=test-type"),
request.generateUrl(createDefaultMetadata()));
}
@Test
public void customType() throws URISyntaxException {
ProjectType projectType = new ProjectType("custom", "Custom Type", "/foo", true, EMPTY_TAGS);
InitializrServiceMetadata metadata = new InitializrServiceMetadata(projectType);
request.setType("custom");
request.getDependencies().add("data-rest");
assertEquals(new URI(ProjectGenerationRequest.DEFAULT_SERVICE_URL + "/foo?style=data-rest&type=custom"),
request.generateUrl(metadata));
}
@Test
public void buildNoMatch() {
InitializrServiceMetadata metadata = readMetadata();
setBuildAndFormat("does-not-exist", null);
thrown.expect(ProjectGenerationException.class);
thrown.expectMessage("does-not-exist");
request.generateUrl(metadata);
}
@Test
public void buildMultipleMatch() {
InitializrServiceMetadata metadata = readMetadata("types-conflict");
setBuildAndFormat("gradle", null);
thrown.expect(ProjectGenerationException.class);
thrown.expectMessage("gradle-project");
thrown.expectMessage("gradle-project-2");
request.generateUrl(metadata);
}
@Test
public void buildOneMatch() {
InitializrServiceMetadata metadata = readMetadata();
setBuildAndFormat("gradle", null);
assertEquals(createDefaultUrl("?type=gradle-project"), request.generateUrl(metadata));
}
@Test
public void invalidType() throws URISyntaxException {
request.setType("does-not-exist");
thrown.expect(ProjectGenerationException.class);
request.generateUrl(createDefaultMetadata());
}
@Test
public void noTypeAndNoDefault() throws URISyntaxException {
thrown.expect(ProjectGenerationException.class);
thrown.expectMessage("no default is defined");
request.generateUrl(readMetadata("types-conflict"));
}
private static URI createDefaultUrl(String param) {
try {
return new URI(ProjectGenerationRequest.DEFAULT_SERVICE_URL + "/starter.zip" + param);
}
catch (URISyntaxException e) {
throw new IllegalStateException(e);
}
}
public void setBuildAndFormat(String build, String format) {
request.setBuild(build != null ? build : "maven");
request.setFormat(format != null ? format : "project");
request.setDetectType(true);
}
private static InitializrServiceMetadata createDefaultMetadata() {
ProjectType projectType = new ProjectType("test-type", "The test type", "/starter.zip", true, EMPTY_TAGS);
return new InitializrServiceMetadata(projectType);
}
private static InitializrServiceMetadata readMetadata() {
return readMetadata("1.1.0");
}
private static InitializrServiceMetadata readMetadata(String version) {
try {
Resource resource = new ClassPathResource("metadata/service-metadata-" + version + ".json");
String content = StreamUtils.copyToString(resource.getInputStream(), Charset.forName("UTF-8"));
JSONObject json = new JSONObject(content);
return new InitializrServiceMetadata(json);
}
catch (IOException e) {
throw new IllegalStateException("Failed to read metadata", e);
}
}
}