Merge "Convert models to JUnit 5"
[policy/models.git] / models-interactions / model-actors / actorServiceProvider / src / test / java / org / onap / policy / controlloop / actorserviceprovider / impl / BidirectionalTopicOperationTest.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, 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.assertThatCode;
25 import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
26 import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
27 import static org.junit.jupiter.api.Assertions.assertEquals;
28 import static org.junit.jupiter.api.Assertions.assertFalse;
29 import static org.junit.jupiter.api.Assertions.assertNotNull;
30 import static org.junit.jupiter.api.Assertions.assertSame;
31 import static org.junit.jupiter.api.Assertions.assertTrue;
32 import static org.mockito.ArgumentMatchers.any;
33 import static org.mockito.ArgumentMatchers.eq;
34 import static org.mockito.Mockito.never;
35 import static org.mockito.Mockito.verify;
36 import static org.mockito.Mockito.when;
37
38 import java.util.Arrays;
39 import java.util.Collections;
40 import java.util.List;
41 import java.util.concurrent.CompletableFuture;
42 import java.util.function.BiConsumer;
43 import lombok.EqualsAndHashCode;
44 import lombok.Getter;
45 import lombok.Setter;
46 import org.junit.jupiter.api.BeforeEach;
47 import org.junit.jupiter.api.Test;
48 import org.junit.jupiter.api.extension.ExtendWith;
49 import org.mockito.ArgumentCaptor;
50 import org.mockito.Captor;
51 import org.mockito.Mock;
52 import org.mockito.Mockito;
53 import org.mockito.junit.jupiter.MockitoExtension;
54 import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
55 import org.onap.policy.common.utils.coder.Coder;
56 import org.onap.policy.common.utils.coder.CoderException;
57 import org.onap.policy.common.utils.coder.StandardCoder;
58 import org.onap.policy.common.utils.coder.StandardCoderObject;
59 import org.onap.policy.common.utils.time.PseudoExecutor;
60 import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
61 import org.onap.policy.controlloop.actorserviceprovider.OperationResult;
62 import org.onap.policy.controlloop.actorserviceprovider.parameters.BidirectionalTopicConfig;
63 import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
64 import org.onap.policy.controlloop.actorserviceprovider.topic.BidirectionalTopicHandler;
65 import org.onap.policy.controlloop.actorserviceprovider.topic.Forwarder;
66
67 @ExtendWith(MockitoExtension.class)
68 class BidirectionalTopicOperationTest {
69     private static final CommInfrastructure SINK_INFRA = CommInfrastructure.NOOP;
70     private static final IllegalStateException EXPECTED_EXCEPTION = new IllegalStateException("expected exception");
71     private static final String ACTOR = "my-actor";
72     private static final String OPERATION = "my-operation";
73     private static final String REQ_ID = "my-request-id";
74     private static final String TEXT = "some text";
75     private static final int TIMEOUT_SEC = 10;
76     private static final long TIMEOUT_MS = 1000 * TIMEOUT_SEC;
77     private static final int MAX_REQUESTS = 100;
78
79     private static final StandardCoder coder = new StandardCoder();
80
81     @Mock
82     private BidirectionalTopicConfig config;
83     @Mock
84     private BidirectionalTopicHandler handler;
85     @Mock
86     private Forwarder forwarder;
87
88     @Captor
89     private ArgumentCaptor<BiConsumer<String, StandardCoderObject>> listenerCaptor;
90
91     private ControlLoopOperationParams params;
92     private OperationOutcome outcome;
93     private StandardCoderObject stdResponse;
94     private MyResponse response;
95     private String responseText;
96     private PseudoExecutor executor;
97     private int ntimes;
98     private BidirectionalTopicOperation<MyRequest, MyResponse> oper;
99
100     /**
101      * Sets up.
102      */
103     @BeforeEach
104     void setUp() throws CoderException {
105         Mockito.lenient().when(config.getTopicHandler()).thenReturn(handler);
106         Mockito.lenient().when(config.getForwarder()).thenReturn(forwarder);
107         Mockito.lenient().when(config.getTimeoutMs()).thenReturn(TIMEOUT_MS);
108
109         Mockito.lenient().when(handler.send(any())).thenReturn(true);
110         Mockito.lenient().when(handler.getSinkTopicCommInfrastructure()).thenReturn(SINK_INFRA);
111
112         executor = new PseudoExecutor();
113
114         params = ControlLoopOperationParams.builder().actor(ACTOR).operation(OPERATION).executor(executor).build();
115         outcome = params.makeOutcome();
116
117         response = new MyResponse();
118         response.setRequestId(REQ_ID);
119         responseText = coder.encode(response);
120         stdResponse = coder.decode(responseText, StandardCoderObject.class);
121
122         ntimes = 1;
123
124         oper = new MyOperation(params, config);
125     }
126
127     @Test
128     void testConstructor_testGetTopicHandler_testGetForwarder_testGetTopicParams() {
129         assertEquals(ACTOR, oper.getActorName());
130         assertEquals(OPERATION, oper.getName());
131         assertSame(handler, oper.getTopicHandler());
132         assertSame(forwarder, oper.getForwarder());
133         assertEquals(TIMEOUT_MS, oper.getTimeoutMs());
134         assertSame(MyResponse.class, oper.getResponseClass());
135     }
136
137     @Test
138     void testStartOperationAsync() throws Exception {
139         // tell it to expect three responses
140         ntimes = 3;
141
142         CompletableFuture<OperationOutcome> future = oper.startOperationAsync(1, outcome);
143         assertFalse(future.isDone());
144
145         verify(forwarder).register(eq(Arrays.asList(REQ_ID)), listenerCaptor.capture());
146
147         verify(forwarder, never()).unregister(any(), any());
148
149         verify(handler).send(any());
150
151         // provide first response
152         listenerCaptor.getValue().accept(responseText, stdResponse);
153         assertTrue(executor.runAll(MAX_REQUESTS));
154         assertFalse(future.isDone());
155
156         // provide second response
157         listenerCaptor.getValue().accept(responseText, stdResponse);
158         assertTrue(executor.runAll(MAX_REQUESTS));
159         assertFalse(future.isDone());
160
161         // provide final response
162         listenerCaptor.getValue().accept(responseText, stdResponse);
163         assertTrue(executor.runAll(MAX_REQUESTS));
164         assertTrue(future.isDone());
165
166         assertSame(outcome, future.get());
167         assertEquals(OperationResult.SUCCESS, outcome.getResult());
168         assertEquals(response, outcome.getResponse());
169
170         verify(forwarder).unregister(Arrays.asList(REQ_ID), listenerCaptor.getValue());
171     }
172
173     /**
174      * Tests startOperationAsync() when processResponse() throws an exception.
175      */
176     @Test
177     void testStartOperationAsyncProcException() throws Exception {
178         oper = new MyOperation(params, config) {
179             @Override
180             protected OperationOutcome processResponse(OperationOutcome outcome, String rawResponse,
181                                                        StandardCoderObject scoResponse) {
182                 throw EXPECTED_EXCEPTION;
183             }
184         };
185
186         CompletableFuture<OperationOutcome> future = oper.startOperationAsync(1, outcome);
187         assertFalse(future.isDone());
188
189         verify(forwarder).register(eq(Arrays.asList(REQ_ID)), listenerCaptor.capture());
190
191         verify(forwarder, never()).unregister(any(), any());
192
193         // provide a response
194         listenerCaptor.getValue().accept(responseText, stdResponse);
195         assertTrue(executor.runAll(MAX_REQUESTS));
196         assertTrue(future.isCompletedExceptionally());
197
198         verify(forwarder).unregister(Arrays.asList(REQ_ID), listenerCaptor.getValue());
199     }
200
201     /**
202      * Tests startOperationAsync() when the publisher throws an exception.
203      */
204     @Test
205      void testStartOperationAsyncPubException() throws Exception {
206         // indicate that nothing was published
207         when(handler.send(any())).thenReturn(false);
208
209         assertThatIllegalStateException().isThrownBy(() -> oper.startOperationAsync(1, outcome));
210
211         verify(forwarder).register(eq(Arrays.asList(REQ_ID)), listenerCaptor.capture());
212
213         // must still unregister
214         verify(forwarder).unregister(Arrays.asList(REQ_ID), listenerCaptor.getValue());
215     }
216
217     @Test
218      void testGetTimeoutMsInteger() {
219         // use default
220         assertEquals(TIMEOUT_MS, oper.getTimeoutMs(null));
221         assertEquals(TIMEOUT_MS, oper.getTimeoutMs(0));
222
223         // use provided value
224         assertEquals(5000, oper.getTimeoutMs(5));
225     }
226
227     @Test
228      void testPublishRequest() {
229         assertThatCode(() -> oper.publishRequest(new MyRequest())).doesNotThrowAnyException();
230     }
231
232     /**
233      * Tests publishRequest() when nothing is published.
234      */
235     @Test
236      void testPublishRequestUnpublished() {
237         when(handler.send(any())).thenReturn(false);
238         assertThatIllegalStateException().isThrownBy(() -> oper.publishRequest(new MyRequest()));
239     }
240
241     /**
242      * Tests publishRequest() when the request type is a String.
243      */
244     @Test
245      void testPublishRequestString() {
246         MyStringOperation oper2 = new MyStringOperation(params, config);
247         assertThatCode(() -> oper2.publishRequest(TEXT)).doesNotThrowAnyException();
248     }
249
250     /**
251      * Tests publishRequest() when the coder throws an exception.
252      */
253     @Test
254      void testPublishRequestException() {
255         setOperCoderException();
256         assertThatIllegalArgumentException().isThrownBy(() -> oper.publishRequest(new MyRequest()));
257     }
258
259     /**
260      * Tests processResponse() when it's a success and the response type is a String.
261      */
262     @Test
263      void testProcessResponseSuccessString() {
264         MyStringOperation oper2 = new MyStringOperation(params, config);
265
266         assertSame(outcome, oper2.processResponse(outcome, TEXT, null));
267         assertEquals(OperationResult.SUCCESS, outcome.getResult());
268         assertEquals(TEXT, outcome.getResponse());
269     }
270
271     /**
272      * Tests processResponse() when it's a success and the response type is a
273      * StandardCoderObject.
274      */
275     @Test
276      void testProcessResponseSuccessSco() {
277         MyScoOperation oper2 = new MyScoOperation(params, config);
278
279         assertSame(outcome, oper2.processResponse(outcome, responseText, stdResponse));
280         assertEquals(OperationResult.SUCCESS, outcome.getResult());
281         assertEquals(stdResponse, outcome.getResponse());
282     }
283
284     /**
285      * Tests processResponse() when it's a failure.
286      */
287     @Test
288      void testProcessResponseFailure() throws CoderException {
289         // indicate error in the response
290         MyResponse resp = new MyResponse();
291         resp.setOutput("error");
292
293         responseText = coder.encode(resp);
294         stdResponse = coder.decode(responseText, StandardCoderObject.class);
295
296         assertSame(outcome, oper.processResponse(outcome, responseText, stdResponse));
297         assertEquals(OperationResult.FAILURE, outcome.getResult());
298         assertEquals(resp, outcome.getResponse());
299     }
300
301     /**
302      * Tests processResponse() when the decoder succeeds.
303      */
304     @Test
305      void testProcessResponseDecodeOk() throws CoderException {
306         assertSame(outcome, oper.processResponse(outcome, responseText, stdResponse));
307         assertEquals(OperationResult.SUCCESS, outcome.getResult());
308         assertEquals(response, outcome.getResponse());
309     }
310
311     /**
312      * Tests processResponse() when the decoder throws an exception.
313      */
314     @Test
315      void testProcessResponseDecodeExcept() throws CoderException {
316         assertThatIllegalArgumentException().isThrownBy(
317             () -> oper.processResponse(outcome, "{invalid json", stdResponse));
318     }
319
320     @Test
321      void testPostProcessResponse() {
322         assertThatCode(() -> oper.postProcessResponse(outcome, null, null)).doesNotThrowAnyException();
323     }
324
325     @Test
326      void testGetCoder() {
327         assertNotNull(oper.getCoder());
328     }
329
330     /**
331      * Creates a new {@link #oper} whose coder will throw an exception.
332      */
333     private void setOperCoderException() {
334         oper = new MyOperation(params, config) {
335             @Override
336             protected Coder getCoder() {
337                 return new StandardCoder() {
338                     @Override
339                     public String encode(Object object, boolean pretty) throws CoderException {
340                         throw new CoderException(EXPECTED_EXCEPTION);
341                     }
342                 };
343             }
344         };
345     }
346
347     @Getter
348     @Setter
349     static class MyRequest {
350         private String theRequestId = REQ_ID;
351         private String input;
352     }
353
354     @Getter
355     @Setter
356     @EqualsAndHashCode
357     static class MyResponse {
358         private String requestId;
359         private String output;
360     }
361
362
363     private class MyStringOperation extends BidirectionalTopicOperation<String, String> {
364
365         MyStringOperation(ControlLoopOperationParams params, BidirectionalTopicConfig config) {
366             super(params, config, String.class, Collections.emptyList());
367         }
368
369         @Override
370         protected String makeRequest(int attempt) {
371             return TEXT;
372         }
373
374         @Override
375         protected List<String> getExpectedKeyValues(int attempt, String request) {
376             return Arrays.asList(REQ_ID);
377         }
378
379         @Override
380         protected Status detmStatus(String rawResponse, String response) {
381             return (response != null ? Status.SUCCESS : Status.FAILURE);
382         }
383     }
384
385
386     private class MyScoOperation extends BidirectionalTopicOperation<MyRequest, StandardCoderObject> {
387         MyScoOperation(ControlLoopOperationParams params, BidirectionalTopicConfig config) {
388             super(params, config, StandardCoderObject.class, Collections.emptyList());
389         }
390
391         @Override
392         protected MyRequest makeRequest(int attempt) {
393             return new MyRequest();
394         }
395
396         @Override
397         protected List<String> getExpectedKeyValues(int attempt, MyRequest request) {
398             return Arrays.asList(REQ_ID);
399         }
400
401         @Override
402         protected Status detmStatus(String rawResponse, StandardCoderObject response) {
403             return (response.getString("output") == null ? Status.SUCCESS : Status.FAILURE);
404         }
405     }
406
407
408     private class MyOperation extends BidirectionalTopicOperation<MyRequest, MyResponse> {
409         MyOperation(ControlLoopOperationParams params, BidirectionalTopicConfig config) {
410             super(params, config, MyResponse.class, Collections.emptyList());
411         }
412
413         @Override
414         protected MyRequest makeRequest(int attempt) {
415             return new MyRequest();
416         }
417
418         @Override
419         protected List<String> getExpectedKeyValues(int attempt, MyRequest request) {
420             return Arrays.asList(REQ_ID);
421         }
422
423         @Override
424         protected Status detmStatus(String rawResponse, MyResponse response) {
425             if (--ntimes <= 0) {
426                 return (response.getOutput() == null ? Status.SUCCESS : Status.FAILURE);
427             }
428
429             return Status.STILL_WAITING;
430         }
431     }
432 }