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