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