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