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.ArgumentMatchers.any;
 
  33 import static org.mockito.Mockito.when;
 
  35 import java.util.Collections;
 
  37 import java.util.Properties;
 
  38 import java.util.UUID;
 
  39 import java.util.concurrent.CancellationException;
 
  40 import java.util.concurrent.CompletableFuture;
 
  41 import java.util.concurrent.ExecutionException;
 
  42 import java.util.concurrent.Executor;
 
  43 import java.util.concurrent.Future;
 
  44 import java.util.concurrent.TimeUnit;
 
  45 import java.util.concurrent.TimeoutException;
 
  46 import java.util.concurrent.atomic.AtomicReference;
 
  47 import javax.ws.rs.Consumes;
 
  48 import javax.ws.rs.DELETE;
 
  49 import javax.ws.rs.GET;
 
  50 import javax.ws.rs.POST;
 
  51 import javax.ws.rs.PUT;
 
  52 import javax.ws.rs.Path;
 
  53 import javax.ws.rs.Produces;
 
  54 import javax.ws.rs.client.Entity;
 
  55 import javax.ws.rs.client.InvocationCallback;
 
  56 import javax.ws.rs.core.MediaType;
 
  57 import javax.ws.rs.core.Response;
 
  58 import javax.ws.rs.core.Response.Status;
 
  61 import org.junit.AfterClass;
 
  62 import org.junit.Before;
 
  63 import org.junit.BeforeClass;
 
  64 import org.junit.Test;
 
  65 import org.mockito.Mock;
 
  66 import org.mockito.MockitoAnnotations;
 
  67 import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
 
  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.HttpClientFactory;
 
  72 import org.onap.policy.common.endpoints.http.client.HttpClientFactoryInstance;
 
  73 import org.onap.policy.common.endpoints.http.server.HttpServletServer;
 
  74 import org.onap.policy.common.endpoints.http.server.HttpServletServerFactoryInstance;
 
  75 import org.onap.policy.common.endpoints.properties.PolicyEndPointProperties;
 
  76 import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
 
  77 import org.onap.policy.common.gson.GsonMessageBodyHandler;
 
  78 import org.onap.policy.common.utils.coder.CoderException;
 
  79 import org.onap.policy.common.utils.network.NetworkUtil;
 
  80 import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
 
  81 import org.onap.policy.controlloop.actorserviceprovider.OperationResult;
 
  82 import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
 
  83 import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpConfig;
 
  84 import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams;
 
  86 public class HttpOperationTest {
 
  88     private static final IllegalStateException EXPECTED_EXCEPTION = new IllegalStateException("expected exception");
 
  89     private static final String ACTOR = "my-actor";
 
  90     private static final String OPERATION = "my-name";
 
  91     private static final String HTTP_CLIENT = "my-client";
 
  92     private static final String HTTP_NO_SERVER = "my-http-no-server-client";
 
  93     private static final String MEDIA_TYPE_APPLICATION_JSON = "application/json";
 
  94     private static final String BASE_URI = "oper";
 
  95     private static final String PATH = "/my-path";
 
  96     private static final String TEXT = "my-text";
 
  97     private static final UUID REQ_ID = UUID.randomUUID();
 
 100      * {@code True} if the server should reject the request, {@code false} otherwise.
 
 102     private static boolean rejectRequest;
 
 104     // call counts of each method type in the server
 
 105     private static int nget;
 
 106     private static int npost;
 
 107     private static int nput;
 
 108     private static int ndelete;
 
 111     private HttpClient client;
 
 113     private HttpClientFactory clientFactory;
 
 115     private Response response;
 
 117     private Executor executor;
 
 119     private ControlLoopOperationParams params;
 
 120     private OperationOutcome outcome;
 
 121     private AtomicReference<InvocationCallback<Response>> callback;
 
 122     private Future<Response> future;
 
 123     private HttpConfig config;
 
 124     private MyGetOperation<String> oper;
 
 127      * Starts the simulator.
 
 130     public static void setUpBeforeClass() throws Exception {
 
 132         int port = NetworkUtil.allocPort();
 
 135          * Start the simulator. Must use "Properties" to configure it, otherwise the
 
 136          * server will use the wrong serialization provider.
 
 138         Properties svrprops = getServerProperties("my-server", port);
 
 139         HttpServletServerFactoryInstance.getServerFactory().build(svrprops).forEach(HttpServletServer::start);
 
 141         if (!NetworkUtil.isTcpPortOpen("localhost", port, 100, 100)) {
 
 142             HttpServletServerFactoryInstance.getServerFactory().destroy();
 
 143             throw new IllegalStateException("server is not running");
 
 147          * Start the clients, one to the server, and one to a non-existent server.
 
 149         TopicParamsBuilder builder = BusTopicParams.builder().managed(true).hostname("localhost").basePath(BASE_URI);
 
 151         HttpClientFactoryInstance.getClientFactory().build(builder.clientName(HTTP_CLIENT).port(port).build());
 
 153         HttpClientFactoryInstance.getClientFactory()
 
 154                         .build(builder.clientName(HTTP_NO_SERVER).port(NetworkUtil.allocPort()).build());
 
 158      * Destroys the Http factories and stops the appender.
 
 161     public static void tearDownAfterClass() {
 
 162         HttpClientFactoryInstance.getClientFactory().destroy();
 
 163         HttpServletServerFactoryInstance.getServerFactory().destroy();
 
 167      * Initializes fields, including {@link #oper}, and resets the static fields used by
 
 171     public void setUp() {
 
 172         MockitoAnnotations.initMocks(this);
 
 174         rejectRequest = false;
 
 180         when(response.readEntity(String.class)).thenReturn(TEXT);
 
 181         when(response.getStatus()).thenReturn(200);
 
 183         params = ControlLoopOperationParams.builder().actor(ACTOR).operation(OPERATION).requestId(REQ_ID).build();
 
 185         outcome = params.makeOutcome();
 
 187         callback = new AtomicReference<>();
 
 188         future = new CompletableFuture<>();
 
 190         when(clientFactory.get(any())).thenReturn(client);
 
 192         initConfig(HTTP_CLIENT);
 
 194         oper = new MyGetOperation<>(String.class);
 
 198     public void testHttpOperator() {
 
 199         assertEquals(ACTOR, oper.getActorName());
 
 200         assertEquals(OPERATION, oper.getName());
 
 201         assertEquals(ACTOR + "." + OPERATION, oper.getFullName());
 
 205     public void testMakeHeaders() {
 
 206         assertEquals(Collections.emptyMap(), oper.makeHeaders());
 
 210     public void testGetPath() {
 
 211         assertEquals(PATH, oper.getPath());
 
 215     public void testMakeUrl() {
 
 217         initRealConfig(HTTP_CLIENT);
 
 219         oper = new MyGetOperation<>(String.class);
 
 221         assertThat(oper.getUrl()).endsWith("/" + BASE_URI + PATH);
 
 225     public void testDoConfigureMapOfStringObject_testGetClient_testGetPath_testGetTimeoutMs() {
 
 227         // use value from operator
 
 228         assertEquals(1000L, oper.getTimeoutMs(null));
 
 229         assertEquals(1000L, oper.getTimeoutMs(0));
 
 231         // should use given value
 
 232         assertEquals(20 * 1000L, oper.getTimeoutMs(20));
 
 236      * Tests handleResponse() when it completes.
 
 239     public void testHandleResponseComplete() throws Exception {
 
 240         CompletableFuture<OperationOutcome> future2 = oper.handleResponse(outcome, PATH, cb -> {
 
 245         assertFalse(future2.isDone());
 
 246         assertNotNull(callback.get());
 
 247         callback.get().completed(response);
 
 249         assertSame(outcome, future2.get(5, TimeUnit.SECONDS));
 
 250         assertSame(TEXT, outcome.getResponse());
 
 252         assertEquals(OperationResult.SUCCESS, outcome.getResult());
 
 256      * Tests handleResponse() when it fails.
 
 259     public void testHandleResponseFailed() throws Exception {
 
 260         CompletableFuture<OperationOutcome> future2 = oper.handleResponse(outcome, PATH, cb -> {
 
 265         assertFalse(future2.isDone());
 
 266         assertNotNull(callback.get());
 
 267         callback.get().failed(EXPECTED_EXCEPTION);
 
 269         assertThatThrownBy(() -> future2.get(5, TimeUnit.SECONDS)).hasCause(EXPECTED_EXCEPTION);
 
 271         // future and future2 may be completed in parallel so we must wait again
 
 272         assertThatThrownBy(() -> future.get(5, TimeUnit.SECONDS)).isInstanceOf(CancellationException.class);
 
 273         assertTrue(future.isCancelled());
 
 277      * Tests processResponse() when it's a success and the response type is a String.
 
 280     public void testProcessResponseSuccessString() throws Exception {
 
 281         CompletableFuture<OperationOutcome> result = oper.processResponse(outcome, PATH, response);
 
 282         assertTrue(result.isDone());
 
 283         assertSame(outcome, result.get());
 
 284         assertEquals(OperationResult.SUCCESS, outcome.getResult());
 
 285         assertSame(TEXT, outcome.getResponse());
 
 289      * Tests processResponse() when it's a failure.
 
 292     public void testProcessResponseFailure() throws Exception {
 
 293         when(response.getStatus()).thenReturn(555);
 
 294         CompletableFuture<OperationOutcome> result = oper.processResponse(outcome, PATH, response);
 
 295         assertTrue(result.isDone());
 
 296         assertSame(outcome, result.get());
 
 297         assertEquals(OperationResult.FAILURE, outcome.getResult());
 
 298         assertSame(TEXT, outcome.getResponse());
 
 302      * Tests processResponse() when the decoder succeeds.
 
 305     public void testProcessResponseDecodeOk() throws Exception {
 
 306         when(response.readEntity(String.class)).thenReturn("10");
 
 308         MyGetOperation<Integer> oper2 = new MyGetOperation<>(Integer.class);
 
 310         CompletableFuture<OperationOutcome> result = oper2.processResponse(outcome, PATH, response);
 
 311         assertTrue(result.isDone());
 
 312         assertSame(outcome, result.get());
 
 313         assertEquals(OperationResult.SUCCESS, outcome.getResult());
 
 314         assertEquals(Integer.valueOf(10), outcome.getResponse());
 
 318      * Tests processResponse() when the decoder throws an exception.
 
 321     public void testProcessResponseDecodeExcept() throws CoderException {
 
 322         MyGetOperation<Integer> oper2 = new MyGetOperation<>(Integer.class);
 
 324         assertThatIllegalArgumentException().isThrownBy(() -> oper2.processResponse(outcome, PATH, response));
 
 328     public void testPostProcessResponse() {
 
 329         assertThatCode(() -> oper.postProcessResponse(outcome, PATH, null, null)).doesNotThrowAnyException();
 
 333     public void testIsSuccess() {
 
 334         when(response.getStatus()).thenReturn(200);
 
 335         assertTrue(oper.isSuccess(response, null));
 
 337         when(response.getStatus()).thenReturn(555);
 
 338         assertFalse(oper.isSuccess(response, null));
 
 345     public void testGet() throws Exception {
 
 347         initRealConfig(HTTP_CLIENT);
 
 349         MyGetOperation<MyResponse> oper2 = new MyGetOperation<>(MyResponse.class);
 
 351         OperationOutcome outcome = runOperation(oper2);
 
 352         assertNotNull(outcome);
 
 353         assertEquals(1, nget);
 
 354         assertEquals(OperationResult.SUCCESS, outcome.getResult());
 
 355         assertTrue(outcome.getResponse() instanceof MyResponse);
 
 362     public void testDelete() throws Exception {
 
 364         initRealConfig(HTTP_CLIENT);
 
 366         MyDeleteOperation oper2 = new MyDeleteOperation();
 
 368         OperationOutcome outcome = runOperation(oper2);
 
 369         assertNotNull(outcome);
 
 370         assertEquals(1, ndelete);
 
 371         assertEquals(OperationResult.SUCCESS, outcome.getResult());
 
 372         assertTrue(outcome.getResponse() instanceof String);
 
 379     public void testPost() throws Exception {
 
 381         initRealConfig(HTTP_CLIENT);
 
 382         MyPostOperation oper2 = new MyPostOperation();
 
 384         OperationOutcome outcome = runOperation(oper2);
 
 385         assertNotNull(outcome);
 
 386         assertEquals(1, npost);
 
 387         assertEquals(OperationResult.SUCCESS, outcome.getResult());
 
 388         assertTrue(outcome.getResponse() instanceof MyResponse);
 
 395     public void testPut() throws Exception {
 
 397         initRealConfig(HTTP_CLIENT);
 
 399         MyPutOperation oper2 = new MyPutOperation();
 
 401         OperationOutcome outcome = runOperation(oper2);
 
 402         assertNotNull(outcome);
 
 403         assertEquals(1, nput);
 
 404         assertEquals(OperationResult.SUCCESS, outcome.getResult());
 
 405         assertTrue(outcome.getResponse() instanceof MyResponse);
 
 409     public void testMakeDecoder() {
 
 410         assertNotNull(oper.getCoder());
 
 414      * Gets server properties.
 
 416      * @param name server name
 
 417      * @param port server port
 
 418      * @return server properties
 
 420     private static Properties getServerProperties(String name, int port) {
 
 421         final Properties props = new Properties();
 
 422         props.setProperty(PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES, name);
 
 424         final String svcpfx = PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES + "." + name;
 
 426         props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_REST_CLASSES_SUFFIX, Server.class.getName());
 
 427         props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_HOST_SUFFIX, "localhost");
 
 428         props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_PORT_SUFFIX, String.valueOf(port));
 
 429         props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_MANAGED_SUFFIX, "true");
 
 430         props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SWAGGER_SUFFIX, "false");
 
 432         props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SERIALIZATION_PROVIDER,
 
 433                         GsonMessageBodyHandler.class.getName());
 
 438      * Initializes the configuration.
 
 440      * @param operator operator to be initialized
 
 441      * @param clientName name of the client which it should use
 
 443     private void initConfig(String clientName) {
 
 444         initConfig(clientName, clientFactory);
 
 448      * Initializes the configuration with a real client.
 
 450      * @param operator operator to be initialized
 
 451      * @param clientName name of the client which it should use
 
 453     private void initConfig(String clientName, HttpClientFactory factory) {
 
 454         HttpParams params = HttpParams.builder().clientName(clientName).path(PATH).timeoutSec(1).build();
 
 455         config = new HttpConfig(executor, params, factory);
 
 459      * Initializes the configuration with a real client.
 
 461      * @param operator operator to be initialized
 
 462      * @param clientName name of the client which it should use
 
 464     private void initRealConfig(String clientName) {
 
 465         initConfig(clientName, HttpClientFactoryInstance.getClientFactory());
 
 469      * Runs the operation.
 
 471      * @param operator operator on which to start the operation
 
 472      * @return the outcome of the operation, or {@code null} if it does not complete in
 
 475     private <T> OperationOutcome runOperation(HttpOperation<T> operator)
 
 476                     throws InterruptedException, ExecutionException, TimeoutException {
 
 478         CompletableFuture<OperationOutcome> future = operator.start();
 
 480         return future.get(5, TimeUnit.SECONDS);
 
 485     public static class MyRequest {
 
 486         private String input = "some input";
 
 491     public static class MyResponse {
 
 492         private String output = "some output";
 
 495     private class MyGetOperation<T> extends HttpOperation<T> {
 
 496         public MyGetOperation(Class<T> responseClass) {
 
 497             super(HttpOperationTest.this.params, HttpOperationTest.this.config, responseClass, Collections.emptyList());
 
 501         protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
 
 502             Map<String, Object> headers = makeHeaders();
 
 504             headers.put("Accept", MediaType.APPLICATION_JSON);
 
 505             String url = getUrl();
 
 507             logMessage(EventType.OUT, CommInfrastructure.REST, url, null);
 
 510             return handleResponse(outcome, url,
 
 511                 callback -> getClient().get(callback, getPath(), headers));
 
 516     private class MyPostOperation extends HttpOperation<MyResponse> {
 
 517         public MyPostOperation() {
 
 518             super(HttpOperationTest.this.params, HttpOperationTest.this.config, MyResponse.class,
 
 519                             Collections.emptyList());
 
 523         protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
 
 525             MyRequest request = new MyRequest();
 
 527             Map<String, Object> headers = makeHeaders();
 
 529             headers.put("Accept", MediaType.APPLICATION_JSON);
 
 530             String url = getUrl();
 
 532             String strRequest = prettyPrint(request);
 
 533             logMessage(EventType.OUT, CommInfrastructure.REST, url, strRequest);
 
 535             Entity<String> entity = Entity.entity(strRequest, MediaType.APPLICATION_JSON);
 
 538             return handleResponse(outcome, url,
 
 539                 callback -> getClient().post(callback, getPath(), entity, headers));
 
 544     private class MyPutOperation extends HttpOperation<MyResponse> {
 
 545         public MyPutOperation() {
 
 546             super(HttpOperationTest.this.params, HttpOperationTest.this.config, MyResponse.class,
 
 547                             Collections.emptyList());
 
 551         protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
 
 553             MyRequest request = new MyRequest();
 
 555             Map<String, Object> headers = makeHeaders();
 
 557             headers.put("Accept", MediaType.APPLICATION_JSON);
 
 558             String url = getUrl();
 
 560             String strRequest = prettyPrint(request);
 
 561             logMessage(EventType.OUT, CommInfrastructure.REST, url, strRequest);
 
 563             Entity<String> entity = Entity.entity(strRequest, MediaType.APPLICATION_JSON);
 
 566             return handleResponse(outcome, url,
 
 567                 callback -> getClient().put(callback, getPath(), entity, headers));
 
 572     private class MyDeleteOperation extends HttpOperation<String> {
 
 573         public MyDeleteOperation() {
 
 574             super(HttpOperationTest.this.params, HttpOperationTest.this.config, String.class, Collections.emptyList());
 
 578         protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
 
 579             Map<String, Object> headers = makeHeaders();
 
 581             headers.put("Accept", MediaType.APPLICATION_JSON);
 
 582             String url = getUrl();
 
 584             logMessage(EventType.OUT, CommInfrastructure.REST, url, null);
 
 587             return handleResponse(outcome, url,
 
 588                 callback -> getClient().delete(callback, getPath(), headers));
 
 596     @Path("/" + BASE_URI)
 
 597     @Produces(MEDIA_TYPE_APPLICATION_JSON)
 
 598     @Consumes(value = {MEDIA_TYPE_APPLICATION_JSON})
 
 599     public static class Server {
 
 602          * Generates a response to a GET.
 
 604          * @return resulting response
 
 608         public Response getRequest() {
 
 612                 return Response.status(Status.BAD_REQUEST).build();
 
 615                 return Response.status(Status.OK).entity(new MyResponse()).build();
 
 620          * Generates a response to a POST.
 
 622          * @param request incoming request
 
 623          * @return resulting response
 
 627         public Response postRequest(MyRequest request) {
 
 631                 return Response.status(Status.BAD_REQUEST).build();
 
 634                 return Response.status(Status.OK).entity(new MyResponse()).build();
 
 639          * Generates a response to a PUT.
 
 641          * @param request incoming request
 
 642          * @return resulting response
 
 646         public Response putRequest(MyRequest request) {
 
 650                 return Response.status(Status.BAD_REQUEST).build();
 
 653                 return Response.status(Status.OK).entity(new MyResponse()).build();
 
 658          * Generates a response to a DELETE.
 
 660          * @return resulting response
 
 664         public Response deleteRequest() {
 
 668                 return Response.status(Status.BAD_REQUEST).build();
 
 671                 return Response.status(Status.OK).entity(new MyResponse()).build();