1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package org.apache.hc.core5.testing.framework;
29
30 import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.BODY;
31 import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.CONTENT_TYPE;
32 import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.HEADERS;
33 import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.METHOD;
34 import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.REQUEST;
35 import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.RESPONSE;
36 import static org.apache.hc.core5.testing.framework.ClientPOJOAdapter.STATUS;
37
38 import java.io.ByteArrayInputStream;
39 import java.io.ByteArrayOutputStream;
40 import java.io.IOException;
41 import java.io.ObjectInputStream;
42 import java.io.ObjectOutputStream;
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.Collections;
46 import java.util.HashMap;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.concurrent.TimeUnit;
50
51 import org.apache.hc.core5.http.HttpVersion;
52 import org.apache.hc.core5.http.ProtocolVersion;
53 import org.apache.hc.core5.http.impl.bootstrap.HttpServer;
54 import org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap;
55 import org.apache.hc.core5.http.impl.routing.RequestRouter;
56 import org.apache.hc.core5.http.io.HttpRequestHandler;
57 import org.apache.hc.core5.http.io.SocketConfig;
58 import org.apache.hc.core5.io.CloseMode;
59
60 public class TestingFramework {
61
62
63
64
65 public static final List<String> ALL_METHODS = Arrays.asList("HEAD", "GET", "DELETE", "POST", "PUT", "PATCH");
66
67
68
69
70
71
72
73 public static final Object ALREADY_CHECKED = new Object();
74
75
76
77
78 public static final String DEFAULT_REQUEST_PATH = "a/path";
79
80
81
82
83 public static final String DEFAULT_REQUEST_BODY = "{\"location\":\"home\"}";
84
85
86
87
88 public static final String DEFAULT_REQUEST_CONTENT_TYPE = "application/json";
89
90
91
92
93 public static final Map<String, String> DEFAULT_REQUEST_QUERY;
94
95
96
97
98 public static final Map<String, String> DEFAULT_REQUEST_HEADERS;
99
100
101
102
103 public static final ProtocolVersion DEFAULT_REQUEST_PROTOCOL_VERSION = HttpVersion.HTTP_1_1;
104
105
106
107
108 public static final int DEFAULT_RESPONSE_STATUS = 200;
109
110
111
112
113 public static final String DEFAULT_RESPONSE_BODY = "{\"location\":\"work\"}";
114
115
116
117
118 public static final String DEFAULT_RESPONSE_CONTENT_TYPE = "application/json";
119
120
121
122
123 public static final Map<String, String> DEFAULT_RESPONSE_HEADERS;
124
125 static {
126 final Map<String, String> request = new HashMap<>();
127 request.put("p1", "this");
128 request.put("p2", "that");
129 DEFAULT_REQUEST_QUERY = Collections.unmodifiableMap(request);
130
131 Map<String, String> headers = new HashMap<>();
132 headers.put("header1", "stuff");
133 headers.put("header2", "more stuff");
134 DEFAULT_REQUEST_HEADERS = Collections.unmodifiableMap(headers);
135
136 headers = new HashMap<>();
137 headers.put("header3", "header_three");
138 headers.put("header4", "header_four");
139 DEFAULT_RESPONSE_HEADERS = Collections.unmodifiableMap(headers);
140 }
141
142 private ClientTestingAdapter adapter;
143 private TestingFrameworkRequestHandler requestHandler = new TestingFrameworkRequestHandler();
144 private List<FrameworkTest> tests = new ArrayList<>();
145
146 private HttpServer server;
147 private int port;
148
149 public TestingFramework() throws TestingFrameworkException {
150 this(null);
151 }
152
153 public TestingFramework(final ClientTestingAdapter adapter) throws TestingFrameworkException {
154 this.adapter = adapter;
155
156
157
158
159 for (final String method : ALL_METHODS) {
160 final List<Integer> statusList = Arrays.asList(200, 201);
161 for (final Integer status : statusList) {
162 final Map<String, Object> request = new HashMap<>();
163 request.put(METHOD, method);
164
165 final Map<String, Object> response = new HashMap<>();
166 response.put(STATUS, status);
167
168 final Map<String, Object> test = new HashMap<>();
169 test.put(REQUEST, request);
170 test.put(RESPONSE, response);
171
172 addTest(test);
173 }
174 }
175 }
176
177
178
179
180
181
182
183 public void setRequestHandler(final TestingFrameworkRequestHandler requestHandler) {
184 this.requestHandler = requestHandler;
185 }
186
187
188
189
190
191
192
193
194 public void runTests() throws TestingFrameworkException {
195 if (adapter == null) {
196 throw new TestingFrameworkException("adapter should not be null");
197 }
198
199 startServer();
200
201 try {
202 for (final FrameworkTest test : tests) {
203 try {
204 callAdapter(test);
205 } catch (final Throwable t) {
206 processThrowable(t, test);
207 }
208 }
209 } finally {
210 stopServer();
211 }
212 }
213
214 private void processThrowable(final Throwable t, final FrameworkTest test) throws TestingFrameworkException {
215 final TestingFrameworkException e;
216 if (t instanceof TestingFrameworkException) {
217 e = (TestingFrameworkException) t;
218 } else {
219 e = new TestingFrameworkException(t);
220 }
221 e.setAdapter(adapter);
222 e.setTest(test);
223 throw e;
224 }
225
226 private void startServer() throws TestingFrameworkException {
227
228
229
230
231 final SocketConfig socketConfig = SocketConfig.custom()
232 .setSoTimeout(15000, TimeUnit.MILLISECONDS)
233 .build();
234
235 final ServerBootstrap serverBootstrap = ServerBootstrap.bootstrap()
236 .setSocketConfig(socketConfig)
237 .setRequestRouter(RequestRouter.<HttpRequestHandler>builder()
238 .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", requestHandler)
239 .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER)
240 .build());
241
242 server = serverBootstrap.create();
243 try {
244 server.start();
245 } catch (final IOException e) {
246 throw new TestingFrameworkException(e);
247 }
248
249 port = server.getLocalPort();
250 }
251
252 private void stopServer() {
253 final HttpServer local = this.server;
254 this.server = null;
255 if (local != null) {
256 local.close(CloseMode.IMMEDIATE);
257 }
258 }
259
260 private void callAdapter(final FrameworkTest test) throws TestingFrameworkException {
261 Map<String, Object> request = test.initRequest();
262
263
264
265
266 if (! adapter.isRequestSupported(request)) {
267 return;
268 }
269
270
271
272
273
274
275 request = adapter.modifyRequest(request);
276
277
278 requestHandler.setRequestExpectations(request);
279
280 Map<String, Object> responseExpectations = test.initResponseExpectations();
281
282
283
284
285
286 responseExpectations = adapter.modifyResponseExpectations(request, responseExpectations);
287
288
289 requestHandler.setDesiredResponse(responseExpectations);
290
291
292
293
294
295
296 final String defaultURI = getDefaultURI();
297 final Map<String, Object> response = adapter.execute(
298 defaultURI,
299 request,
300 requestHandler,
301 Collections.unmodifiableMap(responseExpectations));
302
303
304
305
306
307 requestHandler.assertNothingThrown();
308
309 assertResponseMatchesExpectation(request.get(METHOD), response, responseExpectations);
310 }
311
312 @SuppressWarnings("unchecked")
313 private void assertResponseMatchesExpectation(final Object method, final Map<String, Object> actualResponse,
314 final Map<String, Object> expectedResponse)
315 throws TestingFrameworkException {
316 if (actualResponse == null) {
317 throw new TestingFrameworkException("response should not be null");
318 }
319
320
321
322
323 if (actualResponse.get(STATUS) != TestingFramework.ALREADY_CHECKED) {
324 assertStatusMatchesExpectation(actualResponse.get(STATUS), expectedResponse.get(STATUS));
325 }
326 if (! method.equals("HEAD")) {
327 if (actualResponse.get(BODY) != TestingFramework.ALREADY_CHECKED) {
328 assertBodyMatchesExpectation(actualResponse.get(BODY), expectedResponse.get(BODY));
329 }
330 if (actualResponse.get(CONTENT_TYPE) != TestingFramework.ALREADY_CHECKED) {
331 assertContentTypeMatchesExpectation(actualResponse.get(CONTENT_TYPE), expectedResponse.get(CONTENT_TYPE));
332 }
333 }
334 if (actualResponse.get(HEADERS) != TestingFramework.ALREADY_CHECKED) {
335 assertHeadersMatchExpectation((Map<String, String>) actualResponse.get(HEADERS),
336 (Map<String, String>) expectedResponse.get(HEADERS));
337 }
338 }
339
340 private void assertStatusMatchesExpectation(final Object actualStatus, final Object expectedStatus)
341 throws TestingFrameworkException {
342 if (actualStatus == null) {
343 throw new TestingFrameworkException("Returned status is null.");
344 }
345 if ((expectedStatus != null) && (! actualStatus.equals(expectedStatus))) {
346 throw new TestingFrameworkException("Expected status not found. expected="
347 + expectedStatus + "; actual=" + actualStatus);
348 }
349 }
350
351 private void assertBodyMatchesExpectation(final Object actualBody, final Object expectedBody)
352 throws TestingFrameworkException {
353 if (actualBody == null) {
354 throw new TestingFrameworkException("Returned body is null.");
355 }
356 if ((expectedBody != null) && (! actualBody.equals(expectedBody))) {
357 throw new TestingFrameworkException("Expected body not found. expected="
358 + expectedBody + "; actual=" + actualBody);
359 }
360 }
361
362 private void assertContentTypeMatchesExpectation(final Object actualContentType, final Object expectedContentType)
363 throws TestingFrameworkException {
364 if (expectedContentType != null) {
365 if (actualContentType == null) {
366 throw new TestingFrameworkException("Returned contentType is null.");
367 }
368 if (! actualContentType.equals(expectedContentType)) {
369 throw new TestingFrameworkException("Expected content type not found. expected="
370 + expectedContentType + "; actual=" + actualContentType);
371 }
372 }
373 }
374
375 private void assertHeadersMatchExpectation(final Map<String, String> actualHeaders,
376 final Map<String, String> expectedHeaders)
377 throws TestingFrameworkException {
378 if (expectedHeaders == null) {
379 return;
380 }
381 for (final Map.Entry<String, String> expectedHeader : expectedHeaders.entrySet()) {
382 final String expectedHeaderName = expectedHeader.getKey();
383 if (! actualHeaders.containsKey(expectedHeaderName)) {
384 throw new TestingFrameworkException("Expected header not found: name=" + expectedHeaderName);
385 }
386 if (! actualHeaders.get(expectedHeaderName).equals(expectedHeaders.get(expectedHeaderName))) {
387 throw new TestingFrameworkException("Header value not expected: name=" + expectedHeaderName
388 + "; expected=" + expectedHeaders.get(expectedHeaderName)
389 + "; actual=" + actualHeaders.get(expectedHeaderName));
390 }
391 }
392 }
393
394 private String getDefaultURI() {
395 return "http://localhost:" + port + "/";
396 }
397
398
399
400
401
402
403 public void setAdapter(final ClientTestingAdapter adapter) {
404 this.adapter = adapter;
405 }
406
407
408
409
410 public void deleteTests() {
411 tests = new ArrayList<>();
412 }
413
414
415
416
417
418
419 public void addTest() throws TestingFrameworkException {
420 addTest(null);
421 }
422
423
424
425
426
427
428
429
430 @SuppressWarnings("unchecked")
431 public void addTest(final Map<String, Object> test) throws TestingFrameworkException {
432 final Map<String, Object> testCopy = (Map<String, Object>) deepcopy(test);
433
434 tests.add(new FrameworkTest(testCopy));
435 }
436
437
438
439
440
441
442
443
444
445 public static Object deepcopy(final Object orig) throws TestingFrameworkException {
446 try {
447
448 final ByteArrayOutputStream bos = new ByteArrayOutputStream();
449 final ObjectOutputStream oos = new ObjectOutputStream(bos);
450 oos.writeObject(orig);
451 oos.flush();
452 final ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray());
453 final ObjectInputStream ois = new ObjectInputStream(bin);
454 return ois.readObject();
455 } catch (final ClassNotFoundException | IOException ex) {
456 throw new TestingFrameworkException(ex);
457 }
458 }
459 }