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