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