2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ============LICENSE_END=========================================================
21 package org.onap.policy.controlloop.actorserviceprovider.impl;
23 import static org.assertj.core.api.Assertions.assertThat;
24 import static org.assertj.core.api.Assertions.assertThatCode;
25 import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
26 import static org.assertj.core.api.Assertions.assertThatThrownBy;
27 import static org.junit.Assert.assertEquals;
28 import static org.junit.Assert.assertFalse;
29 import static org.junit.Assert.assertNotNull;
30 import static org.junit.Assert.assertSame;
31 import static org.junit.Assert.assertTrue;
32 import static org.mockito.Mockito.spy;
33 import static org.mockito.Mockito.when;
35 import ch.qos.logback.classic.Logger;
36 import java.util.Collections;
37 import java.util.List;
39 import java.util.Properties;
40 import java.util.UUID;
41 import java.util.concurrent.CancellationException;
42 import java.util.concurrent.CompletableFuture;
43 import java.util.concurrent.ExecutionException;
44 import java.util.concurrent.Future;
45 import java.util.concurrent.TimeUnit;
46 import java.util.concurrent.TimeoutException;
47 import java.util.concurrent.atomic.AtomicReference;
48 import javax.ws.rs.Consumes;
49 import javax.ws.rs.DELETE;
50 import javax.ws.rs.GET;
51 import javax.ws.rs.POST;
52 import javax.ws.rs.PUT;
53 import javax.ws.rs.Path;
54 import javax.ws.rs.Produces;
55 import javax.ws.rs.client.Entity;
56 import javax.ws.rs.client.InvocationCallback;
57 import javax.ws.rs.core.MediaType;
58 import javax.ws.rs.core.Response;
59 import javax.ws.rs.core.Response.Status;
62 import org.junit.AfterClass;
63 import org.junit.Before;
64 import org.junit.BeforeClass;
65 import org.junit.Test;
66 import org.mockito.Mock;
67 import org.mockito.MockitoAnnotations;
68 import org.onap.policy.common.endpoints.event.comm.bus.internal.BusTopicParams;
69 import org.onap.policy.common.endpoints.event.comm.bus.internal.BusTopicParams.TopicParamsBuilder;
70 import org.onap.policy.common.endpoints.http.client.HttpClient;
71 import org.onap.policy.common.endpoints.http.client.HttpClientFactoryInstance;
72 import org.onap.policy.common.endpoints.http.server.HttpServletServer;
73 import org.onap.policy.common.endpoints.http.server.HttpServletServerFactoryInstance;
74 import org.onap.policy.common.endpoints.properties.PolicyEndPointProperties;
75 import org.onap.policy.common.gson.GsonMessageBodyHandler;
76 import org.onap.policy.common.utils.coder.Coder;
77 import org.onap.policy.common.utils.coder.CoderException;
78 import org.onap.policy.common.utils.coder.StandardCoder;
79 import org.onap.policy.common.utils.network.NetworkUtil;
80 import org.onap.policy.common.utils.test.log.logback.ExtractAppender;
81 import org.onap.policy.controlloop.VirtualControlLoopEvent;
82 import org.onap.policy.controlloop.actorserviceprovider.Operation;
83 import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
84 import org.onap.policy.controlloop.actorserviceprovider.Util;
85 import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
86 import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
87 import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams;
88 import org.onap.policy.controlloop.policy.PolicyResult;
89 import org.slf4j.LoggerFactory;
91 public class HttpOperationTest {
93 private static final IllegalStateException EXPECTED_EXCEPTION = new IllegalStateException("expected exception");
94 private static final String ACTOR = "my-actor";
95 private static final String OPERATION = "my-name";
96 private static final String HTTP_CLIENT = "my-client";
97 private static final String HTTP_NO_SERVER = "my-http-no-server-client";
98 private static final String MEDIA_TYPE_APPLICATION_JSON = "application/json";
99 private static final String MY_REQUEST = "my-request";
100 private static final String BASE_URI = "oper";
101 private static final String PATH = "/my-path";
102 private static final String TEXT = "my-text";
103 private static final UUID REQ_ID = UUID.randomUUID();
106 * Used to attach an appender to the class' logger.
108 private static final Logger logger = (Logger) LoggerFactory.getLogger(HttpOperation.class);
109 private static final ExtractAppender appender = new ExtractAppender();
112 * {@code True} if the server should reject the request, {@code false} otherwise.
114 private static boolean rejectRequest;
116 // call counts of each method type in the server
117 private static int nget;
118 private static int npost;
119 private static int nput;
120 private static int ndelete;
123 private HttpClient client;
126 private Response response;
128 private VirtualControlLoopEvent event;
129 private ControlLoopEventContext context;
130 private ControlLoopOperationParams params;
131 private OperationOutcome outcome;
132 private AtomicReference<InvocationCallback<Response>> callback;
133 private Future<Response> future;
134 private HttpOperator operator;
135 private MyGetOperation<String> oper;
138 * Starts the simulator.
141 public static void setUpBeforeClass() throws Exception {
143 int port = NetworkUtil.allocPort();
146 * Start the simulator. Must use "Properties" to configure it, otherwise the
147 * server will use the wrong serialization provider.
149 Properties svrprops = getServerProperties("my-server", port);
150 HttpServletServerFactoryInstance.getServerFactory().build(svrprops).forEach(HttpServletServer::start);
152 if (!NetworkUtil.isTcpPortOpen("localhost", port, 100, 100)) {
153 HttpServletServerFactoryInstance.getServerFactory().destroy();
154 throw new IllegalStateException("server is not running");
158 * Start the clients, one to the server, and one to a non-existent server.
160 TopicParamsBuilder builder = BusTopicParams.builder().managed(true).hostname("localhost").basePath(BASE_URI)
161 .serializationProvider(GsonMessageBodyHandler.class.getName());
163 HttpClientFactoryInstance.getClientFactory().build(builder.clientName(HTTP_CLIENT).port(port).build());
165 HttpClientFactoryInstance.getClientFactory()
166 .build(builder.clientName(HTTP_NO_SERVER).port(NetworkUtil.allocPort()).build());
169 * Attach appender to the logger.
171 appender.setContext(logger.getLoggerContext());
174 logger.addAppender(appender);
178 * Destroys the Http factories and stops the appender.
181 public static void tearDownAfterClass() {
184 HttpClientFactoryInstance.getClientFactory().destroy();
185 HttpServletServerFactoryInstance.getServerFactory().destroy();
189 * Initializes fields, including {@link #oper}, and resets the static fields used by
193 public void setUp() {
194 MockitoAnnotations.initMocks(this);
196 appender.clearExtractions();
198 rejectRequest = false;
204 when(response.readEntity(String.class)).thenReturn(TEXT);
205 when(response.getStatus()).thenReturn(200);
207 event = new VirtualControlLoopEvent();
208 event.setRequestId(REQ_ID);
210 context = new ControlLoopEventContext(event);
211 params = ControlLoopOperationParams.builder().actor(ACTOR).operation(OPERATION).context(context).build();
213 outcome = params.makeOutcome();
215 callback = new AtomicReference<>();
216 future = new CompletableFuture<>();
218 operator = new HttpOperator(ACTOR, OPERATION) {
220 public Operation buildOperation(ControlLoopOperationParams params) {
225 public HttpClient getClient() {
230 initOper(operator, HTTP_CLIENT);
232 oper = new MyGetOperation<>(String.class);
236 public void testHttpOperator() {
237 assertEquals(ACTOR, oper.getActorName());
238 assertEquals(OPERATION, oper.getName());
239 assertEquals(ACTOR + "." + OPERATION, oper.getFullName());
243 public void testMakeHeaders() {
244 assertEquals(Collections.emptyMap(), oper.makeHeaders());
248 public void testMakePath() {
249 assertEquals(PATH, oper.makePath());
253 public void testMakeUrl() {
255 client = HttpClientFactoryInstance.getClientFactory().get(HTTP_CLIENT);
257 assertThat(oper.makeUrl()).endsWith("/" + BASE_URI + PATH);
261 public void testDoConfigureMapOfStringObject_testGetClient_testGetPath_testGetTimeoutMs() {
264 assertEquals(0L, oper.getTimeoutMs(null));
265 assertEquals(0L, oper.getTimeoutMs(0));
267 // should use given value
268 assertEquals(20 * 1000L, oper.getTimeoutMs(20));
270 // indicate we have a timeout value
271 operator = spy(operator);
272 when(operator.getTimeoutMs()).thenReturn(30L);
274 oper = new MyGetOperation<String>(String.class);
276 // should use default
277 assertEquals(30L, oper.getTimeoutMs(null));
278 assertEquals(30L, oper.getTimeoutMs(0));
280 // should use given value
281 assertEquals(40 * 1000L, oper.getTimeoutMs(40));
285 * Tests handleResponse() when it completes.
288 public void testHandleResponseComplete() throws Exception {
289 CompletableFuture<OperationOutcome> future2 = oper.handleResponse(outcome, PATH, cb -> {
294 assertFalse(future2.isDone());
295 assertNotNull(callback.get());
296 callback.get().completed(response);
298 assertSame(outcome, future2.get(5, TimeUnit.SECONDS));
300 assertEquals(PolicyResult.SUCCESS, outcome.getResult());
304 * Tests handleResponse() when it fails.
307 public void testHandleResponseFailed() throws Exception {
308 CompletableFuture<OperationOutcome> future2 = oper.handleResponse(outcome, PATH, cb -> {
313 assertFalse(future2.isDone());
314 assertNotNull(callback.get());
315 callback.get().failed(EXPECTED_EXCEPTION);
317 assertThatThrownBy(() -> future2.get(5, TimeUnit.SECONDS)).hasCause(EXPECTED_EXCEPTION);
319 // future and future2 may be completed in parallel so we must wait again
320 assertThatThrownBy(() -> future.get(5, TimeUnit.SECONDS)).isInstanceOf(CancellationException.class);
321 assertTrue(future.isCancelled());
325 * Tests processResponse() when it's a success and the response type is a String.
328 public void testProcessResponseSuccessString() {
329 assertSame(outcome, oper.processResponse(outcome, PATH, response));
330 assertEquals(PolicyResult.SUCCESS, outcome.getResult());
334 * Tests processResponse() when it's a failure.
337 public void testProcessResponseFailure() {
338 when(response.getStatus()).thenReturn(555);
339 assertSame(outcome, oper.processResponse(outcome, PATH, response));
340 assertEquals(PolicyResult.FAILURE, outcome.getResult());
344 * Tests processResponse() when the decoder succeeds.
347 public void testProcessResponseDecodeOk() throws CoderException {
348 when(response.readEntity(String.class)).thenReturn("10");
350 MyGetOperation<Integer> oper2 = new MyGetOperation<>(Integer.class);
352 assertSame(outcome, oper2.processResponse(outcome, PATH, response));
353 assertEquals(PolicyResult.SUCCESS, outcome.getResult());
357 * Tests processResponse() when the decoder throws an exception.
360 public void testProcessResponseDecodeExcept() throws CoderException {
361 MyGetOperation<Integer> oper2 = new MyGetOperation<>(Integer.class);
363 assertThatIllegalArgumentException().isThrownBy(() -> oper2.processResponse(outcome, PATH, response));
367 public void testPostProcessResponse() {
368 assertThatCode(() -> oper.postProcessResponse(outcome, PATH, null, null)).doesNotThrowAnyException();
372 public void testIsSuccess() {
373 when(response.getStatus()).thenReturn(200);
374 assertTrue(oper.isSuccess(response, null));
376 when(response.getStatus()).thenReturn(555);
377 assertFalse(oper.isSuccess(response, null));
384 public void testGet() throws Exception {
386 client = HttpClientFactoryInstance.getClientFactory().get(HTTP_CLIENT);
388 MyGetOperation<MyResponse> oper2 = new MyGetOperation<>(MyResponse.class);
390 OperationOutcome outcome = runOperation(oper2);
391 assertNotNull(outcome);
392 assertEquals(1, nget);
393 assertEquals(PolicyResult.SUCCESS, outcome.getResult());
400 public void testDelete() throws Exception {
402 client = HttpClientFactoryInstance.getClientFactory().get(HTTP_CLIENT);
404 MyDeleteOperation oper2 = new MyDeleteOperation();
406 OperationOutcome outcome = runOperation(oper2);
407 assertNotNull(outcome);
408 assertEquals(1, ndelete);
409 assertEquals(PolicyResult.SUCCESS, outcome.getResult());
416 public void testPost() throws Exception {
418 client = HttpClientFactoryInstance.getClientFactory().get(HTTP_CLIENT);
420 MyPostOperation oper2 = new MyPostOperation();
422 OperationOutcome outcome = runOperation(oper2);
423 assertNotNull(outcome);
424 assertEquals(1, npost);
425 assertEquals(PolicyResult.SUCCESS, outcome.getResult());
432 public void testPut() throws Exception {
434 client = HttpClientFactoryInstance.getClientFactory().get(HTTP_CLIENT);
436 MyPutOperation oper2 = new MyPutOperation();
438 OperationOutcome outcome = runOperation(oper2);
439 assertNotNull(outcome);
440 assertEquals(1, nput);
441 assertEquals(PolicyResult.SUCCESS, outcome.getResult());
445 public void testLogRestRequest() throws CoderException {
446 // log structured data
447 appender.clearExtractions();
448 oper.logRestRequest(PATH, new MyRequest());
449 List<String> output = appender.getExtracted();
450 assertEquals(1, output.size());
452 assertThat(output.get(0)).contains(PATH).contains("{\n \"input\": \"some input\"\n}");
454 // log a plain string
455 appender.clearExtractions();
456 oper.logRestRequest(PATH, MY_REQUEST);
457 output = appender.getExtracted();
458 assertEquals(1, output.size());
460 assertThat(output.get(0)).contains(PATH).contains(MY_REQUEST);
462 // log a null request
463 appender.clearExtractions();
464 oper.logRestRequest(PATH, null);
465 output = appender.getExtracted();
466 assertEquals(1, output.size());
468 // exception from coder
469 oper = new MyGetOperation<>(String.class) {
471 protected Coder makeCoder() {
472 return new StandardCoder() {
474 public String encode(Object object, boolean pretty) throws CoderException {
475 throw new CoderException(EXPECTED_EXCEPTION);
481 appender.clearExtractions();
482 oper.logRestRequest(PATH, new MyRequest());
483 output = appender.getExtracted();
484 assertEquals(2, output.size());
485 assertThat(output.get(0)).contains("cannot pretty-print request");
486 assertThat(output.get(1)).contains(PATH);
490 public void testLogRestResponse() throws CoderException {
491 // log structured data
492 appender.clearExtractions();
493 oper.logRestResponse(PATH, new MyResponse());
494 List<String> output = appender.getExtracted();
495 assertEquals(1, output.size());
497 assertThat(output.get(0)).contains(PATH).contains("{\n \"output\": \"some output\"\n}");
499 // log a plain string
500 appender.clearExtractions();
501 oper.logRestResponse(PATH, MY_REQUEST);
502 output = appender.getExtracted();
503 assertEquals(1, output.size());
505 // log a null response
506 appender.clearExtractions();
507 oper.logRestResponse(PATH, null);
508 output = appender.getExtracted();
509 assertEquals(1, output.size());
511 assertThat(output.get(0)).contains(PATH).contains("null");
513 // exception from coder
514 oper = new MyGetOperation<>(String.class) {
516 protected Coder makeCoder() {
517 return new StandardCoder() {
519 public String encode(Object object, boolean pretty) throws CoderException {
520 throw new CoderException(EXPECTED_EXCEPTION);
526 appender.clearExtractions();
527 oper.logRestResponse(PATH, new MyResponse());
528 output = appender.getExtracted();
529 assertEquals(2, output.size());
530 assertThat(output.get(0)).contains("cannot pretty-print response");
531 assertThat(output.get(1)).contains(PATH);
535 public void testMakeDecoder() {
536 assertNotNull(oper.makeCoder());
540 * Gets server properties.
542 * @param name server name
543 * @param port server port
544 * @return server properties
546 private static Properties getServerProperties(String name, int port) {
547 final Properties props = new Properties();
548 props.setProperty(PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES, name);
550 final String svcpfx = PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES + "." + name;
552 props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_REST_CLASSES_SUFFIX, Server.class.getName());
553 props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_HOST_SUFFIX, "localhost");
554 props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_PORT_SUFFIX, String.valueOf(port));
555 props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_MANAGED_SUFFIX, "true");
556 props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SWAGGER_SUFFIX, "false");
558 props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SERIALIZATION_PROVIDER,
559 GsonMessageBodyHandler.class.getName());
564 * Initializes the given operator.
566 * @param operator operator to be initialized
567 * @param clientName name of the client which it should use
569 private void initOper(HttpOperator operator, String clientName) {
572 HttpParams params = HttpParams.builder().clientName(clientName).path(PATH).build();
573 Map<String, Object> mapParams = Util.translateToMap(OPERATION, params);
574 operator.configure(mapParams);
579 * Runs the operation.
581 * @param operator operator on which to start the operation
582 * @return the outcome of the operation, or {@code null} if it does not complete in
585 private <T> OperationOutcome runOperation(HttpOperation<T> operator)
586 throws InterruptedException, ExecutionException, TimeoutException {
588 CompletableFuture<OperationOutcome> future = operator.start();
590 return future.get(5, TimeUnit.SECONDS);
595 public static class MyRequest {
596 private String input = "some input";
601 public static class MyResponse {
602 private String output = "some output";
605 private class MyGetOperation<T> extends HttpOperation<T> {
606 public MyGetOperation(Class<T> responseClass) {
607 super(HttpOperationTest.this.params, HttpOperationTest.this.operator, responseClass);
611 protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
612 Map<String, Object> headers = makeHeaders();
614 headers.put("Accept", MediaType.APPLICATION_JSON);
615 String url = makeUrl();
617 logRestRequest(url, null);
620 return handleResponse(outcome, url,
621 callback -> operator.getClient().get(callback, makePath(), headers));
626 private class MyPostOperation extends HttpOperation<MyResponse> {
627 public MyPostOperation() {
628 super(HttpOperationTest.this.params, HttpOperationTest.this.operator, MyResponse.class);
632 protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
634 MyRequest request = new MyRequest();
636 Entity<MyRequest> entity = Entity.entity(request, MediaType.APPLICATION_JSON);
638 Map<String, Object> headers = makeHeaders();
640 headers.put("Accept", MediaType.APPLICATION_JSON);
641 String url = makeUrl();
643 logRestRequest(url, request);
646 return handleResponse(outcome, url,
647 callback -> operator.getClient().post(callback, makePath(), entity, headers));
652 private class MyPutOperation extends HttpOperation<MyResponse> {
653 public MyPutOperation() {
654 super(HttpOperationTest.this.params, HttpOperationTest.this.operator, MyResponse.class);
658 protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
660 MyRequest request = new MyRequest();
662 Entity<MyRequest> entity = Entity.entity(request, MediaType.APPLICATION_JSON);
664 Map<String, Object> headers = makeHeaders();
666 headers.put("Accept", MediaType.APPLICATION_JSON);
667 String url = makeUrl();
669 logRestRequest(url, request);
672 return handleResponse(outcome, url,
673 callback -> operator.getClient().put(callback, makePath(), entity, headers));
678 private class MyDeleteOperation extends HttpOperation<String> {
679 public MyDeleteOperation() {
680 super(HttpOperationTest.this.params, HttpOperationTest.this.operator, String.class);
684 protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
685 Map<String, Object> headers = makeHeaders();
687 headers.put("Accept", MediaType.APPLICATION_JSON);
688 String url = makeUrl();
690 logRestRequest(url, null);
693 return handleResponse(outcome, url,
694 callback -> operator.getClient().delete(callback, makePath(), headers));
702 @Path("/" + BASE_URI)
703 @Produces(MEDIA_TYPE_APPLICATION_JSON)
704 @Consumes(value = {MEDIA_TYPE_APPLICATION_JSON})
705 public static class Server {
708 * Generates a response to a GET.
710 * @return resulting response
714 public Response getRequest() {
718 return Response.status(Status.BAD_REQUEST).build();
721 return Response.status(Status.OK).entity(new MyResponse()).build();
726 * Generates a response to a POST.
728 * @param request incoming request
729 * @return resulting response
733 public Response postRequest(MyRequest request) {
737 return Response.status(Status.BAD_REQUEST).build();
740 return Response.status(Status.OK).entity(new MyResponse()).build();
745 * Generates a response to a PUT.
747 * @param request incoming request
748 * @return resulting response
752 public Response putRequest(MyRequest request) {
756 return Response.status(Status.BAD_REQUEST).build();
759 return Response.status(Status.OK).entity(new MyResponse()).build();
764 * Generates a response to a DELETE.
766 * @return resulting response
770 public Response deleteRequest() {
774 return Response.status(Status.BAD_REQUEST).build();
777 return Response.status(Status.OK).entity(new MyResponse()).build();