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