Improve mod/runtimeapi code coverage
[dcaegen2/platform.git] / mod / runtimeapi / runtime-web / src / test / java / org / onap / dcae / runtime / web / ClientMocking.java
1 /*-
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
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
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=========================================================
17  */
18
19 package org.onap.dcae.runtime.web;
20
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;
27
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
30
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;
42
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;
48
49 import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
50 import org.springframework.test.util.ReflectionTestUtils;
51 import org.springframework.web.client.RestTemplate;
52
53 /**
54  * Simulate responses from RestTemplate based clients.
55  */
56
57 public class ClientMocking implements Answer<HttpResponse> {
58         private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
59         /**
60          * Replace single quotes with double quotes.
61          * Makes it easy to write JSON strings.
62          *
63          * @param s String with single quotes.
64          * @return String replacing single quotes with double quotes.
65          */
66         public static String xq(String s) {
67                 return s.replaceAll("'", "\"");
68         }
69
70         private static class BasicCloseableHttpResponse extends BasicHttpResponse implements CloseableHttpResponse {
71                 public BasicCloseableHttpResponse(StatusLine line) {
72                         super(line);
73                 }
74
75                 @Override
76                 public void close() throws IOException {
77                         // no op
78                 }
79         }
80
81         /**
82          * Information on request being processed
83          */
84         public static class RequestInfo {
85                 private HttpUriRequest req;
86                 private String line;
87
88                 /**
89                  * Collect information on a request.
90                  * @param req The request to examine.
91                  */
92                 public RequestInfo(HttpUriRequest req) {
93                         this.req = req;
94                         line = getMethod() + " " + getPath() + (req.getURI().getQuery() == null ? "": ("?" + req.getURI().getQuery()));
95                 }
96
97                 /**
98                  * Get the method.
99                  *
100                  * @return The HTTP method of the request.
101                  */
102                 public String getMethod() {
103                         return req.getMethod();
104                 }
105
106                 /**
107                  * Get the method and URI
108                  *
109                  * @return The method and URI of the request, including any query string.
110                  */
111                 public String getLine() {
112                         return line;
113                 }
114
115                 /**
116                  * Get the path component of the URI
117                  *
118                  * @return The URI excluding the query string.
119                  */
120                 public String getPath() {
121                         return req.getURI().getPath();
122                 }
123
124                 /**
125                  * Check whether the named header is missing.
126                  *
127                  * @return true if the header is absent.
128                  */
129                 public boolean lacksHeader(String name) {
130                         return req.getFirstHeader(name) == null;
131                 }
132
133                 /**
134                  * Check whether the header has the specified header value.
135                  *
136                  * @return true if the header is missing or has the wrong value.
137                  */
138                 public boolean lacksHeaderValue(String name, String value) {
139                         return req.getFirstHeader(name) == null || !value.equals(req.getFirstHeader(name).getValue());
140                 }
141         }
142
143         private static class Response {
144                 private Predicate<RequestInfo> matcher;
145                 private byte[] body;
146                 private Consumer<RequestInfo> action;
147                 private int code = 200;
148                 private String message = "OK";
149                 private ContentType contentType = ContentType.APPLICATION_JSON;
150         }
151
152         private static Predicate<RequestInfo> s2p(String line) {
153                 return r -> r.getLine().equals(line);
154         }
155
156         private ArrayList<Response> responses;
157         private HttpClient client;
158
159         /**
160          * Create a responder to handle requests.
161          */
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);
166         }
167
168         /**
169          * Redirect the template to this responder.
170          *
171          * @param template The RestTemplate to redirect.
172          * @return This responder.
173          */
174         public ClientMocking applyTo(RestTemplate template) {
175                 template.setRequestFactory(new HttpComponentsClientHttpRequestFactory(client));
176                 return this;
177         }
178
179         /**
180          * Set the identified RestTemplate field to use this responder.
181          *
182          * @param object The object containing the RestTemplate.
183          * @param fieldName The name of the RestTemplate field in the object.
184          * @return This responder.
185          */
186         public ClientMocking applyTo(Object object, String fieldName) {
187                 return applyTo((RestTemplate)ReflectionTestUtils.getField(object, fieldName));
188         }
189
190         /**
191          * Set the restTemplate field of an object to use this responder.
192          *
193          * Typically, the object will be a FederationClient or a GatewayClient.
194          * @param object The object with the restTemplate field.
195          * @return This responder.
196          */
197         public ClientMocking applyTo(Object object) {
198                 return applyTo(object, "restTemplate");
199         }
200
201         @Override
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);
208                                 }
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());
215                                         }
216                                 }
217                                 if (log.isInfoEnabled()) {
218                                         log.info("Mock client response to {} is {}", info.getLine(), (r.body == null? "null": new String(r.body)));
219                                 }
220                                 return ret;
221                         }
222                 }
223                 throw new IOException("Mock unhandled " + info.getLine());
224         }
225
226         /**
227          * Handle the specified requests.
228          *
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.
234          */
235         public ClientMocking on(Predicate<RequestInfo> matcher, byte[] body, ContentType type, Consumer<RequestInfo> action) {
236                 Response r = new Response();
237                 r.matcher = matcher;
238                 r.body = body;
239                 r.contentType = type;
240                 r.action = action;
241                 responses.add(r);
242                 return this;
243         }
244
245         /**
246          * Handle the specified request.
247          *
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.
253          */
254         public ClientMocking on(String line, String body, ContentType type, Consumer<RequestInfo> action) {
255                 return on(s2p(line), body.getBytes(), type, action);
256         }
257
258         /**
259          * Handle the specified request.
260          *
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.
265          */
266         public ClientMocking on(String line, String body, Consumer<RequestInfo> action) {
267                 return on(line, body, ContentType.APPLICATION_JSON, action);
268         }
269
270         /**
271          * Handle the specified request.
272          *
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.
276          */
277         public ClientMocking on(String line, String body) {
278                 return on(line, body, ContentType.APPLICATION_JSON);
279         }
280
281         /**
282          * Handle the specified request.
283          *
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.
288          */
289         public ClientMocking on(String line, String body, ContentType type) {
290                 return on(line, body, type, null);
291         }
292
293         /**
294          * Fail the specified requests.
295          *
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.
300          */
301         public ClientMocking errorOn(Predicate<RequestInfo> matcher, int code, String message) {
302                 Response r = new Response();
303                 r.matcher = matcher;
304                 r.code = code;
305                 r.message = message;
306                 responses.add(r);
307                 return this;
308         }
309
310         /**
311          * Fail the specified request.
312          *
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.
317          */
318         public ClientMocking errorOn(String line, int code, String message) {
319                 return errorOn(s2p(line), code, message);
320         }
321
322         /**
323          * Fail the request if no auth header
324          *
325          * @param code The HTTP error code to return.
326          * @param message The HTTP error status message to return.
327          * @return This responder.
328          */
329         public ClientMocking errorOnNoAuth(int code, String message) {
330                 return errorOn(ri -> ri.lacksHeader("Authorization"), code, message);
331         }
332
333         /**
334          * Fail the request if wrong auth header
335          *
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.
341          */
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);
345         }
346 }