/* * Copyright 2012-2017 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.json.JSONException; 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.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.isA; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** * Abstract base class for tests that use a mock {@link CloseableHttpClient}. * * @author Stephane Nicoll */ public abstract class AbstractHttpClientMockTests { protected final CloseableHttpClient http = mock(CloseableHttpClient.class); protected void mockSuccessfulMetadataTextGet() throws IOException { mockSuccessfulMetadataGet("metadata/service-metadata-2.1.0.txt", "text/plain", true); } protected void mockSuccessfulMetadataGet(boolean serviceCapabilities) throws IOException { mockSuccessfulMetadataGet("metadata/service-metadata-2.1.0.json", "application/vnd.initializr.v2.1+json", serviceCapabilities); } protected void mockSuccessfulMetadataGetV2(boolean serviceCapabilities) throws IOException { mockSuccessfulMetadataGet("metadata/service-metadata-2.0.0.json", "application/vnd.initializr.v2+json", serviceCapabilities); } protected void mockSuccessfulMetadataGet(String contentPath, String contentType, boolean serviceCapabilities) throws IOException { CloseableHttpResponse response = mock(CloseableHttpResponse.class); byte[] content = readClasspathResource(contentPath); mockHttpEntity(response, content, contentType); mockStatus(response, 200); given(this.http.execute(argThat(getForMetadata(serviceCapabilities)))) .willReturn(response); } protected byte[] readClasspathResource(String contentPath) throws IOException { Resource resource = new ClassPathResource(contentPath); return StreamUtils.copyToByteArray(resource.getInputStream()); } protected void mockSuccessfulProjectGeneration( MockHttpProjectGenerationRequest request) throws IOException { // Required for project generation as the metadata is read first mockSuccessfulMetadataGet(false); 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); given(this.http.execute(argThat(getForNonMetadata()))).willReturn(response); } protected void mockProjectGenerationError(int status, String message) throws IOException, JSONException { // Required for project generation as the metadata is read first mockSuccessfulMetadataGet(false); CloseableHttpResponse response = mock(CloseableHttpResponse.class); mockHttpEntity(response, createJsonError(status, message).getBytes(), "application/json"); mockStatus(response, status); given(this.http.execute(isA(HttpGet.class))).willReturn(response); } protected void mockMetadataGetError(int status, String message) throws IOException, JSONException { CloseableHttpResponse response = mock(CloseableHttpResponse.class); mockHttpEntity(response, createJsonError(status, message).getBytes(), "application/json"); mockStatus(response, status); given(this.http.execute(isA(HttpGet.class))).willReturn(response); } protected HttpEntity mockHttpEntity(CloseableHttpResponse response, byte[] content, String contentType) { try { HttpEntity entity = mock(HttpEntity.class); given(entity.getContent()).willReturn(new ByteArrayInputStream(content)); Header contentTypeHeader = contentType != null ? new BasicHeader("Content-Type", contentType) : null; given(entity.getContentType()).willReturn(contentTypeHeader); given(response.getEntity()).willReturn(entity); return entity; } catch (IOException ex) { throw new IllegalStateException("Should not happen", ex); } } protected void mockStatus(CloseableHttpResponse response, int status) { StatusLine statusLine = mock(StatusLine.class); given(statusLine.getStatusCode()).willReturn(status); given(response.getStatusLine()).willReturn(statusLine); } protected void mockHttpHeader(CloseableHttpResponse response, String headerName, String value) { Header header = value != null ? new BasicHeader(headerName, value) : null; given(response.getFirstHeader(headerName)).willReturn(header); } private ArgumentMatcher<HttpGet> getForMetadata(boolean serviceCapabilities) { if (!serviceCapabilities) { return new HasAcceptHeader(InitializrService.ACCEPT_META_DATA, true); } return new HasAcceptHeader(InitializrService.ACCEPT_SERVICE_CAPABILITIES, true); } private ArgumentMatcher<HttpGet> getForNonMetadata() { return new HasAcceptHeader(InitializrService.ACCEPT_META_DATA, false); } private String contentDispositionValue(String fileName) { return "attachment; filename=\"" + fileName + "\""; } private String createJsonError(int status, String message) throws JSONException { 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) { this(contentType, fileName, new byte[] { 0, 0, 0, 0 }); } public MockHttpProjectGenerationRequest(String contentType, String fileName, byte[] content) { this.contentType = contentType; this.fileName = fileName; this.content = content; } } private static class HasAcceptHeader implements ArgumentMatcher<HttpGet> { private final String value; private final boolean shouldMatch; HasAcceptHeader(String value, boolean shouldMatch) { this.value = value; this.shouldMatch = shouldMatch; } @Override public boolean matches(HttpGet get) { if (get == null) { return false; } Header acceptHeader = get.getFirstHeader(HttpHeaders.ACCEPT); if (this.shouldMatch) { return acceptHeader != null && this.value.equals(acceptHeader.getValue()); } return acceptHeader == null || !this.value.equals(acceptHeader.getValue()); } } }