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