1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.forgerock.http.bindings;
17
18 import static java.lang.String.format;
19 import static org.assertj.core.api.Assertions.assertThat;
20 import static org.assertj.core.api.Assertions.fail;
21 import static org.forgerock.http.Applications.describedHttpApplication;
22 import static org.forgerock.http.Applications.simpleHttpApplication;
23 import static org.forgerock.http.filter.TransactionIdInboundFilter.SYSPROP_TRUST_TRANSACTION_HEADER;
24 import static org.forgerock.http.handler.Handlers.chainOf;
25 import static org.forgerock.http.protocol.Response.newResponsePromise;
26 import static org.forgerock.json.JsonValue.json;
27 import static org.forgerock.json.test.assertj.AssertJJsonValueAssert.assertThat;
28 import static org.mockito.Mockito.mock;
29 import static org.mockito.Mockito.verify;
30 import static org.mockito.Mockito.verifyNoMoreInteractions;
31 import static org.mockito.Mockito.when;
32
33 import io.swagger.v3.oas.models.OpenAPI;
34 import io.swagger.v3.oas.models.Operation;
35 import io.swagger.v3.oas.models.PathItem;
36 import io.swagger.v3.oas.models.Paths;
37 import io.swagger.v3.oas.models.info.Info;
38 import java.util.List;
39 import org.assertj.core.api.SoftAssertionError;
40 import org.assertj.core.api.SoftAssertions;
41 import org.forgerock.http.ApiProducer;
42 import org.forgerock.http.Client;
43 import org.forgerock.http.DescribedHttpApplication;
44 import org.forgerock.http.Handler;
45 import org.forgerock.http.HttpApplication;
46 import org.forgerock.http.HttpApplicationException;
47 import org.forgerock.http.handler.DescribableHandler;
48 import org.forgerock.http.handler.HttpClientHandler;
49 import org.forgerock.http.header.CookieHeader;
50 import org.forgerock.http.header.SetCookieHeader;
51 import org.forgerock.http.protocol.Cookie;
52 import org.forgerock.http.protocol.Request;
53 import org.forgerock.http.protocol.Response;
54 import org.forgerock.http.protocol.Status;
55 import org.forgerock.http.routing.UriRouterContext;
56 import org.forgerock.http.session.Session;
57 import org.forgerock.http.session.SessionContext;
58 import org.forgerock.http.swagger.OpenApiRequestFilter;
59 import org.forgerock.http.swagger.SwaggerApiProducer;
60 import org.forgerock.services.TransactionId;
61 import org.forgerock.services.context.ClientContext;
62 import org.forgerock.services.context.Context;
63 import org.forgerock.services.context.TransactionIdContext;
64 import org.forgerock.util.promise.NeverThrowsException;
65 import org.forgerock.util.promise.Promise;
66 import org.testng.annotations.AfterMethod;
67 import org.testng.annotations.BeforeMethod;
68 import org.testng.annotations.Test;
69
70
71
72
73 public abstract class BindingTest {
74
75 private int port;
76
77
78
79
80 protected abstract void createServer();
81
82
83
84
85
86 protected abstract void stopServer() throws Exception;
87
88
89
90
91
92
93 protected abstract int startServer() throws Exception;
94
95
96
97
98
99
100 protected abstract void addApplication(HttpApplication application) throws Exception;
101
102
103
104
105
106 @BeforeMethod
107 public final void setUp() throws Exception {
108 createServer();
109 }
110
111
112
113
114
115 @AfterMethod
116 public final void tearDown() throws Exception {
117 stopServer();
118 port = 0;
119 }
120
121
122
123
124
125 @Test
126 public void testDescribedHttpApplicationLifecycle() throws Exception {
127 final DescribedHttpApplication application = mock(DescribedHttpApplication.class);
128 when(application.start()).thenReturn(mock(DescribableHandler.class));
129 addApplication(application);
130 port = startServer();
131 verify(application).getBufferFactory();
132 verify(application).start();
133 verify(application).getApiProducer();
134 verifyNoMoreInteractions(application);
135
136 stopServer();
137 verify(application).stop();
138 verifyNoMoreInteractions(application);
139 }
140
141
142
143
144
145 @Test
146 public void testHttpApplicationLifecycle() throws Exception {
147 final HttpApplication application = mock(HttpApplication.class);
148 addApplication(application);
149 port = startServer();
150 verify(application).getBufferFactory();
151 verify(application).start();
152 verifyNoMoreInteractions(application);
153
154 stopServer();
155 verify(application).stop();
156 verifyNoMoreInteractions(application);
157 }
158
159
160
161
162
163 @Test
164 public void testAnswerWith500IfHttpApplicationFailedToStart() throws Exception {
165 final HttpApplication application = mock(HttpApplication.class);
166 addApplication(application);
167
168 when(application.start()).thenThrow(new HttpApplicationException("Unable to start the HttpApplication"));
169 port = startServer();
170
171 try (final HttpClientHandler handler = new HttpClientHandler()) {
172 final Client client = new Client(handler);
173 final Request request = new Request()
174 .setMethod("GET")
175 .setUri(format("http://localhost:%d/test", port));
176 final Response response = client.send(request).get();
177 assertThat(response.getStatus()).isEqualTo(Status.INTERNAL_SERVER_ERROR);
178 }
179 }
180
181
182
183
184
185 @Test
186 public void testRequest() throws Exception {
187 HttpApplication application = simpleHttpApplication(new TestHandler(), null);
188 addApplication(application);
189 port = startServer();
190
191 try (final HttpClientHandler handler = new HttpClientHandler()) {
192 final Client client = new Client(handler);
193 final Request request = new Request()
194 .setMethod("POST")
195 .setUri(format("http://localhost:%d/test", port));
196 request.getHeaders().add("X-WhateverHeader", "Whatever Value");
197 request.getEntity().setString("Hello");
198
199 final Response response = client.send(request).get();
200 assertThat(response.getEntity().toString()).isEqualTo("HELLO");
201 assertThat(response.getHeaders().get("X-WhateverHeader").getFirstValue()).isEqualTo("Whatever Value");
202 }
203 }
204
205
206
207
208
209 @Test
210 public void testRequestApi() throws Exception {
211 DescribableHandler testHandler = chainOf(new TestHandler(), new OpenApiRequestFilter());
212 HttpApplication application = describedHttpApplication(testHandler, null,
213 new SwaggerApiProducer(new Info(), "", ""));
214 addApplication(application);
215
216 port = startServer();
217
218 try (final HttpClientHandler handler = new HttpClientHandler()) {
219 final Client client = new Client(handler);
220 final Request request = new Request()
221 .setMethod("GET")
222 .setUri(format("http://localhost:%d/test?_api", port));
223 request.getHeaders().add("X-WhateverHeader", "Whatever Value");
224
225 final Response response = client.send(request).get();
226 assertThat(json(response.getEntity().getJson())).isObject()
227 .hasObject("paths")
228 .hasObject("test")
229 .hasObject("post");
230 }
231 }
232
233
234
235
236
237 @Test
238 public void testSession() throws Exception {
239 HttpApplication application = simpleHttpApplication(new TestSessionHandler(), null);
240 addApplication(application);
241 port = startServer();
242
243 try (final HttpClientHandler handler = new HttpClientHandler()) {
244 final Client client = new Client(handler);
245 final Request populate = new Request()
246 .setMethod("POST")
247 .setUri(format("http://localhost:%d/populate", port));
248
249 Response response = client.send(populate).get();
250 assertThat(response.getStatus()).isEqualTo(Status.OK);
251 final List<Cookie> sessionCookie = response.getHeaders().get(SetCookieHeader.class).getCookies();
252
253 final Request check = new Request()
254 .setMethod("POST")
255 .setUri(format("http://localhost:%d/check", port));
256 check.getHeaders().put(new CookieHeader(sessionCookie));
257
258 response = client.send(check).get();
259 assertThat(response.getEntity().toString()).isEqualTo("OK");
260 }
261 }
262
263
264
265
266
267 @Test
268 public void testTransactionContext() throws Exception {
269 Handler handler = new Handler() {
270 @Override
271 public Promise<Response, NeverThrowsException> handle(Context context, Request request) {
272 if (!context.containsContext(TransactionIdContext.class)) {
273 return newResponsePromise(new Response(Status.EXPECTATION_FAILED));
274 }
275 Response response = new Response(Status.OK);
276 TransactionId transactionId = context.asContext(TransactionIdContext.class).getTransactionId();
277 response.setEntity(transactionId.getValue());
278 return newResponsePromise(response);
279 }
280 };
281 HttpApplication application = simpleHttpApplication(handler, null);
282 addApplication(application);
283
284 String previousPropertyValue = System.setProperty(SYSPROP_TRUST_TRANSACTION_HEADER, "true");
285 port = startServer();
286
287 try (final HttpClientHandler httpClientHandler = new HttpClientHandler()) {
288 final Client client = new Client(httpClientHandler);
289 final Request request = new Request()
290 .setMethod("GET")
291 .setUri(format("http://localhost:%d/", port));
292 request.getHeaders().add("X-ForgeRock-TransactionId", "test-transaction-id");
293
294 Response response = client.send(request).get();
295 assertThat(response.getStatus()).isEqualTo(Status.OK);
296 assertThat(response.getEntity().toString()).isEqualTo("test-transaction-id");
297 } finally {
298 if (previousPropertyValue == null) {
299 System.clearProperty(SYSPROP_TRUST_TRANSACTION_HEADER);
300 } else {
301 System.setProperty(SYSPROP_TRUST_TRANSACTION_HEADER, previousPropertyValue);
302 }
303 }
304 }
305
306 private final class TestHandler implements DescribableHandler {
307
308 @Override
309 public Promise<Response, NeverThrowsException> handle(Context context, Request request) {
310 final SoftAssertions softly = new SoftAssertions();
311 try {
312 softly.assertThat(request.getMethod()).isEqualTo("POST");
313 softly.assertThat(request.getUri().getPath()).isEqualTo("/test");
314 softly.assertThat(request.getEntity().toString()).isEqualTo("Hello");
315 softly.assertThat(request.getHeaders().get("X-WhateverHeader").getFirstValue())
316 .isEqualTo("Whatever Value");
317 softly.assertThat(context.asContext(UriRouterContext.class)).isNotNull();
318 softly.assertThat(context.asContext(UriRouterContext.class).getMatchedUri()).isEmpty();
319 softly.assertThat(context.asContext(UriRouterContext.class).getOriginalUri().toString())
320 .isEqualTo(format("http://localhost:%d/test", port));
321 softly.assertThat(context.asContext(SessionContext.class)).isNotNull();
322 softly.assertThat(context.asContext(SessionContext.class).getSession()).isNotNull();
323 softly.assertThat(context.asContext(ClientContext.class)).isNotNull();
324 softly.assertThat(context.asContext(ClientContext.class).getLocalPort())
325 .isEqualTo(port);
326 softly.assertAll();
327
328 final Response response = new Response(Status.OK);
329 response.getHeaders().addAll(request.getHeaders().asMapOfHeaders());
330 response.setEntity(request.getEntity().toString().toUpperCase());
331 return newResponsePromise(response);
332 } catch (SoftAssertionError e) {
333 return newResponsePromise(new Response(Status.INTERNAL_SERVER_ERROR)
334 .setEntity(e.getMessage()).setCause(new Exception(e)));
335 }
336 }
337
338 @Override
339 public OpenAPI api(ApiProducer<OpenAPI> producer) {
340 return null;
341 }
342
343 @Override
344 public OpenAPI handleApiRequest(Context context, Request request) {
345 Paths paths = new Paths();
346 paths.addPathItem("test", new PathItem().post(new Operation()));
347 return new OpenAPI().paths(paths);
348 }
349
350 @Override
351 public void addDescriptorListener(Listener listener) {
352
353 }
354
355 @Override
356 public void removeDescriptorListener(Listener listener) {
357
358 }
359 }
360
361 private final class TestSessionHandler implements Handler {
362 @Override
363 public Promise<Response, NeverThrowsException> handle(Context context, Request request) {
364 final Session session = context.asContext(SessionContext.class).getSession();
365 try {
366 if (request.getUri().toASCIIString().endsWith("/populate")) {
367 assertThat(session.isEmpty()).isTrue();
368 assertThat(session.size()).isEqualTo(0);
369 assertThat(session.containsKey("sessionKey")).isFalse();
370 assertThat(session.containsValue("sessionValue")).isFalse();
371 assertThat(session.put("sessionKey", "sessionValue")).isNull();
372 } else if (request.getUri().toASCIIString().endsWith("/check")) {
373 assertThat(session.get("sessionKey")).isEqualTo("sessionValue");
374 assertThat(session.isEmpty()).isFalse();
375 assertThat(session.size()).isEqualTo(1);
376 assertThat(session.containsKey("sessionKey")).isTrue();
377 assertThat(session.containsValue("sessionValue")).isTrue();
378 } else {
379 fail("Unsupported URI: " + request.getUri().toString());
380 }
381
382 final Response response = new Response(Status.OK);
383 response.setEntity("OK");
384 return newResponsePromise(response);
385 } catch (AssertionError e) {
386 return newResponsePromise(new Response(Status.INTERNAL_SERVER_ERROR)
387 .setEntity(e.getMessage()).setCause(new Exception(e)));
388 }
389 }
390 }
391 }