2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
4 * ================================================================================
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 * ============LICENSE_END=========================================================
19 package org.onap.dcae.runtime.web;
21 import java.io.IOException;
22 import java.lang.invoke.MethodHandles;
23 import java.util.Base64;
24 import java.util.function.Consumer;
25 import java.util.function.Predicate;
26 import java.util.ArrayList;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
31 import org.apache.http.client.HttpClient;
32 import org.apache.http.client.methods.CloseableHttpResponse;
33 import org.apache.http.client.methods.HttpUriRequest;
34 import org.apache.http.entity.ByteArrayEntity;
35 import org.apache.http.entity.ContentType;
36 import org.apache.http.HttpResponse;
37 import org.apache.http.message.BasicHttpResponse;
38 import org.apache.http.message.BasicStatusLine;
39 import org.apache.http.protocol.HttpContext;
40 import org.apache.http.ProtocolVersion;
41 import org.apache.http.StatusLine;
43 import org.mockito.invocation.InvocationOnMock;
44 import org.mockito.stubbing.Answer;
45 import static org.mockito.ArgumentMatchers.any;
46 import static org.mockito.Mockito.mock;
47 import static org.mockito.Mockito.when;
49 import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
50 import org.springframework.test.util.ReflectionTestUtils;
51 import org.springframework.web.client.RestTemplate;
54 * Simulate responses from RestTemplate based clients.
57 public class ClientMocking implements Answer<HttpResponse> {
58 private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
60 * Replace single quotes with double quotes.
61 * Makes it easy to write JSON strings.
63 * @param s String with single quotes.
64 * @return String replacing single quotes with double quotes.
66 public static String xq(String s) {
67 return s.replaceAll("'", "\"");
70 private static class BasicCloseableHttpResponse extends BasicHttpResponse implements CloseableHttpResponse {
71 public BasicCloseableHttpResponse(StatusLine line) {
76 public void close() throws IOException {
82 * Information on request being processed
84 public static class RequestInfo {
85 private HttpUriRequest req;
89 * Collect information on a request.
90 * @param req The request to examine.
92 public RequestInfo(HttpUriRequest req) {
94 line = getMethod() + " " + getPath() + (req.getURI().getQuery() == null ? "": ("?" + req.getURI().getQuery()));
100 * @return The HTTP method of the request.
102 public String getMethod() {
103 return req.getMethod();
107 * Get the method and URI
109 * @return The method and URI of the request, including any query string.
111 public String getLine() {
116 * Get the path component of the URI
118 * @return The URI excluding the query string.
120 public String getPath() {
121 return req.getURI().getPath();
125 * Check whether the named header is missing.
127 * @return true if the header is absent.
129 public boolean lacksHeader(String name) {
130 return req.getFirstHeader(name) == null;
134 * Check whether the header has the specified header value.
136 * @return true if the header is missing or has the wrong value.
138 public boolean lacksHeaderValue(String name, String value) {
139 return req.getFirstHeader(name) == null || !value.equals(req.getFirstHeader(name).getValue());
143 private static class Response {
144 private Predicate<RequestInfo> matcher;
146 private Consumer<RequestInfo> action;
147 private int code = 200;
148 private String message = "OK";
149 private ContentType contentType = ContentType.APPLICATION_JSON;
152 private static Predicate<RequestInfo> s2p(String line) {
153 return r -> r.getLine().equals(line);
156 private ArrayList<Response> responses;
157 private HttpClient client;
160 * Create a responder to handle requests.
162 public ClientMocking() throws IOException {
163 responses = new ArrayList<>();
164 client = mock(HttpClient.class);
165 when(client.execute(any(HttpUriRequest.class), any(HttpContext.class))).thenAnswer(this);
169 * Redirect the template to this responder.
171 * @param template The RestTemplate to redirect.
172 * @return This responder.
174 public ClientMocking applyTo(RestTemplate template) {
175 template.setRequestFactory(new HttpComponentsClientHttpRequestFactory(client));
180 * Set the identified RestTemplate field to use this responder.
182 * @param object The object containing the RestTemplate.
183 * @param fieldName The name of the RestTemplate field in the object.
184 * @return This responder.
186 public ClientMocking applyTo(Object object, String fieldName) {
187 return applyTo((RestTemplate)ReflectionTestUtils.getField(object, fieldName));
191 * Set the restTemplate field of an object to use this responder.
193 * Typically, the object will be a FederationClient or a GatewayClient.
194 * @param object The object with the restTemplate field.
195 * @return This responder.
197 public ClientMocking applyTo(Object object) {
198 return applyTo(object, "restTemplate");
202 public HttpResponse answer(InvocationOnMock invocation) throws Throwable {
203 RequestInfo info = new RequestInfo((HttpUriRequest)invocation.getArguments()[0]);
204 for (Response r: responses) {
205 if (r.matcher.test(info)) {
206 if (r.action != null) {
207 r.action.accept(info);
209 BasicCloseableHttpResponse ret = new BasicCloseableHttpResponse(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), r.code, r.message));
210 if (r.body != null) {
211 ret.setEntity(new ByteArrayEntity(r.body, r.contentType));
212 ret.addHeader("Content-Length", String.valueOf(r.body.length));
213 if (r.body.length != 0) {
214 ret.addHeader("Content-Type", r.contentType.toString());
217 if (log.isInfoEnabled()) {
218 log.info("Mock client response to {} is {}", info.getLine(), (r.body == null? "null": new String(r.body)));
223 throw new IOException("Mock unhandled " + info.getLine());
227 * Handle the specified requests.
229 * @param matcher A predicate for matching requests.
230 * @param body The response body to return.
231 * @param type The content type of the body.
232 * @param action An action to perform, whenever the request is handled.
233 * @return This responder.
235 public ClientMocking on(Predicate<RequestInfo> matcher, byte[] body, ContentType type, Consumer<RequestInfo> action) {
236 Response r = new Response();
239 r.contentType = type;
246 * Handle the specified request.
248 * @param line The HTTP method and URI to handle.
249 * @param body The response body to return.
250 * @param type The content type of the body.
251 * @param action An action to perform, whenever the request is handled.
252 * @return This responder.
254 public ClientMocking on(String line, String body, ContentType type, Consumer<RequestInfo> action) {
255 return on(s2p(line), body.getBytes(), type, action);
259 * Handle the specified request.
261 * @param line The HTTP method and URI to handle.
262 * @param body The response body to return, as a JSON string.
263 * @param action An action to perform, whenever the request is handled.
264 * @return This responder.
266 public ClientMocking on(String line, String body, Consumer<RequestInfo> action) {
267 return on(line, body, ContentType.APPLICATION_JSON, action);
271 * Handle the specified request.
273 * @param line The HTTP method and URI to handle.
274 * @param body The response body to return, as a JSON string.
275 * @return This responder.
277 public ClientMocking on(String line, String body) {
278 return on(line, body, ContentType.APPLICATION_JSON);
282 * Handle the specified request.
284 * @param line The HTTP method and URI to handle.
285 * @param body The response body to return, as a JSON string.
286 * @param type The content type of the body.
287 * @return This responder.
289 public ClientMocking on(String line, String body, ContentType type) {
290 return on(line, body, type, null);
294 * Fail the specified requests.
296 * @param matcher A predicate for matching requests.
297 * @param code The HTTP error code to return.
298 * @param message The HTTP error status message to return.
299 * @return This responder.
301 public ClientMocking errorOn(Predicate<RequestInfo> matcher, int code, String message) {
302 Response r = new Response();
311 * Fail the specified request.
313 * @param line The HTTP method and URI to handle.
314 * @param code The HTTP error code to return.
315 * @param message The HTTP error status message to return.
316 * @return This responder.
318 public ClientMocking errorOn(String line, int code, String message) {
319 return errorOn(s2p(line), code, message);
323 * Fail the request if no auth header
325 * @param code The HTTP error code to return.
326 * @param message The HTTP error status message to return.
327 * @return This responder.
329 public ClientMocking errorOnNoAuth(int code, String message) {
330 return errorOn(ri -> ri.lacksHeader("Authorization"), code, message);
334 * Fail the request if wrong auth header
336 * @param username The expected user name.
337 * @param password The expected password.
338 * @param code The HTTP error code to return.
339 * @param message The HTTP error status message to return.
340 * @return This responder.
342 public ClientMocking errorOnBadAuth(String username, String password, int code, String message) {
343 String authhdr = "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes());
344 return errorOn(ri -> ri.lacksHeaderValue("Authorization", authhdr), code, message);