diff options
Diffstat (limited to 'models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperationTest.java')
-rw-r--r-- | models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperationTest.java | 781 |
1 files changed, 781 insertions, 0 deletions
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperationTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperationTest.java new file mode 100644 index 000000000..19f781d61 --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperationTest.java @@ -0,0 +1,781 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actorserviceprovider.impl; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import ch.qos.logback.classic.Logger; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.UUID; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReference; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.InvocationCallback; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; +import lombok.Getter; +import lombok.Setter; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.onap.policy.common.endpoints.event.comm.bus.internal.BusTopicParams; +import org.onap.policy.common.endpoints.event.comm.bus.internal.BusTopicParams.TopicParamsBuilder; +import org.onap.policy.common.endpoints.http.client.HttpClient; +import org.onap.policy.common.endpoints.http.client.HttpClientFactoryInstance; +import org.onap.policy.common.endpoints.http.server.HttpServletServer; +import org.onap.policy.common.endpoints.http.server.HttpServletServerFactoryInstance; +import org.onap.policy.common.endpoints.properties.PolicyEndPointProperties; +import org.onap.policy.common.gson.GsonMessageBodyHandler; +import org.onap.policy.common.utils.coder.Coder; +import org.onap.policy.common.utils.coder.CoderException; +import org.onap.policy.common.utils.coder.StandardCoder; +import org.onap.policy.common.utils.network.NetworkUtil; +import org.onap.policy.common.utils.test.log.logback.ExtractAppender; +import org.onap.policy.controlloop.VirtualControlLoopEvent; +import org.onap.policy.controlloop.actorserviceprovider.Operation; +import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome; +import org.onap.policy.controlloop.actorserviceprovider.Util; +import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext; +import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams; +import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams; +import org.onap.policy.controlloop.policy.PolicyResult; +import org.slf4j.LoggerFactory; + +public class HttpOperationTest { + + private static final IllegalStateException EXPECTED_EXCEPTION = new IllegalStateException("expected exception"); + private static final String ACTOR = "my-actor"; + private static final String OPERATION = "my-name"; + private static final String HTTP_CLIENT = "my-client"; + private static final String HTTP_NO_SERVER = "my-http-no-server-client"; + private static final String MEDIA_TYPE_APPLICATION_JSON = "application/json"; + private static final String MY_REQUEST = "my-request"; + private static final String BASE_URI = "oper"; + private static final String PATH = "/my-path"; + private static final String TEXT = "my-text"; + private static final UUID REQ_ID = UUID.randomUUID(); + + /** + * Used to attach an appender to the class' logger. + */ + private static final Logger logger = (Logger) LoggerFactory.getLogger(HttpOperation.class); + private static final ExtractAppender appender = new ExtractAppender(); + + /** + * {@code True} if the server should reject the request, {@code false} otherwise. + */ + private static boolean rejectRequest; + + // call counts of each method type in the server + private static int nget; + private static int npost; + private static int nput; + private static int ndelete; + + @Mock + private HttpClient client; + + @Mock + private Response response; + + private VirtualControlLoopEvent event; + private ControlLoopEventContext context; + private ControlLoopOperationParams params; + private OperationOutcome outcome; + private AtomicReference<InvocationCallback<Response>> callback; + private Future<Response> future; + private HttpOperator operator; + private MyGetOperation<String> oper; + + /** + * Starts the simulator. + */ + @BeforeClass + public static void setUpBeforeClass() throws Exception { + // allocate a port + int port = NetworkUtil.allocPort(); + + /* + * Start the simulator. Must use "Properties" to configure it, otherwise the + * server will use the wrong serialization provider. + */ + Properties svrprops = getServerProperties("my-server", port); + HttpServletServerFactoryInstance.getServerFactory().build(svrprops).forEach(HttpServletServer::start); + + if (!NetworkUtil.isTcpPortOpen("localhost", port, 100, 100)) { + HttpServletServerFactoryInstance.getServerFactory().destroy(); + throw new IllegalStateException("server is not running"); + } + + /* + * Start the clients, one to the server, and one to a non-existent server. + */ + TopicParamsBuilder builder = BusTopicParams.builder().managed(true).hostname("localhost").basePath(BASE_URI) + .serializationProvider(GsonMessageBodyHandler.class.getName()); + + HttpClientFactoryInstance.getClientFactory().build(builder.clientName(HTTP_CLIENT).port(port).build()); + + HttpClientFactoryInstance.getClientFactory() + .build(builder.clientName(HTTP_NO_SERVER).port(NetworkUtil.allocPort()).build()); + + /** + * Attach appender to the logger. + */ + appender.setContext(logger.getLoggerContext()); + appender.start(); + + logger.addAppender(appender); + } + + /** + * Destroys the Http factories and stops the appender. + */ + @AfterClass + public static void tearDownAfterClass() { + appender.stop(); + + HttpClientFactoryInstance.getClientFactory().destroy(); + HttpServletServerFactoryInstance.getServerFactory().destroy(); + } + + /** + * Initializes fields, including {@link #oper}, and resets the static fields used by + * the REST server. + */ + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + appender.clearExtractions(); + + rejectRequest = false; + nget = 0; + npost = 0; + nput = 0; + ndelete = 0; + + when(response.readEntity(String.class)).thenReturn(TEXT); + when(response.getStatus()).thenReturn(200); + + event = new VirtualControlLoopEvent(); + event.setRequestId(REQ_ID); + + context = new ControlLoopEventContext(event); + params = ControlLoopOperationParams.builder().actor(ACTOR).operation(OPERATION).context(context).build(); + + outcome = params.makeOutcome(); + + callback = new AtomicReference<>(); + future = new CompletableFuture<>(); + + operator = new HttpOperator(ACTOR, OPERATION) { + @Override + public Operation buildOperation(ControlLoopOperationParams params) { + return null; + } + + @Override + public HttpClient getClient() { + return client; + } + }; + + initOper(operator, HTTP_CLIENT); + + oper = new MyGetOperation<>(String.class); + } + + @Test + public void testHttpOperator() { + assertEquals(ACTOR, oper.getActorName()); + assertEquals(OPERATION, oper.getName()); + assertEquals(ACTOR + "." + OPERATION, oper.getFullName()); + } + + @Test + public void testMakeHeaders() { + assertEquals(Collections.emptyMap(), oper.makeHeaders()); + } + + @Test + public void testMakePath() { + assertEquals(PATH, oper.makePath()); + } + + @Test + public void testMakeUrl() { + // use a real client + client = HttpClientFactoryInstance.getClientFactory().get(HTTP_CLIENT); + + assertThat(oper.makeUrl()).endsWith("/" + BASE_URI + PATH); + } + + @Test + public void testDoConfigureMapOfStringObject_testGetClient_testGetPath_testGetTimeoutMs() { + + // no default yet + assertEquals(0L, oper.getTimeoutMs(null)); + assertEquals(0L, oper.getTimeoutMs(0)); + + // should use given value + assertEquals(20 * 1000L, oper.getTimeoutMs(20)); + + // indicate we have a timeout value + operator = spy(operator); + when(operator.getTimeoutMs()).thenReturn(30L); + + oper = new MyGetOperation<String>(String.class); + + // should use default + assertEquals(30L, oper.getTimeoutMs(null)); + assertEquals(30L, oper.getTimeoutMs(0)); + + // should use given value + assertEquals(40 * 1000L, oper.getTimeoutMs(40)); + } + + /** + * Tests handleResponse() when it completes. + */ + @Test + public void testHandleResponseComplete() throws Exception { + CompletableFuture<OperationOutcome> future2 = oper.handleResponse(outcome, PATH, cb -> { + callback.set(cb); + return future; + }); + + assertFalse(future2.isDone()); + assertNotNull(callback.get()); + callback.get().completed(response); + + assertSame(outcome, future2.get(5, TimeUnit.SECONDS)); + + assertEquals(PolicyResult.SUCCESS, outcome.getResult()); + } + + /** + * Tests handleResponse() when it fails. + */ + @Test + public void testHandleResponseFailed() throws Exception { + CompletableFuture<OperationOutcome> future2 = oper.handleResponse(outcome, PATH, cb -> { + callback.set(cb); + return future; + }); + + assertFalse(future2.isDone()); + assertNotNull(callback.get()); + callback.get().failed(EXPECTED_EXCEPTION); + + assertThatThrownBy(() -> future2.get(5, TimeUnit.SECONDS)).hasCause(EXPECTED_EXCEPTION); + + // future and future2 may be completed in parallel so we must wait again + assertThatThrownBy(() -> future.get(5, TimeUnit.SECONDS)).isInstanceOf(CancellationException.class); + assertTrue(future.isCancelled()); + } + + /** + * Tests processResponse() when it's a success and the response type is a String. + */ + @Test + public void testProcessResponseSuccessString() { + assertSame(outcome, oper.processResponse(outcome, PATH, response)); + assertEquals(PolicyResult.SUCCESS, outcome.getResult()); + } + + /** + * Tests processResponse() when it's a failure. + */ + @Test + public void testProcessResponseFailure() { + when(response.getStatus()).thenReturn(555); + assertSame(outcome, oper.processResponse(outcome, PATH, response)); + assertEquals(PolicyResult.FAILURE, outcome.getResult()); + } + + /** + * Tests processResponse() when the decoder succeeds. + */ + @Test + public void testProcessResponseDecodeOk() throws CoderException { + when(response.readEntity(String.class)).thenReturn("10"); + + MyGetOperation<Integer> oper2 = new MyGetOperation<>(Integer.class); + + assertSame(outcome, oper2.processResponse(outcome, PATH, response)); + assertEquals(PolicyResult.SUCCESS, outcome.getResult()); + } + + /** + * Tests processResponse() when the decoder throws an exception. + */ + @Test + public void testProcessResponseDecodeExcept() throws CoderException { + MyGetOperation<Integer> oper2 = new MyGetOperation<>(Integer.class); + + assertSame(outcome, oper2.processResponse(outcome, PATH, response)); + assertEquals(PolicyResult.FAILURE_EXCEPTION, outcome.getResult()); + } + + @Test + public void testPostProcessResponse() { + assertThatCode(() -> oper.postProcessResponse(outcome, PATH, null, null)).doesNotThrowAnyException(); + } + + @Test + public void testIsSuccess() { + when(response.getStatus()).thenReturn(200); + assertTrue(oper.isSuccess(response, null)); + + when(response.getStatus()).thenReturn(555); + assertFalse(oper.isSuccess(response, null)); + } + + /** + * Tests a GET. + */ + @Test + public void testGet() throws Exception { + // use a real client + client = HttpClientFactoryInstance.getClientFactory().get(HTTP_CLIENT); + + MyGetOperation<MyResponse> oper2 = new MyGetOperation<>(MyResponse.class); + + OperationOutcome outcome = runOperation(oper2); + assertNotNull(outcome); + assertEquals(1, nget); + assertEquals(PolicyResult.SUCCESS, outcome.getResult()); + } + + /** + * Tests a DELETE. + */ + @Test + public void testDelete() throws Exception { + // use a real client + client = HttpClientFactoryInstance.getClientFactory().get(HTTP_CLIENT); + + MyDeleteOperation oper2 = new MyDeleteOperation(); + + OperationOutcome outcome = runOperation(oper2); + assertNotNull(outcome); + assertEquals(1, ndelete); + assertEquals(PolicyResult.SUCCESS, outcome.getResult()); + } + + /** + * Tests a POST. + */ + @Test + public void testPost() throws Exception { + // use a real client + client = HttpClientFactoryInstance.getClientFactory().get(HTTP_CLIENT); + + MyPostOperation oper2 = new MyPostOperation(); + + OperationOutcome outcome = runOperation(oper2); + assertNotNull(outcome); + assertEquals(1, npost); + assertEquals(PolicyResult.SUCCESS, outcome.getResult()); + } + + /** + * Tests a PUT. + */ + @Test + public void testPut() throws Exception { + // use a real client + client = HttpClientFactoryInstance.getClientFactory().get(HTTP_CLIENT); + + MyPutOperation oper2 = new MyPutOperation(); + + OperationOutcome outcome = runOperation(oper2); + assertNotNull(outcome); + assertEquals(1, nput); + assertEquals(PolicyResult.SUCCESS, outcome.getResult()); + } + + @Test + public void testLogRestRequest() throws CoderException { + // log structured data + appender.clearExtractions(); + oper.logRestRequest(PATH, new MyRequest()); + List<String> output = appender.getExtracted(); + assertEquals(1, output.size()); + + assertThat(output.get(0)).contains(PATH).contains("{\n \"input\": \"some input\"\n}"); + + // log a plain string + appender.clearExtractions(); + oper.logRestRequest(PATH, MY_REQUEST); + output = appender.getExtracted(); + assertEquals(1, output.size()); + + assertThat(output.get(0)).contains(PATH).contains(MY_REQUEST); + + // log a null request + appender.clearExtractions(); + oper.logRestRequest(PATH, null); + output = appender.getExtracted(); + assertEquals(1, output.size()); + + // exception from coder + oper = new MyGetOperation<>(String.class) { + @Override + protected Coder makeCoder() { + return new StandardCoder() { + @Override + public String encode(Object object, boolean pretty) throws CoderException { + throw new CoderException(EXPECTED_EXCEPTION); + } + }; + } + }; + + appender.clearExtractions(); + oper.logRestRequest(PATH, new MyRequest()); + output = appender.getExtracted(); + assertEquals(2, output.size()); + assertThat(output.get(0)).contains("cannot pretty-print request"); + assertThat(output.get(1)).contains(PATH); + } + + @Test + public void testLogRestResponse() throws CoderException { + // log structured data + appender.clearExtractions(); + oper.logRestResponse(PATH, new MyResponse()); + List<String> output = appender.getExtracted(); + assertEquals(1, output.size()); + + assertThat(output.get(0)).contains(PATH).contains("{\n \"output\": \"some output\"\n}"); + + // log a plain string + appender.clearExtractions(); + oper.logRestResponse(PATH, MY_REQUEST); + output = appender.getExtracted(); + assertEquals(1, output.size()); + + // log a null response + appender.clearExtractions(); + oper.logRestResponse(PATH, null); + output = appender.getExtracted(); + assertEquals(1, output.size()); + + assertThat(output.get(0)).contains(PATH).contains("null"); + + // exception from coder + oper = new MyGetOperation<>(String.class) { + @Override + protected Coder makeCoder() { + return new StandardCoder() { + @Override + public String encode(Object object, boolean pretty) throws CoderException { + throw new CoderException(EXPECTED_EXCEPTION); + } + }; + } + }; + + appender.clearExtractions(); + oper.logRestResponse(PATH, new MyResponse()); + output = appender.getExtracted(); + assertEquals(2, output.size()); + assertThat(output.get(0)).contains("cannot pretty-print response"); + assertThat(output.get(1)).contains(PATH); + } + + @Test + public void testMakeDecoder() { + assertNotNull(oper.makeCoder()); + } + + /** + * Gets server properties. + * + * @param name server name + * @param port server port + * @return server properties + */ + private static Properties getServerProperties(String name, int port) { + final Properties props = new Properties(); + props.setProperty(PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES, name); + + final String svcpfx = PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES + "." + name; + + props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_REST_CLASSES_SUFFIX, Server.class.getName()); + props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_HOST_SUFFIX, "localhost"); + props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_PORT_SUFFIX, String.valueOf(port)); + props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_MANAGED_SUFFIX, "true"); + props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SWAGGER_SUFFIX, "false"); + + props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SERIALIZATION_PROVIDER, + GsonMessageBodyHandler.class.getName()); + return props; + } + + /** + * Initializes the given operator. + * + * @param operator operator to be initialized + * @param clientName name of the client which it should use + */ + private void initOper(HttpOperator operator, String clientName) { + operator.stop(); + + HttpParams params = HttpParams.builder().clientName(clientName).path(PATH).build(); + Map<String, Object> mapParams = Util.translateToMap(OPERATION, params); + operator.configure(mapParams); + operator.start(); + } + + /** + * Runs the operation. + * + * @param operator operator on which to start the operation + * @return the outcome of the operation, or {@code null} if it does not complete in + * time + */ + private <T> OperationOutcome runOperation(HttpOperation<T> operator) + throws InterruptedException, ExecutionException, TimeoutException { + + CompletableFuture<OperationOutcome> future = operator.start(); + + return future.get(5, TimeUnit.SECONDS); + } + + @Getter + @Setter + public static class MyRequest { + private String input = "some input"; + } + + @Getter + @Setter + public static class MyResponse { + private String output = "some output"; + } + + private class MyGetOperation<T> extends HttpOperation<T> { + public MyGetOperation(Class<T> responseClass) { + super(HttpOperationTest.this.params, HttpOperationTest.this.operator, responseClass); + } + + @Override + protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) { + Map<String, Object> headers = makeHeaders(); + + headers.put("Accept", MediaType.APPLICATION_JSON); + String url = makeUrl(); + + logRestRequest(url, null); + + // @formatter:off + return handleResponse(outcome, url, + callback -> operator.getClient().get(callback, makePath(), headers)); + // @formatter:on + } + } + + private class MyPostOperation extends HttpOperation<MyResponse> { + public MyPostOperation() { + super(HttpOperationTest.this.params, HttpOperationTest.this.operator, MyResponse.class); + } + + @Override + protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) { + + MyRequest request = new MyRequest(); + + Entity<MyRequest> entity = Entity.entity(request, MediaType.APPLICATION_JSON); + + Map<String, Object> headers = makeHeaders(); + + headers.put("Accept", MediaType.APPLICATION_JSON); + String url = makeUrl(); + + logRestRequest(url, request); + + // @formatter:off + return handleResponse(outcome, url, + callback -> operator.getClient().post(callback, makePath(), entity, headers)); + // @formatter:on + } + } + + private class MyPutOperation extends HttpOperation<MyResponse> { + public MyPutOperation() { + super(HttpOperationTest.this.params, HttpOperationTest.this.operator, MyResponse.class); + } + + @Override + protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) { + + MyRequest request = new MyRequest(); + + Entity<MyRequest> entity = Entity.entity(request, MediaType.APPLICATION_JSON); + + Map<String, Object> headers = makeHeaders(); + + headers.put("Accept", MediaType.APPLICATION_JSON); + String url = makeUrl(); + + logRestRequest(url, request); + + // @formatter:off + return handleResponse(outcome, url, + callback -> operator.getClient().put(callback, makePath(), entity, headers)); + // @formatter:on + } + } + + private class MyDeleteOperation extends HttpOperation<String> { + public MyDeleteOperation() { + super(HttpOperationTest.this.params, HttpOperationTest.this.operator, String.class); + } + + @Override + protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) { + Map<String, Object> headers = makeHeaders(); + + headers.put("Accept", MediaType.APPLICATION_JSON); + String url = makeUrl(); + + logRestRequest(url, null); + + // @formatter:off + return handleResponse(outcome, url, + callback -> operator.getClient().delete(callback, makePath(), headers)); + // @formatter:on + } + } + + /** + * Simulator. + */ + @Path("/" + BASE_URI) + @Produces(MEDIA_TYPE_APPLICATION_JSON) + @Consumes(value = {MEDIA_TYPE_APPLICATION_JSON}) + public static class Server { + + /** + * Generates a response to a GET. + * + * @return resulting response + */ + @GET + @Path(PATH) + public Response getRequest() { + ++nget; + + if (rejectRequest) { + return Response.status(Status.BAD_REQUEST).build(); + + } else { + return Response.status(Status.OK).entity(new MyResponse()).build(); + } + } + + /** + * Generates a response to a POST. + * + * @param request incoming request + * @return resulting response + */ + @POST + @Path(PATH) + public Response postRequest(MyRequest request) { + ++npost; + + if (rejectRequest) { + return Response.status(Status.BAD_REQUEST).build(); + + } else { + return Response.status(Status.OK).entity(new MyResponse()).build(); + } + } + + /** + * Generates a response to a PUT. + * + * @param request incoming request + * @return resulting response + */ + @PUT + @Path(PATH) + public Response putRequest(MyRequest request) { + ++nput; + + if (rejectRequest) { + return Response.status(Status.BAD_REQUEST).build(); + + } else { + return Response.status(Status.OK).entity(new MyResponse()).build(); + } + } + + /** + * Generates a response to a DELETE. + * + * @return resulting response + */ + @DELETE + @Path(PATH) + public Response deleteRequest() { + ++ndelete; + + if (rejectRequest) { + return Response.status(Status.BAD_REQUEST).build(); + + } else { + return Response.status(Status.OK).entity(new MyResponse()).build(); + } + } + } +} |