Clean up and enhancement of Actor re-design
[policy/models.git] / models-interactions / model-actors / actorServiceProvider / src / test / java / org / onap / policy / controlloop / actorserviceprovider / impl / ActorImplTest.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.assertThatIllegalArgumentException;
24 import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
25 import static org.junit.Assert.assertEquals;
26 import static org.junit.Assert.assertFalse;
27 import static org.junit.Assert.assertSame;
28 import static org.junit.Assert.assertTrue;
29 import static org.mockito.ArgumentMatchers.any;
30 import static org.mockito.Mockito.doThrow;
31 import static org.mockito.Mockito.never;
32 import static org.mockito.Mockito.spy;
33 import static org.mockito.Mockito.times;
34 import static org.mockito.Mockito.verify;
35 import static org.mockito.Mockito.when;
36
37 import java.util.Iterator;
38 import java.util.Map;
39 import java.util.TreeMap;
40 import java.util.stream.Collectors;
41 import org.junit.Before;
42 import org.junit.Test;
43 import org.onap.policy.common.parameters.ObjectValidationResult;
44 import org.onap.policy.common.parameters.ValidationStatus;
45 import org.onap.policy.controlloop.actorserviceprovider.Operator;
46 import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException;
47
48 public class ActorImplTest {
49     private static final String EXPECTED_EXCEPTION = "expected exception";
50     private static final String ACTOR_NAME = "my-actor";
51     private static final String OPER1 = "add";
52     private static final String OPER2 = "subtract";
53     private static final String OPER3 = "multiply";
54     private static final String OPER4 = "divide";
55
56     private MyOper oper1;
57     private MyOper oper2;
58     private MyOper oper3;
59     private MyOper oper4;
60
61     private Map<String, Object> sub1;
62     private Map<String, Object> sub2;
63     private Map<String, Object> sub3;
64     private Map<String, Object> sub4;
65     private Map<String, Object> params;
66
67     private ActorImpl actor;
68
69
70     /**
71      * Initializes the fields, including a fully populated {@link #actor}.
72      */
73     @Before
74     public void setUp() {
75         oper1 = spy(new MyOper(OPER1));
76         oper2 = spy(new MyOper(OPER2));
77         oper3 = spy(new MyOper(OPER3));
78         oper4 = spy(new MyOper(OPER4));
79
80         sub1 = Map.of("sub A", "value A");
81         sub2 = Map.of("sub B", "value B");
82         sub3 = Map.of("sub C", "value C");
83         sub4 = Map.of("sub D", "value D");
84
85         params = Map.of(OPER1, sub1, OPER2, sub2, OPER3, sub3, OPER4, sub4);
86
87         actor = makeActor(oper1, oper2, oper3, oper4);
88     }
89
90     @Test
91     public void testActorImpl_testGetName() {
92         assertEquals(ACTOR_NAME, actor.getName());
93         assertEquals(4, actor.getOperationNames().size());
94     }
95
96     @Test
97     public void testDoStart() {
98         actor.configure(params);
99         assertEquals(4, actor.getOperationNames().size());
100
101         /*
102          * arrange for second operator to be unconfigured and the third operator to throw
103          * an exception
104          */
105         Iterator<Operator> iter = actor.getOperators().iterator();
106         iter.next();
107         when(iter.next().isConfigured()).thenReturn(false);
108         when(iter.next().start()).thenThrow(new IllegalStateException(EXPECTED_EXCEPTION));
109
110         /*
111          * Start the actor.
112          */
113         actor.start();
114         assertTrue(actor.isAlive());
115
116         iter = actor.getOperators().iterator();
117         verify(iter.next()).start();
118         // this one isn't configured, so shouldn't attempt to start it
119         verify(iter.next(), never()).start();
120         // this one threw an exception
121         iter.next();
122         verify(iter.next()).start();
123
124         // no other types of operations
125         verify(oper1, never()).stop();
126         verify(oper1, never()).shutdown();
127     }
128
129     @Test
130     public void testDoStop() {
131         actor.configure(params);
132         actor.start();
133         assertEquals(4, actor.getOperationNames().size());
134
135         // arrange for second operator to throw an exception
136         Iterator<Operator> iter = actor.getOperators().iterator();
137         iter.next();
138         when(iter.next().stop()).thenThrow(new IllegalStateException(EXPECTED_EXCEPTION));
139
140         /*
141          * Stop the actor.
142          */
143         actor.stop();
144         assertFalse(actor.isAlive());
145
146         iter = actor.getOperators().iterator();
147         verify(iter.next()).stop();
148         // this one threw an exception
149         iter.next();
150         verify(iter.next()).stop();
151         verify(iter.next()).stop();
152
153         // no additional types of operations
154         verify(oper1).configure(any());
155         verify(oper1).start();
156
157         // no other types of operation
158         verify(oper1, never()).shutdown();
159     }
160
161     @Test
162     public void testDoShutdown() {
163         actor.configure(params);
164         actor.start();
165         assertEquals(4, actor.getOperationNames().size());
166
167         // arrange for second operator to throw an exception
168         Iterator<Operator> iter = actor.getOperators().iterator();
169         iter.next();
170         doThrow(new IllegalStateException(EXPECTED_EXCEPTION)).when(iter.next()).shutdown();
171
172         /*
173          * Stop the actor.
174          */
175         actor.shutdown();
176         assertFalse(actor.isAlive());
177
178         iter = actor.getOperators().iterator();
179         verify(iter.next()).shutdown();
180         // this one threw an exception
181         iter.next();
182         verify(iter.next()).shutdown();
183         verify(iter.next()).shutdown();
184
185         // no additional types of operations
186         verify(oper1).configure(any());
187         verify(oper1).start();
188
189         // no other types of operation
190         verify(oper1, never()).stop();
191     }
192
193     @Test
194     public void testAddOperator() {
195         // cannot add operators if already configured
196         actor.configure(params);
197         assertThatIllegalStateException().isThrownBy(() -> actor.addOperator(oper1));
198
199         /*
200          * make an actor where operators two and four have names that are duplicates of
201          * the others
202          */
203         oper2 = spy(new MyOper(OPER1));
204         oper4 = spy(new MyOper(OPER3));
205
206         actor = makeActor(oper1, oper2, oper3, oper4);
207
208         assertEquals(2, actor.getOperationNames().size());
209
210         assertSame(oper1, actor.getOperator(OPER1));
211         assertSame(oper3, actor.getOperator(OPER3));
212     }
213
214     @Test
215     public void testGetOperator() {
216         assertSame(oper1, actor.getOperator(OPER1));
217         assertSame(oper3, actor.getOperator(OPER3));
218
219         assertThatIllegalArgumentException().isThrownBy(() -> actor.getOperator("unknown name"));
220     }
221
222     @Test
223     public void testGetOperators() {
224         // @formatter:off
225         assertEquals("[add, divide, multiply, subtract]",
226                         actor.getOperators().stream()
227                             .map(Operator::getName)
228                             .sorted()
229                             .collect(Collectors.toList())
230                             .toString());
231         // @formatter:on
232     }
233
234     @Test
235     public void testGetOperationNames() {
236         // @formatter:off
237         assertEquals("[add, divide, multiply, subtract]",
238                         actor.getOperationNames().stream()
239                             .sorted()
240                             .collect(Collectors.toList())
241                             .toString());
242         // @formatter:on
243     }
244
245     @Test
246     public void testDoConfigure() {
247         actor.configure(params);
248         assertTrue(actor.isConfigured());
249
250         verify(oper1).configure(sub1);
251         verify(oper2).configure(sub2);
252         verify(oper3).configure(sub3);
253         verify(oper4).configure(sub4);
254
255         // no other types of operations
256         verify(oper1, never()).start();
257         verify(oper1, never()).stop();
258         verify(oper1, never()).shutdown();
259     }
260
261     /**
262      * Tests doConfigure() where operators throw parameter validation and runtime
263      * exceptions.
264      */
265     @Test
266     public void testDoConfigureExceptions() {
267         makeValidException(oper1);
268         makeRuntimeException(oper2);
269         makeValidException(oper3);
270
271         actor.configure(params);
272         assertTrue(actor.isConfigured());
273     }
274
275     /**
276      * Tests doConfigure(). Arranges for the following:
277      * <ul>
278      * <li>one operator is configured, but has parameters</li>
279      * <li>another operator is configured, but has no parameters</li>
280      * <li>another operator has no parameters and is not configured</li>
281      * </ul>
282      */
283     @Test
284     public void testDoConfigureConfigure() {
285         // need mutable parameters
286         params = new TreeMap<>(params);
287
288         // configure one operator
289         oper1.configure(sub1);
290
291         // configure another and remove its parameters
292         oper2.configure(sub2);
293         params.remove(OPER2);
294
295         // create a new, unconfigured actor
296         Operator oper5 = spy(new MyOper("UNCONFIGURED"));
297         actor = makeActor(oper1, oper2, oper3, oper4, oper5);
298
299         /*
300          * Configure it.
301          */
302         actor.configure(params);
303         assertTrue(actor.isConfigured());
304
305         // this should have been configured again
306         verify(oper1, times(2)).configure(sub1);
307
308         // no parameters, so this should not have been configured again
309         verify(oper2).configure(sub2);
310
311         // these were only configured once
312         verify(oper3).configure(sub3);
313         verify(oper4).configure(sub4);
314
315         // never configured
316         verify(oper5, never()).configure(any());
317         assertFalse(oper5.isConfigured());
318
319         // start and verify that all are started except for the last
320         actor.start();
321         verify(oper1).start();
322         verify(oper2).start();
323         verify(oper3).start();
324         verify(oper4).start();
325         verify(oper5, never()).start();
326     }
327
328     /**
329      * Arranges for an operator to throw a validation exception when
330      * {@link Operator#configure(Map)} is invoked.
331      *
332      * @param oper operator of interest
333      */
334     private void makeValidException(Operator oper) {
335         ParameterValidationRuntimeException ex = new ParameterValidationRuntimeException(
336                         new ObjectValidationResult(actor.getName(), null, ValidationStatus.INVALID, "null"));
337         doThrow(ex).when(oper).configure(any());
338     }
339
340     /**
341      * Arranges for an operator to throw a runtime exception when
342      * {@link Operator#configure(Map)} is invoked.
343      *
344      * @param oper operator of interest
345      */
346     private void makeRuntimeException(Operator oper) {
347         IllegalStateException ex = new IllegalStateException(EXPECTED_EXCEPTION);
348         doThrow(ex).when(oper).configure(any());
349     }
350
351     @Test
352     public void testMakeOperatorParameters() {
353         actor.configure(params);
354
355         // each operator should have received its own parameters
356         verify(oper1).configure(sub1);
357         verify(oper2).configure(sub2);
358         verify(oper3).configure(sub3);
359         verify(oper4).configure(sub4);
360     }
361
362     /**
363      * Makes an actor with the given operators.
364      *
365      * @param operators associated operators
366      * @return a new actor
367      */
368     private ActorImpl makeActor(Operator... operators) {
369         ActorImpl actor = new ActorImpl(ACTOR_NAME);
370
371         for (Operator oper : operators) {
372             actor.addOperator(oper);
373         }
374
375         return actor;
376     }
377
378     private static class MyOper extends OperatorPartial implements Operator {
379
380         public MyOper(String name) {
381             super(ACTOR_NAME, name);
382         }
383     }
384 }