Changes for checkstyle 8.32
[policy/drools-applications.git] / controlloop / common / rules-test / src / test / java / org / onap / policy / controlloop / common / rules / test / RulesTest.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.common.rules.test;
22
23 import static org.assertj.core.api.Assertions.assertThat;
24 import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
25 import static org.junit.Assert.assertEquals;
26 import static org.junit.Assert.assertNotNull;
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.ArgumentMatchers.eq;
31 import static org.mockito.Mockito.doAnswer;
32 import static org.mockito.Mockito.mock;
33 import static org.mockito.Mockito.verify;
34 import static org.mockito.Mockito.when;
35
36 import ch.qos.logback.classic.Logger;
37 import java.io.File;
38 import java.util.LinkedList;
39 import java.util.List;
40 import java.util.Properties;
41 import java.util.concurrent.BlockingQueue;
42 import java.util.concurrent.LinkedBlockingQueue;
43 import java.util.concurrent.TimeUnit;
44 import java.util.stream.Collectors;
45 import org.junit.AfterClass;
46 import org.junit.Before;
47 import org.junit.BeforeClass;
48 import org.junit.Test;
49 import org.kie.api.definition.rule.Rule;
50 import org.kie.api.event.rule.AfterMatchFiredEvent;
51 import org.kie.api.event.rule.AgendaEventListener;
52 import org.kie.api.event.rule.BeforeMatchFiredEvent;
53 import org.kie.api.event.rule.MatchCancelledEvent;
54 import org.kie.api.event.rule.MatchCreatedEvent;
55 import org.kie.api.event.rule.ObjectDeletedEvent;
56 import org.kie.api.event.rule.ObjectInsertedEvent;
57 import org.kie.api.event.rule.ObjectUpdatedEvent;
58 import org.kie.api.event.rule.RuleRuntimeEventListener;
59 import org.kie.api.runtime.KieSession;
60 import org.kie.api.runtime.rule.Match;
61 import org.mockito.Mock;
62 import org.mockito.MockitoAnnotations;
63 import org.onap.policy.common.utils.test.log.logback.ExtractAppender;
64 import org.onap.policy.controlloop.ControlLoopEvent;
65 import org.onap.policy.controlloop.drl.legacy.ControlLoopParams;
66 import org.onap.policy.controlloop.eventmanager.ControlLoopEventManager2;
67 import org.onap.policy.drools.controller.DroolsController;
68 import org.onap.policy.drools.persistence.SystemPersistence;
69 import org.onap.policy.drools.system.PolicyController;
70 import org.onap.policy.drools.system.PolicyControllerFactory;
71 import org.onap.policy.drools.system.PolicyEngine;
72 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
73 import org.slf4j.LoggerFactory;
74
75 public class RulesTest {
76     private static final String EXPECTED_EXCEPTION = "expected exception";
77     private static final String CONTROLLER_NAME = "rulesTest";
78     private static final String POLICY_FILE = "src/test/resources/tosca-policy.json";
79     private static final String MY_POLICY = "operational.restart";
80     private static final String RESOURCE_DIR = "src/test/resources";
81     private static final String MY_RULE_NAME = "my-rule-name";
82     private static final String MY_TEXT = "my text";
83
84     /**
85      * Used to attach an appender to the class' logger.
86      */
87     private static final Logger logger = (Logger) LoggerFactory.getLogger(Rules.class);
88     private static final ExtractAppender appender = new ExtractAppender();
89
90     @Mock
91     private PolicyEngine engine;
92     @Mock
93     private SystemPersistence repo;
94     @Mock
95     private PolicyController controller;
96     @Mock
97     private KieSession kieSession;
98     @Mock
99     private PolicyControllerFactory controllerFactory;
100     @Mock
101     private DroolsController drools;
102
103     private List<Object> facts;
104     private List<RuleRuntimeEventListener> ruleListeners;
105     private List<AgendaEventListener> agendaListeners;
106     private Properties properties;
107     private boolean installed;
108
109     private Rules rules;
110
111     /**
112      * Attaches the appender to the logger.
113      */
114     @BeforeClass
115     public static void setUpBeforeClass() throws Exception {
116         /*
117          * Attach appender to the logger.
118          */
119         appender.setContext(logger.getLoggerContext());
120         appender.start();
121
122         logger.addAppender(appender);
123     }
124
125     /**
126      * Stops the appender.
127      */
128     @AfterClass
129     public static void tearDownAfterClass() {
130         appender.stop();
131     }
132
133     /**
134      * Sets up.
135      */
136     @Before
137     public void setUp() {
138         MockitoAnnotations.initMocks(this);
139
140         facts = new LinkedList<>();
141         ruleListeners = new LinkedList<>();
142         agendaListeners = new LinkedList<>();
143         installed = false;
144         properties = new Properties();
145
146         when(engine.createPolicyController(any(), any())).thenReturn(controller);
147         when(repo.getControllerProperties(CONTROLLER_NAME)).thenReturn(properties);
148         when(controller.getDrools()).thenReturn(drools);
149
150         when(drools.facts(eq(CONTROLLER_NAME), any())).thenAnswer(args -> {
151             Class<?> clazz = args.getArgument(1);
152             return facts.stream().filter(obj -> obj.getClass() == clazz).collect(Collectors.toList());
153         });
154
155         // notify listeners when objects are added to drools
156         when(drools.offer(any())).thenAnswer(args -> {
157             Object object = args.getArgument(0);
158             notifyInserted(object);
159
160             if (!(object instanceof ToscaPolicy)) {
161                 return true;
162             }
163
164             // "insert" Params objects associated with the policy (i.e., mimic the rules)
165             ToscaPolicy policy = (ToscaPolicy) object;
166             ControlLoopParams params = new ControlLoopParams();
167             params.setToscaPolicy(policy);
168             notifyInserted(params);
169
170             return true;
171         });
172
173         when(drools.delete(any())).thenAnswer(args -> {
174             Class<?> clazz = args.getArgument(0);
175             facts.removeIf(obj -> obj.getClass() == clazz);
176             return null;
177         });
178
179         // handle rule listener registration and deregistration with the kieSession
180         doAnswer(args -> {
181             ruleListeners.add(args.getArgument(0));
182             return null;
183         }).when(kieSession).addEventListener(any(RuleRuntimeEventListener.class));
184
185         doAnswer(args -> {
186             ruleListeners.remove(args.getArgument(0));
187             return null;
188         }).when(kieSession).removeEventListener(any(RuleRuntimeEventListener.class));
189
190         // handle agenda listener registration and deregistration with the kieSession
191         doAnswer(args -> {
192             agendaListeners.add(args.getArgument(0));
193             return null;
194         }).when(kieSession).addEventListener(any(AgendaEventListener.class));
195
196         doAnswer(args -> {
197             agendaListeners.remove(args.getArgument(0));
198             return null;
199         }).when(kieSession).removeEventListener(any(AgendaEventListener.class));
200
201         rules = new MyRules();
202
203         rules.configure(RESOURCE_DIR);
204         rules.start();
205     }
206
207     @Test
208     public void testRules() {
209         assertEquals(CONTROLLER_NAME, rules.getControllerName());
210
211         assertSame(engine, rules.getPdpd());
212         assertSame(repo, rules.getPdpdRepo());
213         assertSame(controller, rules.getController());
214     }
215
216     @Test
217     public void testStart() throws Exception {
218         verify(repo).setConfigurationDir("src/test/resources/config");
219         assertTrue(installed);
220         verify(engine).configure(any(Properties.class));
221         verify(engine).createPolicyController(CONTROLLER_NAME, properties);
222         verify(engine).start();
223
224         verify(kieSession).addEventListener(any(RuleRuntimeEventListener.class));
225         verify(kieSession).addEventListener(any(AgendaEventListener.class));
226     }
227
228     @Test
229     public void testDestroy() {
230         rules.destroy();
231
232         verify(controllerFactory).shutdown(CONTROLLER_NAME);
233         verify(engine).stop();
234     }
235
236     @Test
237     public void testResetFacts() {
238         rules.resetFacts();
239
240         verify(drools).delete(ToscaPolicy.class);
241         verify(drools).delete(ControlLoopParams.class);
242         verify(drools).delete(ControlLoopEventManager2.class);
243         verify(drools).delete(ControlLoopEvent.class);
244     }
245
246     @Test
247     public void testSetupPolicyFromTemplate_testGetPolicyFromTemplate() throws InterruptedException {
248         rules.setupPolicyFromTemplate("tosca-template.json", MY_POLICY);
249
250         assertThatIllegalArgumentException()
251                         .isThrownBy(() -> rules.setupPolicyFromTemplate("missing-file.json", "a-policy"));
252
253         // check interrupt case
254         checkInterrupt(() -> rules.setupPolicyFromTemplate("tosca-template.json", MY_POLICY),
255                         "policy operational.restart");
256     }
257
258     @Test
259     public void testSetupPolicyFromFile_testGetPolicyFromFile_testSetupPolicy() throws InterruptedException {
260         assertNotNull(rules.setupPolicyFromFile(POLICY_FILE));
261
262         assertThatIllegalArgumentException().isThrownBy(() -> rules.setupPolicyFromFile("missing-file.json"));
263
264         // check interrupt case
265         checkInterrupt(() -> rules.setupPolicyFromFile(POLICY_FILE), "policy " + POLICY_FILE);
266     }
267
268     @Test
269     public void testRuleListenerLogger() {
270         Rule rule = mock(Rule.class);
271         when(rule.getName()).thenReturn(MY_RULE_NAME);
272
273         // insertions - with and without rule name
274         ObjectInsertedEvent insert = mock(ObjectInsertedEvent.class);
275         when(insert.getObject()).thenReturn(MY_TEXT);
276         checkLogging("inserted", () -> ruleListeners.forEach(listener -> listener.objectInserted(insert)));
277         when(insert.getRule()).thenReturn(rule);
278         checkLogging("inserted", () -> ruleListeners.forEach(listener -> listener.objectInserted(insert)));
279
280         // updates - with and without rule name
281         ObjectUpdatedEvent update = mock(ObjectUpdatedEvent.class);
282         when(update.getObject()).thenReturn(MY_TEXT);
283         checkLogging("updated", () -> ruleListeners.forEach(listener -> listener.objectUpdated(update)));
284         when(update.getRule()).thenReturn(rule);
285         checkLogging("updated", () -> ruleListeners.forEach(listener -> listener.objectUpdated(update)));
286
287         // deletions - with and without rule name
288         ObjectDeletedEvent delete = mock(ObjectDeletedEvent.class);
289         when(delete.getOldObject()).thenReturn(MY_TEXT);
290         checkLogging("deleted", () -> ruleListeners.forEach(listener -> listener.objectDeleted(delete)));
291         when(delete.getRule()).thenReturn(rule);
292         checkLogging("deleted", () -> ruleListeners.forEach(listener -> listener.objectDeleted(delete)));
293     }
294
295     @Test
296     public void testAgendaListenerLogger() {
297         Rule rule = mock(Rule.class);
298         when(rule.getName()).thenReturn(MY_RULE_NAME);
299
300         Match match = mock(Match.class);
301         when(match.getRule()).thenReturn(rule);
302
303         // create
304         MatchCreatedEvent create = mock(MatchCreatedEvent.class);
305         when(create.getMatch()).thenReturn(match);
306         checkLogging("match created", () -> agendaListeners.forEach(listener -> listener.matchCreated(create)));
307
308         // cancel
309         MatchCancelledEvent cancel = mock(MatchCancelledEvent.class);
310         when(cancel.getMatch()).thenReturn(match);
311         checkLogging("match cancelled", () -> agendaListeners.forEach(listener -> listener.matchCancelled(cancel)));
312
313         // before-fire
314         BeforeMatchFiredEvent before = mock(BeforeMatchFiredEvent.class);
315         when(before.getMatch()).thenReturn(match);
316         // @formatter:off
317         checkLogging("before match fired",
318             () -> agendaListeners.forEach(listener -> listener.beforeMatchFired(before)));
319         // @formatter:on
320
321         // after-fire
322         AfterMatchFiredEvent after = mock(AfterMatchFiredEvent.class);
323         when(after.getMatch()).thenReturn(match);
324         checkLogging("after match fired", () -> agendaListeners.forEach(listener -> listener.afterMatchFired(after)));
325     }
326
327     @Test
328     public void testMakePdpd_testMakePdpdRepo() {
329         // need rules that makes real objects
330         rules = new Rules(CONTROLLER_NAME);
331
332         assertNotNull(rules.getPdpd());
333         assertNotNull(rules.getPdpdRepo());
334     }
335
336     private void checkInterrupt(Runnable command, String expectedMsg) throws InterruptedException {
337         rules = new MyRules() {
338             @Override
339             protected ToscaPolicy setupPolicy(ToscaPolicy policy) throws InterruptedException {
340                 throw new InterruptedException(EXPECTED_EXCEPTION);
341             }
342         };
343         rules.configure(RESOURCE_DIR);
344         rules.start();
345
346         BlockingQueue<IllegalArgumentException> exceptions = new LinkedBlockingQueue<>();
347
348         Thread thread = new Thread(() -> {
349             try {
350                 command.run();
351             } catch (IllegalArgumentException e) {
352                 exceptions.add(e);
353             }
354         });
355
356         thread.setDaemon(true);
357         thread.start();
358
359         assertThat(exceptions.poll(10, TimeUnit.SECONDS)).isNotNull().hasMessage(expectedMsg)
360                         .hasCauseInstanceOf(InterruptedException.class);
361     }
362
363     private void checkLogging(String expectedMsg, Runnable command) {
364         appender.clearExtractions();
365         command.run();
366         List<String> messages = appender.getExtracted();
367         assertEquals(1, messages.size());
368         assertThat(messages.get(0)).contains(expectedMsg);
369     }
370
371     protected void notifyInserted(Object object) {
372         // add it to our list
373         facts.add(object);
374
375         // increase code coverage by adding random objects
376         ObjectInsertedEvent event0 = mock(ObjectInsertedEvent.class);
377         when(event0.getObject()).thenReturn(new Object());
378         ruleListeners.forEach(listener -> listener.objectInserted(event0));
379
380         // increase code coverage by adding a random object
381         ObjectInsertedEvent event = mock(ObjectInsertedEvent.class);
382         when(event.getObject()).thenReturn(object);
383
384         if (object instanceof ToscaPolicy) {
385             // increase code coverage by associating it with a random rule
386             Rule rule = mock(Rule.class);
387             when(rule.getName()).thenReturn(MY_RULE_NAME);
388             when(event.getRule()).thenReturn(rule);
389         }
390
391         ruleListeners.forEach(listener -> listener.objectInserted(event));
392     }
393
394     private class MyRules extends Rules {
395         public MyRules() {
396             super(CONTROLLER_NAME);
397         }
398
399         @Override
400         protected PolicyEngine makeEngine() {
401             return engine;
402         }
403
404         @Override
405         protected SystemPersistence makePdpdRepo() {
406             return repo;
407         }
408
409         @Override
410         protected KieSession getKieSession() {
411             return kieSession;
412         }
413
414         @Override
415         protected PolicyControllerFactory getControllerFactory() {
416             return controllerFactory;
417         }
418
419         @Override
420         protected void installArtifact(File kmoduleFile, File pomFile, String resourceDir, List<File> ruleFiles) {
421             installed = true;
422         }
423     }
424 }