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