/**
* 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 {
}
}