/** * Licensed to the Austrian Association for Software Tool Integration (AASTI) * under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright * ownership. The AASTI licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.openengsb.core.workflow.drools; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.nullValue; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import static org.junit.matchers.JUnitMatchers.hasItem; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import org.junit.Test; import org.mockito.InOrder; import org.openengsb.core.api.Domain; import org.openengsb.core.api.Event; import org.openengsb.core.api.context.ContextHolder; import org.openengsb.core.test.NullDomain; import org.openengsb.core.test.NullEvent3; import org.openengsb.core.workflow.api.RuleBaseException; import org.openengsb.core.workflow.api.model.InternalWorkflowEvent; import org.openengsb.core.workflow.api.model.ProcessBag; import org.openengsb.core.workflow.api.model.RuleBaseElementId; import org.openengsb.core.workflow.api.model.RuleBaseElementType; import com.fasterxml.jackson.databind.ObjectMapper; public class WorkflowServiceTest extends AbstractWorkflowServiceTest { private DummyExampleDomain logService; private DummyNotificationDomain notification; @Override public void setUp() throws Exception { super.setUp(); logService = (DummyExampleDomain) domains.get("example"); notification = (DummyNotificationDomain) domains.get("notification"); } @Test public void testProcessEvent_shouldProcessEvent() throws Exception { service.processEvent(new Event()); } @Test public void testProcessInternalWorkflowEvent_shouldNotFail() throws Exception { InternalWorkflowEvent event = new InternalWorkflowEvent(); event.getProcessBag().setProcessId("0"); service.processEvent(event); } @Test public void testProcessEvent_shouldTriggerHelloWorld() throws Exception { Event event = new Event(); service.processEvent(event); verify(notification, atLeast(1)).notify("Hello"); verify((DummyExampleDomain) domains.get("example"), atLeast(1)).doSomething("Hello World"); verify(myservice, atLeast(1)).call(); } @Test public void testUseLog_shouldLog() throws Exception { Event event = new Event("test-context"); service.processEvent(event); verify(logService).doSomething("42"); } @Test public void testUpdateRule_shouldWork() throws Exception { manager.update(new RuleBaseElementId(RuleBaseElementType.Rule, "hello1"), "when\n Event ( name == \"test-context\")\n then \n example.doSomething(\"21\");"); Event event = new Event("test-context"); service.processEvent(event); verify(logService).doSomething("21"); } @Test public void testUseLogContent_shouldCallLogService() throws Exception { Event event = new Event("test-context"); service.processEvent(event); verify(logService, times(2)).doSomething(anyString()); } @Test public void testAddInvalidRule_shouldNotModifyRulebase() throws Exception { try { manager.add(new RuleBaseElementId(RuleBaseElementType.Rule, "hello"), "this*is_invalid"); fail("expected Exception"); } catch (RuleBaseException e) { // expected } Event event = new Event("test-context"); service.processEvent(event); verify(logService, times(2)).doSomething(anyString()); } @Test public void testInvalidModifyRule_shouldNotModifyRulebase() throws Exception { try { manager.update(new RuleBaseElementId(RuleBaseElementType.Rule, "hello1"), "this*is_invalid"); fail("expected Exception"); } catch (RuleBaseException e) { assertThat(e.getCause(), nullValue()); } Event event = new Event("test-context"); service.processEvent(event); verify(logService, times(2)).doSomething(anyString()); } @Test public void testStartProcess_shouldRunScriptNodes() throws Exception { long id = service.startFlow("flowtest"); service.waitForFlowToFinishIndefinitely(id); verify(logService).doSomething("context: " + ContextHolder.get().getCurrentContextId()); } @Test public void testStartMultipleProcesses_shouldRunInCorrectContext() throws Exception { int tryThreads = 2; List<DummyExampleDomain> services = new ArrayList<DummyExampleDomain>(); for (int i = 0; i < tryThreads; i++) { ContextHolder.get().setCurrentContextId(Integer.toString(i)); services.add(registerDummyConnector(DummyExampleDomain.class, "example")); } for (int i = 0; i < tryThreads; i++) { ContextHolder.get().setCurrentContextId(Integer.toString(i)); long id = service.startFlow("flowtest"); service.waitForFlowToFinishIndefinitely(id); verify(services.get(i)).doSomething("context: " + ContextHolder.get().getCurrentContextId()); } } @Test public void testStartProcessWithEvents_shouldRunScriptNodes() throws Exception { long id = service.startFlow("floweventtest"); service.processEvent(new Event()); service.processEvent(new TestEvent()); service.waitForFlowToFinishIndefinitely(id); InOrder inOrder2 = inOrder(logService); inOrder2.verify(logService).doSomething("start testflow"); inOrder2.verify(logService).doSomething("first event received"); } @Test public void testStart2Processes_shouldOnlyTriggerSpecificEvents() throws Exception { long id1 = service.startFlow("floweventtest"); long id2 = service.startFlow("floweventtest"); service.processEvent(new Event("event", id1)); service.processEvent(new TestEvent(id1)); service.waitForFlowToFinishIndefinitely(id1); assertThat(service.getRunningFlows(), hasItem(id2)); assertThat(service.getRunningFlows(), not(hasItem(id1))); } @Test public void testCiWorkflow_shouldRunWorkflow() throws Exception { long id = service.startFlow("ci"); service.processEvent(new BuildSuccess()); service.processEvent(new TestSuccess()); service.waitForFlowToFinishIndefinitely(id); verify((DummyReport) domains.get("report"), times(1)).collectData(); verify(notification, atLeast(1)).notify(anyString()); verify((DummyDeploy) domains.get("deploy"), times(1)).deployProject(); } @Test public void testStartInBackgroundWithoutStartedEvent_shouldRunInBackground() throws Exception { long id = service.startFlow("backgroundFlow"); service.waitForFlowToFinish(id, 5000); verify(logService).doSomething(eq("" + id)); } @Test public void testStartWorkflowTriggeredByEvent_shouldStartWorkflow() throws Exception { manager.add(new RuleBaseElementId(RuleBaseElementType.Rule, "test42"), "when\n" + " Event()\n" + "then\n" + " kcontext.getKnowledgeRuntime().startProcess(\"ci\");\n"); service.processEvent(new Event()); assertThat(service.getRunningFlows().isEmpty(), is(false)); } @Test public void testRegisterWorkflowTrigger_shouldRegisterTrigger() throws Exception { service.registerFlowTriggerEvent(new Event("triggerEvent"), "ci"); service.processEvent(new Event()); service.processEvent(new Event("triggerEvent")); assertThat(service.getRunningFlows().size(), is(1)); } @Test public void testRegisterWorkflowTriggerWithSubclass_shouldRegisterTrigger() throws Exception { NullEvent3 testEvent = new NullEvent3(); testEvent.setName("triggerEvent"); testEvent.setTestProperty("foo"); testEvent.setTestStringProp("bar"); testEvent.setTestBoolProp(true); testEvent.setTestIntProp(42); service.registerFlowTriggerEvent(testEvent, "ci"); service.processEvent(new Event()); service.processEvent(testEvent); assertThat(service.getRunningFlows().size(), is(1)); } @Test public void testRegisterWorkflowTriggerIgnoreNullFields_shouldRegisterTrigger() throws Exception { NullEvent3 testEvent = new NullEvent3(); testEvent.setName("triggerEvent"); service.registerFlowTriggerEvent(testEvent, "ci"); service.processEvent(new Event()); service.processEvent(testEvent); assertThat(service.getRunningFlows().size(), is(1)); } @Test public void testRegisterWorkflowTriggerIgnoreNullFieldsMixed_shouldRegisterTrigger() throws Exception { NullEvent3 testEvent = new NullEvent3(); testEvent.setName("triggerEvent"); testEvent.setTestStringProp("bar"); testEvent.setTestIntProp(42); service.registerFlowTriggerEvent(testEvent, "ci"); service.processEvent(new Event()); service.processEvent(testEvent); assertThat(service.getRunningFlows().size(), is(1)); } @Test(timeout = 3000) public void testRegisterWorkflowTriggerWithFlowStartedEvent_shouldRegisterTrigger() throws Exception { service.registerFlowTriggerEvent(new Event("triggerEvent"), "flowStartedEvent"); service.processEvent(new Event("triggerEvent")); for (Long id : service.getRunningFlows()) { service.waitForFlowToFinishIndefinitely(id); } } @Test public void testIfEventIsRetracted_shouldWork() throws Exception { Event event = new Event(); service.processEvent(event); event = new Event("test-context"); service.processEvent(event); verify(logService, times(2)).doSomething("Hello World"); } @Test public void testStartProcessWithProperyBagAndChangePropertyByScriptNode_shouldChangeProperty() throws Exception { ProcessBag processBag = new ProcessBag(); Map<String, Object> parameterMap = new HashMap<String, Object>(); parameterMap.put("processBag", processBag); long id = service.startFlowWithParameters("propertybagtest", parameterMap); service.waitForFlowToFinishIndefinitely(id); assertThat((String) processBag.getProperty("test"), is(String.valueOf(id))); } @Test public void testProcessEventsConcurrently_shouldProcessBothEvents() throws Exception { manager.addImport(TestEvent.class.getName()); manager.add(new RuleBaseElementId(RuleBaseElementType.Rule, "concurrent test"), "when\n" + "TestEvent(value == \"0\")\n" + "then\n" + "example.doSomething(\"concurrent\");"); manager.add(new RuleBaseElementId(RuleBaseElementType.Rule, "concurrent test1"), "when\n" + "TestEvent(value == \"1\")\n" + "then\n" + "Thread.sleep(1000);"); Callable<Void> task = makeProcessEventTask(new TestEvent("1")); Callable<Void> task2 = makeProcessEventTask(new TestEvent("0")); ExecutorService executor = Executors.newCachedThreadPool(); Future<Void> future1 = executor.submit(task); Thread.sleep(300); Future<Void> future2 = executor.submit(task2); future1.get(); future2.get(); verify(logService).doSomething("concurrent"); } private Callable<Void> makeProcessEventTask(final Event event) { Callable<Void> task = new Callable<Void>() { @Override public Void call() throws Exception { service.processEvent(event); return null; } }; return task; } @Test public void testExecuteWorkflow_shouldRunWorkFlow() throws Exception { ProcessBag result = service.executeWorkflow("simpleFlow", new ProcessBag()); assertThat((Integer) result.getProperty("test"), is(42)); assertThat((String) result.getProperty("alternativeName"), is("The answer to life the universe and everything")); } @Test public void testCancelWorkflow_shouldAbortWorkflow() throws Exception { long pid = service.startFlow("ci"); service.cancelFlow(pid); service.waitForFlowToFinish(pid, 5000); } @Test public void testCancelWorkflowWithOpenTasks_shouldAbortWorkflow() throws Exception { long pid = service.startFlow("ci"); ProcessBag bag = new ProcessBag(); bag.setProcessId(Long.toString(pid)); taskboxInternal.createNewTask(bag); service.cancelFlow(pid); service.waitForFlowToFinish(pid, 5000); assertThat("Tasks were not cancelled properly", taskbox.getOpenTasks().isEmpty(), is(true)); } @Test public void testWaitForFlow_shouldReturnTrue() throws Exception { Long pid = service.startFlow("flowtest"); boolean finished = service.waitForFlowToFinish(pid, 400); assertThat(finished, is(true)); } @Test public void testWaitForFlowThatCannotFinish_shouldReturnFalse() throws Exception { Long pid = service.startFlow("floweventtest"); service.processEvent(new Event("FirstEvent")); service.startFlow("flowtest"); boolean finished = service.waitForFlowToFinish(pid, 400); assertThat(finished, is(false)); } @Test public void testResponseRule_shouldProcessEvent() throws Exception { NullDomain nullDomainImpl = mock(NullDomain.class); registerServiceViaId(nullDomainImpl, "test-connector", NullDomain.class, Domain.class); manager.addImport(NullDomain.class.getName()); manager.add(new RuleBaseElementId(RuleBaseElementType.Rule, "response-test"), "" + "when\n" + " e : Event()\n" + "then\n" + " NullDomain origin = (NullDomain) OsgiHelper.getResponseProxy(e, NullDomain.class);" + " origin.nullMethod(42);"); Event event = new Event(); event.setOrigin("test-connector"); service.processEvent(event); verify(nullDomainImpl).nullMethod(42); } @Test public void testTriggerExceptionInEventProcessing_shouldNotKeepLocked() throws Exception { manager.add(new RuleBaseElementId(RuleBaseElementType.Rule, "response-test"), "" + "when\n" + " e : Event(name==\"evil\")\n" + "then\n" + " String testxx = null;" + " testxx.toString();"); // provoke NPE try { service.processEvent(new Event("evil")); fail("evil Event should trigger Exception"); } catch (Exception e) { // expected } final AtomicReference<Exception> exceptionOccured = new AtomicReference<Exception>(); final AtomicBoolean completed = new AtomicBoolean(false); Thread t = new Thread() { @Override public void run() { try { service.processEvent(new Event()); // should work because the evil Event should have been removed completed.set(true); } catch (Exception e) { exceptionOccured.set(e); } }; }; t.start(); t.join(10000); assertThat("processEvent did not complete in time. Seems the workflow-engine is locked", completed.get(), is(true)); if (exceptionOccured.get() != null) { throw exceptionOccured.get(); } } @Test public void testSerializeConsequenceException_shouldReturnString() throws Exception { manager.add(new RuleBaseElementId(RuleBaseElementType.Rule, "response-test"), "" + "when\n" + " e : Event(name==\"evil\")\n" + "then\n" + " String testxx = null;" + " testxx.toString();"); // provoke NPE try { service.processEvent(new Event("evil")); fail("evil Event should trigger Exception"); } catch (Exception e) { String exceptionString = new ObjectMapper().writeValueAsString(e); assertThat(exceptionString, not(nullValue())); } } @Test public void testThrowEvent_shouldAuditEvent() throws Exception { Event event = new Event("good"); service.processEvent(event); verify(auditingMock).onEvent(event); } @Test public void testFlowListener_shouldTrigger() throws Exception { long id = service.startFlow("ci"); service.processEvent(new BuildSuccess()); Thread.sleep(300); verify(auditingMock).onNodeStart(eq("ci"), eq(id), eq("Start Tests")); service.processEvent(new TestSuccess()); verify(auditingMock).onNodeStart(eq("ci"), eq(id), eq("deployProject")); service.waitForFlowToFinishIndefinitely(id); } private static class BuildSuccess extends Event { } private static class TestSuccess extends Event { } }