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:
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user