Removing deprecated DMAAP library
[policy/drools-pdp.git] / policy-management / src / test / java / org / onap / policy / drools / controller / internal / MavenDroolsController2Test.java
1 /*
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2019-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.drools.controller.internal;
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.Mockito.doThrow;
34 import static org.mockito.Mockito.lenient;
35 import static org.mockito.Mockito.mock;
36 import static org.mockito.Mockito.never;
37 import static org.mockito.Mockito.times;
38 import static org.mockito.Mockito.verify;
39 import static org.mockito.Mockito.when;
40
41 import java.util.Collections;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.TreeMap;
45 import org.junit.jupiter.api.AfterEach;
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.kie.api.KieBase;
50 import org.kie.api.definition.KiePackage;
51 import org.kie.api.definition.rule.Query;
52 import org.kie.api.runtime.KieContainer;
53 import org.kie.api.runtime.KieSession;
54 import org.kie.api.runtime.rule.FactHandle;
55 import org.kie.api.runtime.rule.QueryResults;
56 import org.kie.api.runtime.rule.QueryResultsRow;
57 import org.mockito.ArgumentCaptor;
58 import org.mockito.Mock;
59 import org.mockito.MockitoAnnotations;
60 import org.mockito.junit.jupiter.MockitoExtension;
61 import org.onap.policy.common.endpoints.event.comm.TopicSink;
62 import org.onap.policy.common.utils.services.OrderedServiceImpl;
63 import org.onap.policy.drools.core.PolicyContainer;
64 import org.onap.policy.drools.core.PolicySession;
65 import org.onap.policy.drools.features.DroolsControllerFeatureApi;
66 import org.onap.policy.drools.features.DroolsControllerFeatureApiConstants;
67 import org.onap.policy.drools.protocol.coders.EventProtocolCoder;
68 import org.onap.policy.drools.protocol.coders.EventProtocolCoderConstants;
69 import org.onap.policy.drools.protocol.coders.EventProtocolParams;
70 import org.onap.policy.drools.protocol.coders.JsonProtocolFilter;
71 import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration;
72 import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration.CustomGsonCoder;
73 import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration.PotentialCoderFilter;
74
75 @ExtendWith(MockitoExtension.class)
76 class MavenDroolsController2Test {
77     private static final int FACT1_OBJECT = 1000;
78     private static final int FACT3_OBJECT = 1001;
79
80     private static final long FACT_COUNT = 200L;
81
82     private static final String EXPECTED_EXCEPTION = "expected exception";
83     private static final RuntimeException RUNTIME_EX = new RuntimeException(EXPECTED_EXCEPTION);
84     private static final IllegalArgumentException ARG_EX = new IllegalArgumentException(EXPECTED_EXCEPTION);
85
86     private static final String UNKNOWN_CLASS = "unknown class";
87
88     private static final String GROUP = "my-group";
89     private static final String ARTIFACT = "my-artifact";
90     private static final String VERSION = "my-version";
91
92     private static final String GROUP2 = "my-groupB";
93     private static final String ARTIFACT2 = "my-artifactB";
94     private static final String VERSION2 = "my-versionB";
95
96     private static final String TOPIC = "my-topic";
97     private static final String TOPIC2 = "my-topic";
98
99     private static final ClassLoader CLASS_LOADER = MavenDroolsController2Test.class.getClassLoader();
100     private static final int CLASS_LOADER_HASHCODE = CLASS_LOADER.hashCode();
101
102     private static final String SESSION1 = "session-A";
103     private static final String SESSION2 = "session-B";
104     private static final String FULL_SESSION1 = "full-A";
105     private static final String FULL_SESSION2 = "full-B";
106
107     private static final String EVENT_TEXT = "my-event-text";
108     private static final Object EVENT = new Object();
109
110     private static final String QUERY = "my-query";
111     private static final String QUERY2 = "my-query-B";
112     private static final String ENTITY = "my-entity";
113     private static final Object PARM1 = "parmA";
114     private static final Object PARM2 = "parmB";
115
116     @Mock
117     private EventProtocolCoder coderMgr;
118     @Mock
119     private DroolsControllerFeatureApi prov1;
120     @Mock
121     private DroolsControllerFeatureApi prov2;
122     @Mock
123     private OrderedServiceImpl<DroolsControllerFeatureApi> droolsProviders;
124     @Mock
125     private TopicCoderFilterConfiguration decoder1;
126     @Mock
127     private TopicCoderFilterConfiguration decoder2;
128     @Mock
129     private TopicCoderFilterConfiguration encoder1;
130     @Mock
131     private TopicCoderFilterConfiguration encoder2;
132     @Mock
133     private PolicyContainer container;
134     @Mock
135     private CustomGsonCoder gson1;
136     @Mock
137     private CustomGsonCoder gson2;
138     @Mock
139     private PotentialCoderFilter filter1a;
140     @Mock
141     private JsonProtocolFilter jsonFilter1a;
142     @Mock
143     private PotentialCoderFilter filter1b;
144     @Mock
145     private JsonProtocolFilter jsonFilter1b;
146     @Mock
147     private PotentialCoderFilter filter2;
148     @Mock
149     private JsonProtocolFilter jsonFilter2;
150     @Mock
151     private PolicySession sess1;
152     @Mock
153     private PolicySession sess2;
154     @Mock
155     private KieSession kieSess;
156     @Mock
157     private KieSession kieSess2;
158     @Mock
159     private TopicSink sink;
160     @Mock
161     private FactHandle fact1;
162     @Mock
163     private FactHandle fact2;
164     @Mock
165     private FactHandle fact3;
166     @Mock
167     private FactHandle factex;
168     @Mock
169     private KieBase kieBase;
170     @Mock
171     private KiePackage pkg1;
172     @Mock
173     private KiePackage pkg2;
174     @Mock
175     private Query query1;
176     @Mock
177     private Query query2;
178     @Mock
179     private Query query3;
180     @Mock
181     private QueryResults queryResults;
182     @Mock
183     private QueryResultsRow row1;
184     @Mock
185     private QueryResultsRow row2;
186
187     private List<TopicCoderFilterConfiguration> decoders;
188     private List<TopicCoderFilterConfiguration> encoders;
189
190     private MavenDroolsController drools;
191
192     AutoCloseable autoCloseable;
193
194     /**
195      * Initializes objects, including the drools controller.
196      */
197     @BeforeEach
198     public void setUp() {
199         autoCloseable = MockitoAnnotations.openMocks(this);
200         lenient().when(droolsProviders.getList()).thenReturn(List.of(prov1, prov2));
201
202         lenient().when(coderMgr.isDecodingSupported(GROUP, ARTIFACT, TOPIC)).thenReturn(true);
203         lenient().when(coderMgr.decode(GROUP, ARTIFACT, TOPIC, EVENT_TEXT)).thenReturn(EVENT);
204
205         lenient().when(kieSess.getFactCount()).thenReturn(FACT_COUNT);
206         lenient().when(kieSess.getFactHandles()).thenReturn(List.of(fact1, fact2, factex, fact3));
207         lenient().when(kieSess.getFactHandles(any())).thenReturn(List.of(fact1, fact3));
208         lenient().when(kieSess.getKieBase()).thenReturn(kieBase);
209         lenient().when(kieSess.getQueryResults(QUERY, PARM1, PARM2)).thenReturn(queryResults);
210
211         lenient().when(kieSess.getFactHandle(FACT3_OBJECT)).thenReturn(fact3);
212
213         lenient().when(kieSess.getObject(fact1)).thenReturn(FACT1_OBJECT);
214         lenient().when(kieSess.getObject(fact2)).thenReturn("");
215         lenient().when(kieSess.getObject(fact3)).thenReturn(FACT3_OBJECT);
216         lenient().when(kieSess.getObject(factex)).thenThrow(RUNTIME_EX);
217
218         lenient().when(kieBase.getKiePackages()).thenReturn(List.of(pkg1, pkg2));
219
220         lenient().when(pkg1.getQueries()).thenReturn(List.of(query3));
221         lenient().when(pkg2.getQueries()).thenReturn(List.of(query2, query1));
222
223         lenient().when(query1.getName()).thenReturn(QUERY);
224         lenient().when(query2.getName()).thenReturn(QUERY2);
225
226         lenient().when(queryResults.iterator()).thenReturn(List.of(row1, row2).iterator());
227
228         lenient().when(row1.get(ENTITY)).thenReturn(FACT1_OBJECT);
229         lenient().when(row2.get(ENTITY)).thenReturn(FACT3_OBJECT);
230
231         lenient().when(row1.getFactHandle(ENTITY)).thenReturn(fact1);
232         lenient().when(row2.getFactHandle(ENTITY)).thenReturn(fact3);
233
234         lenient().when(sess1.getKieSession()).thenReturn(kieSess);
235         lenient().when(sess2.getKieSession()).thenReturn(kieSess2);
236
237         lenient().when(sess1.getName()).thenReturn(SESSION1);
238         lenient().when(sess2.getName()).thenReturn(SESSION2);
239
240         lenient().when(sess1.getFullName()).thenReturn(FULL_SESSION1);
241         lenient().when(sess2.getFullName()).thenReturn(FULL_SESSION2);
242
243         lenient().when(container.getClassLoader()).thenReturn(CLASS_LOADER);
244         lenient().when(container.getPolicySessions()).thenReturn(List.of(sess1, sess2));
245         lenient().when(container.insertAll(EVENT)).thenReturn(true);
246
247         lenient().when(decoder1.getTopic()).thenReturn(TOPIC);
248         lenient().when(decoder2.getTopic()).thenReturn(TOPIC2);
249
250         lenient().when(encoder1.getTopic()).thenReturn(TOPIC);
251         lenient().when(encoder2.getTopic()).thenReturn(TOPIC2);
252
253         decoders = List.of(decoder1, decoder2);
254         encoders = List.of(encoder1, encoder2);
255
256         lenient().when(decoder1.getCustomGsonCoder()).thenReturn(gson1);
257         lenient().when(encoder2.getCustomGsonCoder()).thenReturn(gson2);
258
259         lenient().when(filter1a.getCodedClass()).thenReturn(Object.class.getName());
260         lenient().when(filter1a.getFilter()).thenReturn(jsonFilter1a);
261
262         lenient().when(filter1b.getCodedClass()).thenReturn(String.class.getName());
263         lenient().when(filter1b.getFilter()).thenReturn(jsonFilter1b);
264
265         lenient().when(filter2.getCodedClass()).thenReturn(Integer.class.getName());
266         lenient().when(filter2.getFilter()).thenReturn(jsonFilter2);
267
268         lenient().when(decoder1.getCoderFilters()).thenReturn(List.of(filter1a, filter1b));
269         lenient().when(decoder2.getCoderFilters()).thenReturn(Collections.emptyList());
270
271         lenient().when(encoder1.getCoderFilters()).thenReturn(Collections.emptyList());
272         lenient().when(encoder2.getCoderFilters()).thenReturn(List.of(filter2));
273
274         lenient().when(sink.getTopic()).thenReturn(TOPIC);
275         lenient().when(sink.send(EVENT_TEXT)).thenReturn(true);
276
277         drools = new MyDrools(GROUP, ARTIFACT, VERSION, null, null);
278
279         lenient().when(coderMgr.encode(TOPIC, EVENT, drools)).thenReturn(EVENT_TEXT);
280     }
281
282     @AfterEach
283     void closeMocks() throws Exception {
284         autoCloseable.close();
285     }
286
287     @Test
288     void testMavenDroolsController_InvalidArgs() {
289         assertThatIllegalArgumentException().isThrownBy(() -> new MyDrools(null, ARTIFACT, VERSION, null, null))
290             .withMessageContaining("group");
291         assertThatIllegalArgumentException().isThrownBy(() -> new MyDrools("", ARTIFACT, VERSION, null, null))
292             .withMessageContaining("group");
293
294         assertThatIllegalArgumentException().isThrownBy(() -> new MyDrools(GROUP, null, VERSION, null, null))
295             .withMessageContaining("artifact");
296         assertThatIllegalArgumentException().isThrownBy(() -> new MyDrools(GROUP, "", VERSION, null, null))
297             .withMessageContaining("artifact");
298
299         assertThatIllegalArgumentException().isThrownBy(() -> new MyDrools(GROUP, ARTIFACT, null, null, null))
300             .withMessageContaining("version");
301         assertThatIllegalArgumentException().isThrownBy(() -> new MyDrools(GROUP, ARTIFACT, "", null, null))
302             .withMessageContaining("version");
303     }
304
305     @Test
306     void testUpdateToVersion() {
307         // add coders
308         drools.updateToVersion(GROUP, ARTIFACT, VERSION2, decoders, encoders);
309
310         verify(container).updateToVersion(VERSION2);
311
312         // nothing removed the first time
313         verify(coderMgr, never()).removeDecoders(GROUP, ARTIFACT, TOPIC2);
314         verify(coderMgr, never()).removeEncoders(GROUP, ARTIFACT, TOPIC);
315
316         verify(coderMgr, times(2)).addDecoder(any());
317         verify(coderMgr, times(1)).addEncoder(any());
318
319         // remove coders
320         when(container.getVersion()).thenReturn(VERSION2);
321         drools.updateToVersion(GROUP, ARTIFACT, VERSION, null, null);
322
323         verify(container).updateToVersion(VERSION);
324
325         verify(coderMgr, times(2)).removeDecoders(GROUP, ARTIFACT, TOPIC2);
326         verify(coderMgr, times(2)).removeEncoders(GROUP, ARTIFACT, TOPIC);
327
328         // not added again
329         verify(coderMgr, times(2)).addDecoder(any());
330         verify(coderMgr, times(1)).addEncoder(any());
331     }
332
333     @Test
334     void testUpdateToVersion_Unchanged() {
335         drools.updateToVersion(GROUP, ARTIFACT, VERSION, decoders, encoders);
336
337         verify(coderMgr, never()).addDecoder(any());
338         verify(coderMgr, never()).addEncoder(any());
339     }
340
341     @Test
342     void testUpdateToVersion_InvalidArgs() {
343         assertThatIllegalArgumentException()
344             .isThrownBy(() -> drools.updateToVersion(null, ARTIFACT, VERSION, null, null))
345             .withMessageContaining("group");
346         assertThatIllegalArgumentException().isThrownBy(() -> drools.updateToVersion("", ARTIFACT, VERSION, null, null))
347             .withMessageContaining("group");
348
349         assertThatIllegalArgumentException().isThrownBy(() -> drools.updateToVersion(GROUP, null, VERSION, null, null))
350             .withMessageContaining("artifact");
351         assertThatIllegalArgumentException().isThrownBy(() -> drools.updateToVersion(GROUP, "", VERSION, null, null))
352             .withMessageContaining("artifact");
353
354         assertThatIllegalArgumentException().isThrownBy(() -> drools.updateToVersion(GROUP, ARTIFACT, null, null, null))
355             .withMessageContaining("version");
356         assertThatIllegalArgumentException().isThrownBy(() -> drools.updateToVersion(GROUP, ARTIFACT, "", null, null))
357             .withMessageContaining("version");
358
359         assertThatIllegalArgumentException()
360             .isThrownBy(() -> drools.updateToVersion("no-group-id", ARTIFACT, VERSION, null, null))
361             .withMessageContaining("BRAINLESS");
362
363         assertThatIllegalArgumentException()
364             .isThrownBy(() -> drools.updateToVersion(GROUP, "no-artifact-id", VERSION, null, null))
365             .withMessageContaining("BRAINLESS");
366
367         assertThatIllegalArgumentException()
368             .isThrownBy(() -> drools.updateToVersion(GROUP, ARTIFACT, "no-version", null, null))
369             .withMessageContaining("BRAINLESS");
370
371         assertThatIllegalArgumentException()
372             .isThrownBy(() -> drools.updateToVersion(GROUP2, ARTIFACT, VERSION, null, null))
373             .withMessageContaining("coordinates must be identical");
374
375         assertThatIllegalArgumentException()
376             .isThrownBy(() -> drools.updateToVersion(GROUP, ARTIFACT2, VERSION, null, null))
377             .withMessageContaining("coordinates must be identical");
378     }
379
380     @Test
381     void testInitCoders_NullCoders() {
382         // already constructed with null coders
383         verify(coderMgr, never()).addDecoder(any());
384         verify(coderMgr, never()).addEncoder(any());
385     }
386
387     @Test
388     void testInitCoders_NullOrEmptyFilters() {
389         when(decoder1.getCoderFilters()).thenReturn(Collections.emptyList());
390         when(decoder2.getCoderFilters()).thenReturn(null);
391
392         when(encoder1.getCoderFilters()).thenReturn(null);
393         when(encoder2.getCoderFilters()).thenReturn(Collections.emptyList());
394
395         drools = new MyDrools(GROUP, ARTIFACT, VERSION, decoders, encoders);
396
397         verify(coderMgr, never()).addDecoder(any());
398         verify(coderMgr, never()).addEncoder(any());
399     }
400
401     @Test
402     void testInitCoders_GsonClass() {
403         when(gson1.getClassContainer()).thenReturn("");
404         when(gson2.getClassContainer()).thenReturn(Long.class.getName());
405
406         drools = new MyDrools(GROUP, ARTIFACT, VERSION, decoders, encoders);
407
408         // all should be added
409         verify(coderMgr, times(2)).addDecoder(any());
410         verify(coderMgr, times(1)).addEncoder(any());
411     }
412
413     @Test
414     void testInitCoders_InvalidGsonClass() {
415         when(gson1.getClassContainer()).thenReturn(UNKNOWN_CLASS);
416
417         assertThatIllegalArgumentException()
418             .isThrownBy(() -> new MyDrools(GROUP, ARTIFACT, VERSION, decoders, encoders))
419             .withMessageContaining("cannot be retrieved");
420     }
421
422     @Test
423     void testInitCoders_InvalidFilterClass() {
424         when(filter2.getCodedClass()).thenReturn(UNKNOWN_CLASS);
425
426         assertThatIllegalArgumentException()
427             .isThrownBy(() -> new MyDrools(GROUP, ARTIFACT, VERSION, decoders, encoders))
428             .withMessageContaining("cannot be retrieved");
429     }
430
431     @Test
432     void testInitCoders_Filters() {
433
434         drools = new MyDrools(GROUP, ARTIFACT, VERSION, decoders, encoders);
435
436         ArgumentCaptor<EventProtocolParams> dec = ArgumentCaptor.forClass(EventProtocolParams.class);
437         verify(coderMgr, times(2)).addDecoder(dec.capture());
438
439         ArgumentCaptor<EventProtocolParams> enc = ArgumentCaptor.forClass(EventProtocolParams.class);
440         verify(coderMgr, times(1)).addEncoder(enc.capture());
441
442         // validate parameters
443         EventProtocolParams params = dec.getAllValues().get(0);
444         assertEquals(ARTIFACT, params.getArtifactId());
445         assertEquals(gson1, params.getCustomGsonCoder());
446         assertEquals(Object.class.getName(), params.getEventClass());
447         assertEquals(GROUP, params.getGroupId());
448         assertEquals(CLASS_LOADER_HASHCODE, params.getModelClassLoaderHash());
449         assertEquals(jsonFilter1a, params.getProtocolFilter());
450         assertEquals(TOPIC, params.getTopic());
451
452         params = dec.getAllValues().get(1);
453         assertEquals(ARTIFACT, params.getArtifactId());
454         assertEquals(gson1, params.getCustomGsonCoder());
455         assertEquals(String.class.getName(), params.getEventClass());
456         assertEquals(GROUP, params.getGroupId());
457         assertEquals(CLASS_LOADER_HASHCODE, params.getModelClassLoaderHash());
458         assertEquals(jsonFilter1b, params.getProtocolFilter());
459         assertEquals(TOPIC, params.getTopic());
460
461         params = enc.getAllValues().get(0);
462         assertEquals(ARTIFACT, params.getArtifactId());
463         assertEquals(gson2, params.getCustomGsonCoder());
464         assertEquals(Integer.class.getName(), params.getEventClass());
465         assertEquals(GROUP, params.getGroupId());
466         assertEquals(CLASS_LOADER_HASHCODE, params.getModelClassLoaderHash());
467         assertEquals(jsonFilter2, params.getProtocolFilter());
468         assertEquals(TOPIC, params.getTopic());
469     }
470
471     @Test
472     void testOwnsCoder() {
473         int hc = CLASS_LOADER_HASHCODE;
474
475         // wrong hash code
476         assertFalse(drools.ownsCoder(String.class, hc + 1));
477
478         // correct hash code
479         assertTrue(drools.ownsCoder(String.class, hc));
480
481         // unknown class
482         drools = new MyDrools(GROUP, ARTIFACT, VERSION, null, null) {
483             @Override
484             protected boolean isNotAClass(String className) {
485                 return true;
486             }
487         };
488         assertFalse(drools.ownsCoder(String.class, hc));
489     }
490
491     @Test
492     void testStart_testStop_testIsAlive() {
493         drools = new MyDrools(GROUP, ARTIFACT, VERSION, decoders, encoders);
494
495         when(container.start()).thenReturn(true);
496         when(container.stop()).thenReturn(true);
497
498         assertFalse(drools.isAlive());
499
500         // start it
501         assertTrue(drools.start());
502         verify(container).start();
503         assertTrue(drools.isAlive());
504
505         // repeat - no changes
506         assertTrue(drools.start());
507         verify(container).start();
508         assertTrue(drools.isAlive());
509
510         // stop it
511         assertTrue(drools.stop());
512         verify(container).stop();
513         assertFalse(drools.isAlive());
514
515         // repeat - no changes
516         assertTrue(drools.stop());
517         verify(container).stop();
518         assertFalse(drools.isAlive());
519
520         // now check with container returning false - should still be invoked
521         when(container.start()).thenReturn(false);
522         when(container.stop()).thenReturn(false);
523         assertFalse(drools.start());
524         assertTrue(drools.isAlive());
525         assertFalse(drools.stop());
526         assertFalse(drools.isAlive());
527         verify(container, times(2)).start();
528         verify(container, times(2)).stop();
529
530         // coders should still be intact
531         verify(coderMgr, never()).removeDecoders(any(), any(), any());
532         verify(coderMgr, never()).removeEncoders(any(), any(), any());
533
534         verify(container, never()).shutdown();
535         verify(container, never()).destroy();
536     }
537
538     @Test
539     void testShutdown() {
540         drools = new MyDrools(GROUP, ARTIFACT, VERSION, decoders, encoders);
541
542         // start it
543         drools.start();
544
545         // shut down
546         drools.shutdown();
547
548         verify(container).stop();
549         assertFalse(drools.isAlive());
550
551         // coders should have been removed
552         verify(coderMgr, times(2)).removeDecoders(any(), any(), any());
553         verify(coderMgr, times(2)).removeEncoders(any(), any(), any());
554
555         verify(container).shutdown();
556         verify(container, never()).destroy();
557     }
558
559     @Test
560     void testShutdown_Ex() {
561         drools = new MyDrools(GROUP, ARTIFACT, VERSION, decoders, encoders);
562
563         // start it
564         drools.start();
565
566         when(container.stop()).thenThrow(RUNTIME_EX);
567
568         // shut down
569         drools.shutdown();
570
571         assertFalse(drools.isAlive());
572
573         verify(container).shutdown();
574         verify(container, never()).destroy();
575     }
576
577     @Test
578     void testHalt() {
579         drools = new MyDrools(GROUP, ARTIFACT, VERSION, decoders, encoders);
580
581         // start it
582         drools.start();
583
584         // halt
585         drools.halt();
586
587         verify(container).stop();
588         assertFalse(drools.isAlive());
589
590         // coders should have been removed
591         verify(coderMgr, times(2)).removeDecoders(any(), any(), any());
592         verify(coderMgr, times(2)).removeEncoders(any(), any(), any());
593
594         verify(container).destroy();
595     }
596
597     @Test
598     void testHalt_Ex() {
599         drools = new MyDrools(GROUP, ARTIFACT, VERSION, decoders, encoders);
600
601         // start it
602         drools.start();
603
604         when(container.stop()).thenThrow(RUNTIME_EX);
605
606         // halt
607         drools.halt();
608
609         assertFalse(drools.isAlive());
610
611         verify(container).destroy();
612     }
613
614     @Test
615     void testRemoveCoders_Ex() {
616         drools = new MyDrools(GROUP, ARTIFACT, VERSION, decoders, encoders) {
617             @Override
618             protected void removeDecoders() {
619                 throw ARG_EX;
620             }
621
622             @Override
623             protected void removeEncoders() {
624                 throw ARG_EX;
625             }
626         };
627
628         assertThatCode(() -> drools.updateToVersion(GROUP, ARTIFACT, VERSION2, null, null)).doesNotThrowAnyException();
629     }
630
631     @Test
632     void testOfferStringString() {
633         drools.start();
634         assertTrue(drools.offer(TOPIC, EVENT_TEXT));
635
636         verify(container).insertAll(EVENT);
637     }
638
639     @Test
640     void testOfferStringString_NoDecode() {
641         when(coderMgr.isDecodingSupported(GROUP, ARTIFACT, TOPIC)).thenReturn(false);
642
643         drools.start();
644         assertTrue(drools.offer(TOPIC, EVENT_TEXT));
645
646         verify(container, never()).insertAll(EVENT);
647     }
648
649     @Test
650     void testOfferStringString_DecodeUnsupported() {
651         when(coderMgr.decode(GROUP, ARTIFACT, TOPIC, EVENT_TEXT))
652             .thenThrow(new UnsupportedOperationException(EXPECTED_EXCEPTION));
653
654         drools.start();
655         assertTrue(drools.offer(TOPIC, EVENT_TEXT));
656
657         verify(container, never()).insertAll(EVENT);
658     }
659
660     @Test
661     void testOfferStringString_DecodeEx() {
662         when(coderMgr.decode(GROUP, ARTIFACT, TOPIC, EVENT_TEXT)).thenThrow(RUNTIME_EX);
663
664         drools.start();
665         assertTrue(drools.offer(TOPIC, EVENT_TEXT));
666
667         verify(container, never()).insertAll(EVENT);
668     }
669
670     @Test
671     void testOfferStringString_Ignored() {
672         drools.start();
673
674         drools.lock();
675         assertTrue(drools.offer(TOPIC, EVENT_TEXT));
676         assertEquals(0, drools.getRecentSourceEvents().length);
677         drools.unlock();
678
679         drools.stop();
680         assertTrue(drools.offer(TOPIC, EVENT_TEXT));
681         assertEquals(0, drools.getRecentSourceEvents().length);
682         drools.start();
683
684         // no sessions
685         when(container.getPolicySessions()).thenReturn(Collections.emptyList());
686         assertTrue(drools.offer(TOPIC, EVENT_TEXT));
687         assertEquals(0, drools.getRecentSourceEvents().length);
688     }
689
690     @Test
691     void testOfferT() {
692         drools.start();
693         assertTrue(drools.offer(EVENT));
694         assertEquals(1, drools.getRecentSourceEvents().length);
695         assertEquals(EVENT, drools.getRecentSourceEvents()[0]);
696         verify(container).insertAll(EVENT);
697
698         verify(prov1).beforeInsert(drools, EVENT);
699         verify(prov2).beforeInsert(drools, EVENT);
700
701         verify(prov1).afterInsert(drools, EVENT, true);
702         verify(prov2).afterInsert(drools, EVENT, true);
703     }
704
705     @Test
706     void testOfferT_Ex() {
707         when(prov1.beforeInsert(drools, EVENT)).thenThrow(RUNTIME_EX);
708         when(prov1.afterInsert(drools, EVENT, true)).thenThrow(RUNTIME_EX);
709
710         drools.start();
711         assertTrue(drools.offer(EVENT));
712         assertEquals(1, drools.getRecentSourceEvents().length);
713         assertEquals(EVENT, drools.getRecentSourceEvents()[0]);
714         verify(container).insertAll(EVENT);
715
716         // should still invoke prov2
717         verify(prov2).beforeInsert(drools, EVENT);
718
719         verify(prov2).afterInsert(drools, EVENT, true);
720     }
721
722     @Test
723     void testOfferT_NotInserted() {
724         when(container.insertAll(EVENT)).thenReturn(false);
725
726         drools.start();
727         assertTrue(drools.offer(EVENT));
728         assertEquals(1, drools.getRecentSourceEvents().length);
729         assertEquals(EVENT, drools.getRecentSourceEvents()[0]);
730         verify(container).insertAll(EVENT);
731
732         verify(prov1).beforeInsert(drools, EVENT);
733         verify(prov2).beforeInsert(drools, EVENT);
734
735         verify(prov1).afterInsert(drools, EVENT, false);
736         verify(prov2).afterInsert(drools, EVENT, false);
737     }
738
739     @Test
740     void testOfferT_BeforeInsertIntercept() {
741         drools.start();
742         when(prov1.beforeInsert(drools, EVENT)).thenReturn(true);
743
744         assertTrue(drools.offer(EVENT));
745         assertEquals(1, drools.getRecentSourceEvents().length);
746         assertEquals(EVENT, drools.getRecentSourceEvents()[0]);
747         verify(container, never()).insertAll(EVENT);
748
749         verify(prov1).beforeInsert(drools, EVENT);
750
751         // nothing else invoked
752         verify(prov2, never()).beforeInsert(drools, EVENT);
753         verify(prov1, never()).afterInsert(drools, EVENT, true);
754         verify(prov2, never()).afterInsert(drools, EVENT, true);
755     }
756
757     @Test
758     void testOfferT_AfterInsertIntercept() {
759         drools.start();
760
761         when(prov1.afterInsert(drools, EVENT, true)).thenReturn(true);
762
763         assertTrue(drools.offer(EVENT));
764         assertEquals(1, drools.getRecentSourceEvents().length);
765         assertEquals(EVENT, drools.getRecentSourceEvents()[0]);
766         verify(container).insertAll(EVENT);
767
768         verify(prov1).beforeInsert(drools, EVENT);
769         verify(prov2).beforeInsert(drools, EVENT);
770
771         verify(prov1).afterInsert(drools, EVENT, true);
772
773         // prov2 is never called
774         verify(prov2, never()).afterInsert(drools, EVENT, true);
775     }
776
777     @Test
778     void testOfferT_Ignored() {
779         drools.start();
780
781         drools.lock();
782         assertTrue(drools.offer(EVENT));
783         assertEquals(0, drools.getRecentSourceEvents().length);
784         drools.unlock();
785
786         drools.stop();
787         assertTrue(drools.offer(EVENT));
788         assertEquals(0, drools.getRecentSourceEvents().length);
789         drools.start();
790
791         // no sessions
792         when(container.getPolicySessions()).thenReturn(Collections.emptyList());
793         assertTrue(drools.offer(EVENT));
794         assertEquals(0, drools.getRecentSourceEvents().length);
795     }
796
797     @Test
798     void testDeliver() {
799         drools.start();
800         assertTrue(drools.deliver(sink, EVENT));
801         assertEquals(1, drools.getRecentSinkEvents().length);
802         assertEquals(EVENT_TEXT, drools.getRecentSinkEvents()[0]);
803
804         verify(sink).send(EVENT_TEXT);
805
806         verify(prov1).beforeDeliver(drools, sink, EVENT);
807         verify(prov2).beforeDeliver(drools, sink, EVENT);
808
809         verify(prov1).afterDeliver(drools, sink, EVENT, EVENT_TEXT, true);
810         verify(prov2).afterDeliver(drools, sink, EVENT, EVENT_TEXT, true);
811     }
812
813     @Test
814     void testDeliver_InvalidArgs() {
815         drools.start();
816
817         assertThatIllegalArgumentException().isThrownBy(() -> drools.deliver(null, EVENT))
818             .withMessageContaining("sink");
819
820         assertThatIllegalArgumentException().isThrownBy(() -> drools.deliver(sink, null))
821             .withMessageContaining("event");
822
823         drools.lock();
824         assertThatIllegalStateException().isThrownBy(() -> drools.deliver(sink, EVENT)).withMessageContaining("locked");
825         drools.unlock();
826
827         drools.stop();
828         assertThatIllegalStateException().isThrownBy(() -> drools.deliver(sink, EVENT))
829             .withMessageContaining("stopped");
830         drools.start();
831
832         assertEquals(0, drools.getRecentSinkEvents().length);
833     }
834
835     @Test
836     void testDeliver_BeforeIntercept() {
837         when(prov1.beforeDeliver(drools, sink, EVENT)).thenReturn(true);
838
839         drools.start();
840         assertTrue(drools.deliver(sink, EVENT));
841         assertEquals(0, drools.getRecentSinkEvents().length);
842
843         verify(prov1).beforeDeliver(drools, sink, EVENT);
844
845         // nothing else should have been invoked
846         verify(sink, never()).send(EVENT_TEXT);
847         verify(prov2, never()).beforeDeliver(drools, sink, EVENT);
848         verify(prov1, never()).afterDeliver(drools, sink, EVENT, EVENT_TEXT, true);
849         verify(prov2, never()).afterDeliver(drools, sink, EVENT, EVENT_TEXT, true);
850     }
851
852     @Test
853     void testDeliver_AfterIntercept() {
854         when(prov1.afterDeliver(drools, sink, EVENT, EVENT_TEXT, true)).thenReturn(true);
855
856         drools.start();
857         assertTrue(drools.deliver(sink, EVENT));
858         assertEquals(1, drools.getRecentSinkEvents().length);
859         assertEquals(EVENT_TEXT, drools.getRecentSinkEvents()[0]);
860
861         verify(prov1).beforeDeliver(drools, sink, EVENT);
862         verify(prov2).beforeDeliver(drools, sink, EVENT);
863
864         verify(sink).send(EVENT_TEXT);
865
866         verify(prov1).afterDeliver(drools, sink, EVENT, EVENT_TEXT, true);
867
868         // nothing else should have been invoked
869         verify(prov2, never()).afterDeliver(drools, sink, EVENT, EVENT_TEXT, true);
870     }
871
872     @Test
873     void testDeliver_InterceptEx() {
874         when(prov1.beforeDeliver(drools, sink, EVENT)).thenThrow(RUNTIME_EX);
875         when(prov1.afterDeliver(drools, sink, EVENT, EVENT_TEXT, true)).thenThrow(RUNTIME_EX);
876
877         drools.start();
878         assertTrue(drools.deliver(sink, EVENT));
879
880         verify(sink).send(EVENT_TEXT);
881
882         // should still invoke prov2
883         verify(prov2).beforeDeliver(drools, sink, EVENT);
884         verify(prov2).afterDeliver(drools, sink, EVENT, EVENT_TEXT, true);
885     }
886
887     @Test
888     void testGetXxx() {
889         assertEquals(VERSION, drools.getVersion());
890         assertEquals(ARTIFACT, drools.getArtifactId());
891         assertEquals(GROUP, drools.getGroupId());
892         assertEquals(CLASS_LOADER_HASHCODE, drools.getModelClassLoaderHash());
893         assertSame(container, drools.getContainer());
894         assertEquals(List.of(sess1, sess2), drools.getSessions());
895
896         // test junit methods - need a controller with fewer overrides
897         drools = new MavenDroolsController(GROUP, ARTIFACT, VERSION, null, null) {
898             @Override
899             protected PolicyContainer makePolicyContainer(String groupId, String artifactId, String version) {
900                 return container;
901             }
902         };
903
904         assertSame(EventProtocolCoderConstants.getManager(), drools.getCoderManager());
905         assertSame(DroolsControllerFeatureApiConstants.getProviders(), drools.getDroolsProviders());
906     }
907
908     @Test
909     void testLock_testUnlock_testIsLocked() {
910         assertFalse(drools.isLocked());
911
912         assertTrue(drools.lock());
913         assertTrue(drools.isLocked());
914
915         assertTrue(drools.unlock());
916         assertFalse(drools.isLocked());
917
918         // repeat
919         assertTrue(drools.lock());
920         assertTrue(drools.isLocked());
921
922         assertTrue(drools.unlock());
923         assertFalse(drools.isLocked());
924     }
925
926     @Test
927     void testGetSessionNames_testGetCanonicalSessionNames() {
928         assertEquals("[session-A, session-B]", drools.getSessionNames(true).toString());
929         assertEquals("[full-A, full-B]", drools.getSessionNames(false).toString());
930
931         assertEquals("[session-A, session-B]", drools.getSessionNames().toString());
932
933         assertEquals("[full-A, full-B]", drools.getCanonicalSessionNames().toString());
934
935         // exception case
936         when(container.getPolicySessions()).thenThrow(RUNTIME_EX);
937         assertEquals("[expected exception]", drools.getSessionNames().toString());
938     }
939
940     @Test
941     void testGetBaseDomainNames() {
942         KieContainer kiecont = mock(KieContainer.class);
943         when(kiecont.getKieBaseNames()).thenReturn(List.of("kieA", "kieB"));
944         when(container.getKieContainer()).thenReturn(kiecont);
945
946         assertEquals("[kieA, kieB]", drools.getBaseDomainNames().toString());
947     }
948
949     @Test
950     void testGetSession() {
951         assertThatIllegalArgumentException().isThrownBy(() -> drools.getSession(null))
952             .withMessageContaining("must be provided");
953
954         assertThatIllegalArgumentException().isThrownBy(() -> drools.getSession(""))
955             .withMessageContaining("must be provided");
956
957         assertSame(sess1, drools.getSession(SESSION1));
958         assertSame(sess1, drools.getSession(FULL_SESSION1));
959
960         assertSame(sess2, drools.getSession(SESSION2));
961
962         assertThatIllegalArgumentException().isThrownBy(() -> drools.getSession("unknown session"))
963             .withMessageContaining("Invalid Session Name");
964     }
965
966     @Test
967     void testFactClassNames() {
968         // copy to a sorted map so the order remains unchanged
969         Map<String, Integer> map = new TreeMap<>(drools.factClassNames(SESSION1));
970         assertEquals("{java.lang.Integer=2, java.lang.String=1}", map.toString());
971
972         assertThatIllegalArgumentException().isThrownBy(() -> drools.factClassNames(null))
973             .withMessageContaining("Invalid Session Name");
974
975         assertThatIllegalArgumentException().isThrownBy(() -> drools.factClassNames(""))
976             .withMessageContaining("Invalid Session Name");
977     }
978
979     @Test
980     void testFactCount() {
981         assertEquals(FACT_COUNT, drools.factCount(SESSION1));
982
983         assertThatIllegalArgumentException().isThrownBy(() -> drools.factCount(null))
984             .withMessageContaining("Invalid Session Name");
985
986         assertThatIllegalArgumentException().isThrownBy(() -> drools.factCount(""))
987             .withMessageContaining("Invalid Session Name");
988     }
989
990     @Test
991     void testFactsStringStringBoolean() {
992         assertEquals("[1000, 1001]", drools.facts(SESSION1, Integer.class.getName(), false).toString());
993         verify(kieSess, never()).delete(fact1);
994         verify(kieSess, never()).delete(fact2);
995         verify(kieSess, never()).delete(fact3);
996         verify(kieSess, never()).delete(factex);
997
998         // now delete - but should only delete 1 & 3
999         assertEquals("[1000, 1001]", drools.facts(SESSION1, Integer.class.getName(), true).toString());
1000         verify(kieSess).delete(fact1);
1001         verify(kieSess, never()).delete(fact2);
1002         verify(kieSess).delete(fact3);
1003         verify(kieSess, never()).delete(factex);
1004
1005         assertThatIllegalArgumentException().isThrownBy(() -> drools.facts(null, Integer.class.getName(), false))
1006             .withMessageContaining("Invalid Session Name");
1007
1008         assertThatIllegalArgumentException().isThrownBy(() -> drools.facts("", Integer.class.getName(), false))
1009             .withMessageContaining("Invalid Session Name");
1010
1011         assertThatIllegalArgumentException().isThrownBy(() -> drools.facts(SESSION1, null, false))
1012             .withMessageContaining("Invalid Class Name");
1013
1014         assertThatIllegalArgumentException().isThrownBy(() -> drools.facts(SESSION1, "", false))
1015             .withMessageContaining("Invalid Class Name");
1016
1017         assertThatIllegalArgumentException().isThrownBy(() -> drools.facts(SESSION1, UNKNOWN_CLASS, false))
1018             .withMessageContaining("classloader");
1019     }
1020
1021     @Test
1022     void testFactsStringStringBoolean_DeleteEx() {
1023         doThrow(RUNTIME_EX).when(kieSess).delete(fact1);
1024
1025         assertEquals("[1000, 1001]", drools.facts(SESSION1, Integer.class.getName(), true).toString());
1026
1027         // should still have deleted #3
1028         verify(kieSess).delete(fact3);
1029     }
1030
1031     @Test
1032     void testFactsStringClassOfT() {
1033         assertEquals("[1000, 1001]", drools.facts(SESSION1, Integer.class).toString());
1034     }
1035
1036     @Test
1037     void testFactQuery() {
1038         assertEquals("[1000, 1001]", drools.factQuery(SESSION1, QUERY, ENTITY, false, PARM1, PARM2).toString());
1039
1040         verify(kieSess, never()).delete(fact1);
1041         verify(kieSess, never()).delete(fact3);
1042
1043         assertThatIllegalArgumentException()
1044             .isThrownBy(() -> drools.factQuery(null, QUERY, ENTITY, false, PARM1, PARM2))
1045             .withMessageContaining("Invalid Session Name");
1046
1047         assertThatIllegalArgumentException().isThrownBy(() -> drools.factQuery("", QUERY, ENTITY, false, PARM1, PARM2))
1048             .withMessageContaining("Invalid Session Name");
1049
1050         assertThatIllegalArgumentException()
1051             .isThrownBy(() -> drools.factQuery(SESSION1, null, ENTITY, false, PARM1, PARM2))
1052             .withMessageContaining("Invalid Query Name");
1053
1054         assertThatIllegalArgumentException()
1055             .isThrownBy(() -> drools.factQuery(SESSION1, "", ENTITY, false, PARM1, PARM2))
1056             .withMessageContaining("Invalid Query Name");
1057
1058         assertThatIllegalArgumentException()
1059             .isThrownBy(() -> drools.factQuery(SESSION1, QUERY, null, false, PARM1, PARM2))
1060             .withMessageContaining("Invalid Queried Entity");
1061
1062         assertThatIllegalArgumentException()
1063             .isThrownBy(() -> drools.factQuery(SESSION1, QUERY, "", false, PARM1, PARM2))
1064             .withMessageContaining("Invalid Queried Entity");
1065
1066         assertThatIllegalArgumentException().isThrownBy(
1067                 () -> drools.factQuery(SESSION1, QUERY + "-unknown-query", ENTITY, false, PARM1, PARM2))
1068             .withMessageContaining("Invalid Query Name");
1069     }
1070
1071     @Test
1072     void testFactQuery_Delete() {
1073         doThrow(RUNTIME_EX).when(kieSess).delete(fact1);
1074
1075         assertEquals("[1000, 1001]", drools.factQuery(SESSION1, QUERY, ENTITY, true, PARM1, PARM2).toString());
1076
1077         // should still delete fact #3
1078         verify(kieSess).delete(fact3);
1079     }
1080
1081     @Test
1082     void testDeleteStringT() {
1083         assertTrue(drools.delete(SESSION1, FACT3_OBJECT));
1084
1085         verify(kieSess, never()).delete(fact1);
1086         verify(kieSess).delete(fact3);
1087
1088         // not found
1089         assertFalse(drools.delete(SESSION1, "hello"));
1090
1091         // repeat, but generate exception while getting the first object
1092         assertTrue(drools.delete(SESSION1, FACT3_OBJECT));
1093
1094         verify(kieSess, never()).delete(fact1);
1095
1096         // should still delete fact #3
1097         verify(kieSess, times(2)).delete(fact3);
1098     }
1099
1100     @Test
1101     void testDeleteT() {
1102         assertTrue(drools.delete(FACT3_OBJECT));
1103
1104         verify(kieSess).delete(fact3);
1105     }
1106
1107     @Test
1108     void testDeleteStringClassOfT() {
1109         assertTrue(drools.delete(SESSION1, Integer.class));
1110
1111         verify(kieSess).delete(fact1);
1112         verify(kieSess).delete(fact3);
1113     }
1114
1115     @Test
1116     void testDeleteStringClassOfT_Ex() {
1117         doThrow(RUNTIME_EX).when(kieSess).delete(fact1);
1118
1119         assertFalse(drools.delete(SESSION1, Integer.class));
1120
1121         // should still delete fact #3
1122         verify(kieSess).delete(fact3);
1123     }
1124
1125     @Test
1126     void testDeleteClassOfT() {
1127         assertTrue(drools.delete(Integer.class));
1128
1129         verify(kieSess).delete(fact1);
1130         verify(kieSess).delete(fact3);
1131     }
1132
1133     @Test
1134     void testFetchModelClass() {
1135         assertSame(Long.class, drools.fetchModelClass(Long.class.getName()));
1136     }
1137
1138     @Test
1139     void testIsBrained() {
1140         assertTrue(drools.isBrained());
1141     }
1142
1143     @Test
1144     void testToString() {
1145         assertNotNull(drools.toString());
1146     }
1147
1148     private class MyDrools extends MavenDroolsController {
1149
1150         public MyDrools(String groupId, String artifactId, String version,
1151                         List<TopicCoderFilterConfiguration> decoderConfigurations,
1152                         List<TopicCoderFilterConfiguration> encoderConfigurations) {
1153
1154             super(groupId, artifactId, version, decoderConfigurations, encoderConfigurations);
1155         }
1156
1157         @Override
1158         protected EventProtocolCoder getCoderManager() {
1159             return coderMgr;
1160         }
1161
1162         @Override
1163         protected OrderedServiceImpl<DroolsControllerFeatureApi> getDroolsProviders() {
1164             return droolsProviders;
1165         }
1166
1167         @Override
1168         protected PolicyContainer makePolicyContainer(String groupId, String artifactId, String version) {
1169             lenient().when(container.getGroupId()).thenReturn(groupId);
1170             lenient().when(container.getArtifactId()).thenReturn(artifactId);
1171             lenient().when(container.getVersion()).thenReturn(version);
1172
1173             return container;
1174         }
1175     }
1176 }