More changes to actor code
[policy/models.git] / models-interactions / model-actors / actorServiceProvider / src / test / java / org / onap / policy / controlloop / actorserviceprovider / impl / HttpOperationTest.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP
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
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
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=========================================================
19  */
20
21 package org.onap.policy.controlloop.actorserviceprovider.impl;
22
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;
34
35 import java.util.Collections;
36 import java.util.Map;
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.Future;
43 import java.util.concurrent.TimeUnit;
44 import java.util.concurrent.TimeoutException;
45 import java.util.concurrent.atomic.AtomicReference;
46 import javax.ws.rs.Consumes;
47 import javax.ws.rs.DELETE;
48 import javax.ws.rs.GET;
49 import javax.ws.rs.POST;
50 import javax.ws.rs.PUT;
51 import javax.ws.rs.Path;
52 import javax.ws.rs.Produces;
53 import javax.ws.rs.client.Entity;
54 import javax.ws.rs.client.InvocationCallback;
55 import javax.ws.rs.core.MediaType;
56 import javax.ws.rs.core.Response;
57 import javax.ws.rs.core.Response.Status;
58 import lombok.Getter;
59 import lombok.Setter;
60 import org.junit.AfterClass;
61 import org.junit.Before;
62 import org.junit.BeforeClass;
63 import org.junit.Test;
64 import org.mockito.Mock;
65 import org.mockito.MockitoAnnotations;
66 import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
67 import org.onap.policy.common.endpoints.event.comm.bus.internal.BusTopicParams;
68 import org.onap.policy.common.endpoints.event.comm.bus.internal.BusTopicParams.TopicParamsBuilder;
69 import org.onap.policy.common.endpoints.http.client.HttpClient;
70 import org.onap.policy.common.endpoints.http.client.HttpClientFactoryInstance;
71 import org.onap.policy.common.endpoints.http.server.HttpServletServer;
72 import org.onap.policy.common.endpoints.http.server.HttpServletServerFactoryInstance;
73 import org.onap.policy.common.endpoints.properties.PolicyEndPointProperties;
74 import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
75 import org.onap.policy.common.gson.GsonMessageBodyHandler;
76 import org.onap.policy.common.utils.coder.CoderException;
77 import org.onap.policy.common.utils.network.NetworkUtil;
78 import org.onap.policy.controlloop.VirtualControlLoopEvent;
79 import org.onap.policy.controlloop.actorserviceprovider.Operation;
80 import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
81 import org.onap.policy.controlloop.actorserviceprovider.Util;
82 import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
83 import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
84 import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams;
85 import org.onap.policy.controlloop.policy.PolicyResult;
86
87 public class HttpOperationTest {
88
89     private static final IllegalStateException EXPECTED_EXCEPTION = new IllegalStateException("expected exception");
90     private static final String ACTOR = "my-actor";
91     private static final String OPERATION = "my-name";
92     private static final String HTTP_CLIENT = "my-client";
93     private static final String HTTP_NO_SERVER = "my-http-no-server-client";
94     private static final String MEDIA_TYPE_APPLICATION_JSON = "application/json";
95     private static final String BASE_URI = "oper";
96     private static final String PATH = "/my-path";
97     private static final String TEXT = "my-text";
98     private static final UUID REQ_ID = UUID.randomUUID();
99
100     /**
101      * {@code True} if the server should reject the request, {@code false} otherwise.
102      */
103     private static boolean rejectRequest;
104
105     // call counts of each method type in the server
106     private static int nget;
107     private static int npost;
108     private static int nput;
109     private static int ndelete;
110
111     @Mock
112     private HttpClient client;
113
114     @Mock
115     private Response response;
116
117     private VirtualControlLoopEvent event;
118     private ControlLoopEventContext context;
119     private ControlLoopOperationParams params;
120     private OperationOutcome outcome;
121     private AtomicReference<InvocationCallback<Response>> callback;
122     private Future<Response> future;
123     private HttpOperator operator;
124     private MyGetOperation<String> oper;
125
126     /**
127      * Starts the simulator.
128      */
129     @BeforeClass
130     public static void setUpBeforeClass() throws Exception {
131         // allocate a port
132         int port = NetworkUtil.allocPort();
133
134         /*
135          * Start the simulator. Must use "Properties" to configure it, otherwise the
136          * server will use the wrong serialization provider.
137          */
138         Properties svrprops = getServerProperties("my-server", port);
139         HttpServletServerFactoryInstance.getServerFactory().build(svrprops).forEach(HttpServletServer::start);
140
141         if (!NetworkUtil.isTcpPortOpen("localhost", port, 100, 100)) {
142             HttpServletServerFactoryInstance.getServerFactory().destroy();
143             throw new IllegalStateException("server is not running");
144         }
145
146         /*
147          * Start the clients, one to the server, and one to a non-existent server.
148          */
149         TopicParamsBuilder builder = BusTopicParams.builder().managed(true).hostname("localhost").basePath(BASE_URI)
150                         .serializationProvider(GsonMessageBodyHandler.class.getName());
151
152         HttpClientFactoryInstance.getClientFactory().build(builder.clientName(HTTP_CLIENT).port(port).build());
153
154         HttpClientFactoryInstance.getClientFactory()
155                         .build(builder.clientName(HTTP_NO_SERVER).port(NetworkUtil.allocPort()).build());
156     }
157
158     /**
159      * Destroys the Http factories and stops the appender.
160      */
161     @AfterClass
162     public static void tearDownAfterClass() {
163         HttpClientFactoryInstance.getClientFactory().destroy();
164         HttpServletServerFactoryInstance.getServerFactory().destroy();
165     }
166
167     /**
168      * Initializes fields, including {@link #oper}, and resets the static fields used by
169      * the REST server.
170      */
171     @Before
172     public void setUp() {
173         MockitoAnnotations.initMocks(this);
174
175         rejectRequest = false;
176         nget = 0;
177         npost = 0;
178         nput = 0;
179         ndelete = 0;
180
181         when(response.readEntity(String.class)).thenReturn(TEXT);
182         when(response.getStatus()).thenReturn(200);
183
184         event = new VirtualControlLoopEvent();
185         event.setRequestId(REQ_ID);
186
187         context = new ControlLoopEventContext(event);
188         params = ControlLoopOperationParams.builder().actor(ACTOR).operation(OPERATION).context(context).build();
189
190         outcome = params.makeOutcome();
191
192         callback = new AtomicReference<>();
193         future = new CompletableFuture<>();
194
195         operator = new HttpOperator(ACTOR, OPERATION) {
196             @Override
197             public Operation buildOperation(ControlLoopOperationParams params) {
198                 return null;
199             }
200
201             @Override
202             public HttpClient getClient() {
203                 return client;
204             }
205         };
206
207         initOper(operator, HTTP_CLIENT);
208
209         oper = new MyGetOperation<>(String.class);
210     }
211
212     @Test
213     public void testHttpOperator() {
214         assertEquals(ACTOR, oper.getActorName());
215         assertEquals(OPERATION, oper.getName());
216         assertEquals(ACTOR + "." + OPERATION, oper.getFullName());
217     }
218
219     @Test
220     public void testMakeHeaders() {
221         assertEquals(Collections.emptyMap(), oper.makeHeaders());
222     }
223
224     @Test
225     public void testMakePath() {
226         assertEquals(PATH, oper.makePath());
227     }
228
229     @Test
230     public void testMakeUrl() {
231         // use a real client
232         client = HttpClientFactoryInstance.getClientFactory().get(HTTP_CLIENT);
233
234         assertThat(oper.makeUrl()).endsWith("/" + BASE_URI + PATH);
235     }
236
237     @Test
238     public void testDoConfigureMapOfStringObject_testGetClient_testGetPath_testGetTimeoutMs() {
239
240         // use value from operator
241         assertEquals(1000L, oper.getTimeoutMs(null));
242         assertEquals(1000L, oper.getTimeoutMs(0));
243
244         // should use given value
245         assertEquals(20 * 1000L, oper.getTimeoutMs(20));
246
247         // indicate we have a timeout value
248         operator = spy(operator);
249         when(operator.getTimeoutMs()).thenReturn(30L);
250
251         oper = new MyGetOperation<String>(String.class);
252
253         // should use default
254         assertEquals(30L, oper.getTimeoutMs(null));
255         assertEquals(30L, oper.getTimeoutMs(0));
256
257         // should use given value
258         assertEquals(40 * 1000L, oper.getTimeoutMs(40));
259     }
260
261     /**
262      * Tests handleResponse() when it completes.
263      */
264     @Test
265     public void testHandleResponseComplete() throws Exception {
266         CompletableFuture<OperationOutcome> future2 = oper.handleResponse(outcome, PATH, cb -> {
267             callback.set(cb);
268             return future;
269         });
270
271         assertFalse(future2.isDone());
272         assertNotNull(callback.get());
273         callback.get().completed(response);
274
275         assertSame(outcome, future2.get(5, TimeUnit.SECONDS));
276
277         assertEquals(PolicyResult.SUCCESS, outcome.getResult());
278     }
279
280     /**
281      * Tests handleResponse() when it fails.
282      */
283     @Test
284     public void testHandleResponseFailed() throws Exception {
285         CompletableFuture<OperationOutcome> future2 = oper.handleResponse(outcome, PATH, cb -> {
286             callback.set(cb);
287             return future;
288         });
289
290         assertFalse(future2.isDone());
291         assertNotNull(callback.get());
292         callback.get().failed(EXPECTED_EXCEPTION);
293
294         assertThatThrownBy(() -> future2.get(5, TimeUnit.SECONDS)).hasCause(EXPECTED_EXCEPTION);
295
296         // future and future2 may be completed in parallel so we must wait again
297         assertThatThrownBy(() -> future.get(5, TimeUnit.SECONDS)).isInstanceOf(CancellationException.class);
298         assertTrue(future.isCancelled());
299     }
300
301     /**
302      * Tests processResponse() when it's a success and the response type is a String.
303      */
304     @Test
305     public void testProcessResponseSuccessString() throws Exception {
306         CompletableFuture<OperationOutcome> result = oper.processResponse(outcome, PATH, response);
307         assertTrue(result.isDone());
308         assertSame(outcome, result.get());
309         assertEquals(PolicyResult.SUCCESS, outcome.getResult());
310     }
311
312     /**
313      * Tests processResponse() when it's a failure.
314      */
315     @Test
316     public void testProcessResponseFailure() throws Exception {
317         when(response.getStatus()).thenReturn(555);
318         CompletableFuture<OperationOutcome> result = oper.processResponse(outcome, PATH, response);
319         assertTrue(result.isDone());
320         assertSame(outcome, result.get());
321         assertEquals(PolicyResult.FAILURE, outcome.getResult());
322     }
323
324     /**
325      * Tests processResponse() when the decoder succeeds.
326      */
327     @Test
328     public void testProcessResponseDecodeOk() throws Exception {
329         when(response.readEntity(String.class)).thenReturn("10");
330
331         MyGetOperation<Integer> oper2 = new MyGetOperation<>(Integer.class);
332
333         CompletableFuture<OperationOutcome> result = oper2.processResponse(outcome, PATH, response);
334         assertTrue(result.isDone());
335         assertSame(outcome, result.get());
336         assertEquals(PolicyResult.SUCCESS, outcome.getResult());
337     }
338
339     /**
340      * Tests processResponse() when the decoder throws an exception.
341      */
342     @Test
343     public void testProcessResponseDecodeExcept() throws CoderException {
344         MyGetOperation<Integer> oper2 = new MyGetOperation<>(Integer.class);
345
346         assertThatIllegalArgumentException().isThrownBy(() -> oper2.processResponse(outcome, PATH, response));
347     }
348
349     @Test
350     public void testPostProcessResponse() {
351         assertThatCode(() -> oper.postProcessResponse(outcome, PATH, null, null)).doesNotThrowAnyException();
352     }
353
354     @Test
355     public void testIsSuccess() {
356         when(response.getStatus()).thenReturn(200);
357         assertTrue(oper.isSuccess(response, null));
358
359         when(response.getStatus()).thenReturn(555);
360         assertFalse(oper.isSuccess(response, null));
361     }
362
363     /**
364      * Tests a GET.
365      */
366     @Test
367     public void testGet() throws Exception {
368         // use a real client
369         client = HttpClientFactoryInstance.getClientFactory().get(HTTP_CLIENT);
370
371         MyGetOperation<MyResponse> oper2 = new MyGetOperation<>(MyResponse.class);
372
373         OperationOutcome outcome = runOperation(oper2);
374         assertNotNull(outcome);
375         assertEquals(1, nget);
376         assertEquals(PolicyResult.SUCCESS, outcome.getResult());
377     }
378
379     /**
380      * Tests a DELETE.
381      */
382     @Test
383     public void testDelete() throws Exception {
384         // use a real client
385         client = HttpClientFactoryInstance.getClientFactory().get(HTTP_CLIENT);
386
387         MyDeleteOperation oper2 = new MyDeleteOperation();
388
389         OperationOutcome outcome = runOperation(oper2);
390         assertNotNull(outcome);
391         assertEquals(1, ndelete);
392         assertEquals(PolicyResult.SUCCESS, outcome.getResult());
393     }
394
395     /**
396      * Tests a POST.
397      */
398     @Test
399     public void testPost() throws Exception {
400         // use a real client
401         client = HttpClientFactoryInstance.getClientFactory().get(HTTP_CLIENT);
402
403         MyPostOperation oper2 = new MyPostOperation();
404
405         OperationOutcome outcome = runOperation(oper2);
406         assertNotNull(outcome);
407         assertEquals(1, npost);
408         assertEquals(PolicyResult.SUCCESS, outcome.getResult());
409     }
410
411     /**
412      * Tests a PUT.
413      */
414     @Test
415     public void testPut() throws Exception {
416         // use a real client
417         client = HttpClientFactoryInstance.getClientFactory().get(HTTP_CLIENT);
418
419         MyPutOperation oper2 = new MyPutOperation();
420
421         OperationOutcome outcome = runOperation(oper2);
422         assertNotNull(outcome);
423         assertEquals(1, nput);
424         assertEquals(PolicyResult.SUCCESS, outcome.getResult());
425     }
426
427     @Test
428     public void testMakeDecoder() {
429         assertNotNull(oper.makeCoder());
430     }
431
432     /**
433      * Gets server properties.
434      *
435      * @param name server name
436      * @param port server port
437      * @return server properties
438      */
439     private static Properties getServerProperties(String name, int port) {
440         final Properties props = new Properties();
441         props.setProperty(PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES, name);
442
443         final String svcpfx = PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES + "." + name;
444
445         props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_REST_CLASSES_SUFFIX, Server.class.getName());
446         props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_HOST_SUFFIX, "localhost");
447         props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_PORT_SUFFIX, String.valueOf(port));
448         props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_MANAGED_SUFFIX, "true");
449         props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SWAGGER_SUFFIX, "false");
450
451         props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SERIALIZATION_PROVIDER,
452                         GsonMessageBodyHandler.class.getName());
453         return props;
454     }
455
456     /**
457      * Initializes the given operator.
458      *
459      * @param operator operator to be initialized
460      * @param clientName name of the client which it should use
461      */
462     private void initOper(HttpOperator operator, String clientName) {
463         operator.stop();
464
465         HttpParams params = HttpParams.builder().clientName(clientName).path(PATH).timeoutSec(1).build();
466         Map<String, Object> mapParams = Util.translateToMap(OPERATION, params);
467         operator.configure(mapParams);
468         operator.start();
469     }
470
471     /**
472      * Runs the operation.
473      *
474      * @param operator operator on which to start the operation
475      * @return the outcome of the operation, or {@code null} if it does not complete in
476      *         time
477      */
478     private <T> OperationOutcome runOperation(HttpOperation<T> operator)
479                     throws InterruptedException, ExecutionException, TimeoutException {
480
481         CompletableFuture<OperationOutcome> future = operator.start();
482
483         return future.get(5, TimeUnit.SECONDS);
484     }
485
486     @Getter
487     @Setter
488     public static class MyRequest {
489         private String input = "some input";
490     }
491
492     @Getter
493     @Setter
494     public static class MyResponse {
495         private String output = "some output";
496     }
497
498     private class MyGetOperation<T> extends HttpOperation<T> {
499         public MyGetOperation(Class<T> responseClass) {
500             super(HttpOperationTest.this.params, HttpOperationTest.this.operator, responseClass);
501         }
502
503         @Override
504         protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
505             Map<String, Object> headers = makeHeaders();
506
507             headers.put("Accept", MediaType.APPLICATION_JSON);
508             String url = makeUrl();
509
510             logMessage(EventType.OUT, CommInfrastructure.REST, url, null);
511
512             // @formatter:off
513             return handleResponse(outcome, url,
514                 callback -> operator.getClient().get(callback, makePath(), headers));
515             // @formatter:on
516         }
517     }
518
519     private class MyPostOperation extends HttpOperation<MyResponse> {
520         public MyPostOperation() {
521             super(HttpOperationTest.this.params, HttpOperationTest.this.operator, MyResponse.class);
522         }
523
524         @Override
525         protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
526
527             MyRequest request = new MyRequest();
528
529             Entity<MyRequest> entity = Entity.entity(request, MediaType.APPLICATION_JSON);
530
531             Map<String, Object> headers = makeHeaders();
532
533             headers.put("Accept", MediaType.APPLICATION_JSON);
534             String url = makeUrl();
535
536             logMessage(EventType.OUT, CommInfrastructure.REST, url, request);
537
538             // @formatter:off
539             return handleResponse(outcome, url,
540                 callback -> operator.getClient().post(callback, makePath(), entity, headers));
541             // @formatter:on
542         }
543     }
544
545     private class MyPutOperation extends HttpOperation<MyResponse> {
546         public MyPutOperation() {
547             super(HttpOperationTest.this.params, HttpOperationTest.this.operator, MyResponse.class);
548         }
549
550         @Override
551         protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
552
553             MyRequest request = new MyRequest();
554
555             Entity<MyRequest> entity = Entity.entity(request, MediaType.APPLICATION_JSON);
556
557             Map<String, Object> headers = makeHeaders();
558
559             headers.put("Accept", MediaType.APPLICATION_JSON);
560             String url = makeUrl();
561
562             logMessage(EventType.OUT, CommInfrastructure.REST, url, request);
563
564             // @formatter:off
565             return handleResponse(outcome, url,
566                 callback -> operator.getClient().put(callback, makePath(), entity, headers));
567             // @formatter:on
568         }
569     }
570
571     private class MyDeleteOperation extends HttpOperation<String> {
572         public MyDeleteOperation() {
573             super(HttpOperationTest.this.params, HttpOperationTest.this.operator, String.class);
574         }
575
576         @Override
577         protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
578             Map<String, Object> headers = makeHeaders();
579
580             headers.put("Accept", MediaType.APPLICATION_JSON);
581             String url = makeUrl();
582
583             logMessage(EventType.OUT, CommInfrastructure.REST, url, null);
584
585             // @formatter:off
586             return handleResponse(outcome, url,
587                 callback -> operator.getClient().delete(callback, makePath(), headers));
588             // @formatter:on
589         }
590     }
591
592     /**
593      * Simulator.
594      */
595     @Path("/" + BASE_URI)
596     @Produces(MEDIA_TYPE_APPLICATION_JSON)
597     @Consumes(value = {MEDIA_TYPE_APPLICATION_JSON})
598     public static class Server {
599
600         /**
601          * Generates a response to a GET.
602          *
603          * @return resulting response
604          */
605         @GET
606         @Path(PATH)
607         public Response getRequest() {
608             ++nget;
609
610             if (rejectRequest) {
611                 return Response.status(Status.BAD_REQUEST).build();
612
613             } else {
614                 return Response.status(Status.OK).entity(new MyResponse()).build();
615             }
616         }
617
618         /**
619          * Generates a response to a POST.
620          *
621          * @param request incoming request
622          * @return resulting response
623          */
624         @POST
625         @Path(PATH)
626         public Response postRequest(MyRequest request) {
627             ++npost;
628
629             if (rejectRequest) {
630                 return Response.status(Status.BAD_REQUEST).build();
631
632             } else {
633                 return Response.status(Status.OK).entity(new MyResponse()).build();
634             }
635         }
636
637         /**
638          * Generates a response to a PUT.
639          *
640          * @param request incoming request
641          * @return resulting response
642          */
643         @PUT
644         @Path(PATH)
645         public Response putRequest(MyRequest request) {
646             ++nput;
647
648             if (rejectRequest) {
649                 return Response.status(Status.BAD_REQUEST).build();
650
651             } else {
652                 return Response.status(Status.OK).entity(new MyResponse()).build();
653             }
654         }
655
656         /**
657          * Generates a response to a DELETE.
658          *
659          * @return resulting response
660          */
661         @DELETE
662         @Path(PATH)
663         public Response deleteRequest() {
664             ++ndelete;
665
666             if (rejectRequest) {
667                 return Response.status(Status.BAD_REQUEST).build();
668
669             } else {
670                 return Response.status(Status.OK).entity(new MyResponse()).build();
671             }
672         }
673     }
674 }