/*
* Copyright 2004-2012 the original author or authors.
*
* Licensed 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.springframework.webflow.engine;
import java.util.ArrayList;
import junit.framework.TestCase;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.binding.expression.support.FluentParserContext;
import org.springframework.binding.mapping.impl.DefaultMapper;
import org.springframework.binding.mapping.impl.DefaultMapping;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.webflow.TestException;
import org.springframework.webflow.action.TestMultiAction;
import org.springframework.webflow.core.collection.AttributeMap;
import org.springframework.webflow.core.collection.LocalAttributeMap;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.engine.support.DefaultTargetStateResolver;
import org.springframework.webflow.engine.support.MockTransitionCriteria;
import org.springframework.webflow.engine.support.TransitionExecutingFlowExecutionExceptionHandler;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.FlowExecutionException;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.TestAction;
import org.springframework.webflow.expression.spel.WebFlowSpringELExpressionParser;
import org.springframework.webflow.test.MockRequestControlContext;
/**
* Unit test for the Flow class.
*
* @author Keith Donald
*/
public class FlowTests extends TestCase {
private Flow flow = createSimpleFlow();
private Flow createSimpleFlow() {
flow = new Flow("myFlow");
ViewState state1 = new ViewState(flow, "myState1", new StubViewFactory());
state1.getTransitionSet().add(new Transition(on("submit"), to("myState2")));
new EndState(flow, "myState2");
flow.getGlobalTransitionSet().add(new Transition(on("globalEvent"), to("myState2")));
return flow;
}
public void testAddStates() {
Flow flow = new Flow("myFlow");
new EndState(flow, "myState1");
new EndState(flow, "myState2");
assertEquals("Wrong start state:", "myState1", flow.getStartState().getId());
assertEquals("State count wrong:", 2, flow.getStateCount());
assertTrue(flow.containsState("myState1"));
assertTrue(flow.containsState("myState2"));
State state = flow.getStateInstance("myState1");
assertEquals("Wrong flow:", flow.getId(), state.getFlow().getId());
assertEquals("Wrong state:", "myState1", flow.getState("myState1").getId());
assertEquals("Wrong state:", "myState2", flow.getState("myState2").getId());
}
public void testAddDuplicateState() {
Flow flow = new Flow("myFlow");
new EndState(flow, "myState1");
try {
new EndState(flow, "myState1");
fail("Duplicate state added");
} catch (IllegalArgumentException e) {
// expected
}
}
public void testAddSameStateTwice() {
Flow flow = new Flow("myFlow");
EndState state = new EndState(flow, "myState1");
try {
flow.add(state);
fail("Should have failed");
} catch (IllegalArgumentException e) {
}
assertEquals("State count wrong:", 1, flow.getStateCount());
}
public void testAddStateAlreadyInOtherFlow() {
Flow otherFlow = new Flow("myOtherFlow");
State state = new EndState(otherFlow, "myState1");
Flow flow = new Flow("myFlow");
try {
flow.add(state);
fail("Added state part of another flow");
} catch (IllegalArgumentException e) {
// expected
}
}
public void testGetStateNoStartState() {
Flow flow = new Flow("myFlow");
try {
flow.getStartState();
fail("Retrieved start state when no such state");
} catch (IllegalStateException e) {
// expected
}
}
public void testGetStateNoSuchState() {
try {
flow.getState("myState3");
fail("Returned a state that doesn't exist");
} catch (IllegalArgumentException e) {
// expected
}
}
public void testGetTransitionableState() {
assertEquals("Wrong state:", "myState1", flow.getTransitionableState("myState1").getId());
assertEquals("Wrong state:", "myState1", flow.getState("myState1").getId());
}
public void testGetStateNoSuchTransitionableState() {
try {
flow.getTransitionableState("myState2");
fail("End states aren't transtionable");
} catch (ClassCastException e) {
// expected
}
try {
flow.getTransitionableState("doesNotExist");
} catch (IllegalArgumentException e) {
// expected
}
}
public void testGetPossibleOutcomes() {
Flow flow = new Flow("myFlow");
new EndState(flow, "myState1");
new EndState(flow, "myState2");
assertEquals("myState1", flow.getPossibleOutcomes()[0]);
assertEquals("myState2", flow.getPossibleOutcomes()[1]);
}
public void testAddActions() {
flow.getStartActionList().add(new TestMultiAction());
flow.getStartActionList().add(new TestMultiAction());
flow.getEndActionList().add(new TestMultiAction());
assertEquals(2, flow.getStartActionList().size());
assertEquals(1, flow.getEndActionList().size());
}
public void testAddGlobalTransition() {
Transition t = new Transition(to("myState2"));
flow.getGlobalTransitionSet().add(t);
assertSame(t, flow.getGlobalTransitionSet().toArray()[1]);
}
public void testStart() {
MockRequestControlContext context = new MockRequestControlContext(flow);
flow.start(context, new LocalAttributeMap<Object>());
assertEquals("Wrong start state", "myState1", context.getCurrentState().getId());
}
public void testStartWithoutStartState() {
MockRequestControlContext context = new MockRequestControlContext(flow);
try {
Flow empty = new Flow("empty");
empty.start(context, null);
fail("should have failed");
} catch (IllegalStateException e) {
}
}
public void testStartWithAction() {
MockRequestControlContext context = new MockRequestControlContext(flow);
TestAction action = new TestAction();
flow.getStartActionList().add(action);
flow.start(context, new LocalAttributeMap<Object>());
assertEquals("Wrong start state", "myState1", context.getCurrentState().getId());
assertEquals(1, action.getExecutionCount());
}
public void testStartWithVariables() {
MockRequestControlContext context = new MockRequestControlContext(flow);
flow.addVariable(new FlowVariable("var1", new VariableValueFactory() {
public Object createInitialValue(RequestContext context) {
return new ArrayList<Object>();
}
public void restoreReferences(Object value, RequestContext context) {
}
}));
flow.start(context, new LocalAttributeMap<Object>());
context.getFlowScope().getRequired("var1", ArrayList.class);
}
public void testStartWithMapper() {
DefaultMapper attributeMapper = new DefaultMapper();
ExpressionParser parser = new WebFlowSpringELExpressionParser(new SpelExpressionParser());
Expression x = parser.parseExpression("attr", new FluentParserContext().evaluate(AttributeMap.class));
Expression y = parser.parseExpression("flowScope.attr",
new FluentParserContext().evaluate(RequestContext.class));
attributeMapper.addMapping(new DefaultMapping(x, y));
flow.setInputMapper(attributeMapper);
MockRequestControlContext context = new MockRequestControlContext(flow);
LocalAttributeMap<Object> sessionInput = new LocalAttributeMap<Object>();
sessionInput.put("attr", "foo");
flow.start(context, sessionInput);
assertEquals("foo", context.getFlowScope().get("attr"));
}
public void testStartWithMapperButNoInput() {
DefaultMapper attributeMapper = new DefaultMapper();
ExpressionParser parser = new WebFlowSpringELExpressionParser(new SpelExpressionParser());
Expression x = parser.parseExpression("attr", new FluentParserContext().evaluate(AttributeMap.class));
Expression y = parser.parseExpression("flowScope.attr",
new FluentParserContext().evaluate(RequestContext.class));
attributeMapper.addMapping(new DefaultMapping(x, y));
flow.setInputMapper(attributeMapper);
MockRequestControlContext context = new MockRequestControlContext(flow);
LocalAttributeMap<Object> sessionInput = new LocalAttributeMap<Object>();
flow.start(context, sessionInput);
assertTrue(context.getFlowScope().contains("attr"));
assertNull(context.getFlowScope().get("attr"));
}
public void testOnEventNullCurrentState() {
MockRequestControlContext context = new MockRequestControlContext(flow);
Event event = new Event(this, "foo");
try {
context.setCurrentEvent(event);
flow.handleEvent(context);
} catch (IllegalStateException e) {
}
}
public void testOnEventInvalidCurrentState() {
MockRequestControlContext context = new MockRequestControlContext(flow);
context.setCurrentState(flow.getStateInstance("myState2"));
Event event = new Event(this, "submit");
context.setCurrentEvent(event);
try {
context.setCurrentEvent(event);
flow.handleEvent(context);
} catch (IllegalStateException e) {
}
}
public void testOnEvent() {
MockRequestControlContext context = new MockRequestControlContext(flow);
context.setCurrentState(flow.getStateInstance("myState1"));
Event event = new Event(this, "submit");
context.setCurrentEvent(event);
assertTrue(context.getFlowExecutionContext().isActive());
context.setCurrentEvent(event);
flow.handleEvent(context);
assertTrue(!context.getFlowExecutionContext().isActive());
}
public void testOnEventGlobalTransition() {
MockRequestControlContext context = new MockRequestControlContext(flow);
context.setCurrentState(flow.getStateInstance("myState1"));
Event event = new Event(this, "globalEvent");
context.setCurrentEvent(event);
assertTrue(context.getFlowExecutionContext().isActive());
context.setCurrentEvent(event);
flow.handleEvent(context);
assertTrue(!context.getFlowExecutionContext().isActive());
}
public void testOnEventNoTransition() {
MockRequestControlContext context = new MockRequestControlContext(flow);
context.setCurrentState(flow.getStateInstance("myState1"));
Event event = new Event(this, "bogus");
context.setCurrentEvent(event);
try {
context.setCurrentEvent(event);
flow.handleEvent(context);
} catch (NoMatchingTransitionException e) {
}
}
public void testResume() {
MockRequestControlContext context = new MockRequestControlContext(flow);
context.setCurrentState(flow.getStateInstance("myState1"));
flow.resume(context);
assertTrue(context.getFlowScope().getBoolean("renderCalled"));
}
public void testEnd() {
TestAction action = new TestAction();
flow.getEndActionList().add(action);
MockRequestControlContext context = new MockRequestControlContext(flow);
LocalAttributeMap<Object> sessionOutput = new LocalAttributeMap<Object>();
flow.end(context, "finish", sessionOutput);
assertEquals(1, action.getExecutionCount());
}
public void testEndWithOutputMapper() {
DefaultMapper attributeMapper = new DefaultMapper();
ExpressionParser parser = new WebFlowSpringELExpressionParser(new SpelExpressionParser());
Expression x = parser.parseExpression("flowScope.attr",
new FluentParserContext().evaluate(RequestContext.class));
Expression y = parser.parseExpression("attr", new FluentParserContext().evaluate(MutableAttributeMap.class));
attributeMapper.addMapping(new DefaultMapping(x, y));
flow.setOutputMapper(attributeMapper);
MockRequestControlContext context = new MockRequestControlContext(flow);
context.getFlowScope().put("attr", "foo");
LocalAttributeMap<Object> sessionOutput = new LocalAttributeMap<Object>();
flow.end(context, "finish", sessionOutput);
assertEquals("foo", sessionOutput.get("attr"));
}
public void testHandleException() {
flow.getExceptionHandlerSet().add(
new TransitionExecutingFlowExecutionExceptionHandler().add(TestException.class, "myState2"));
MockRequestControlContext context = new MockRequestControlContext(flow);
context.setCurrentState(flow.getStateInstance("myState1"));
FlowExecutionException e = new FlowExecutionException(flow.getId(), flow.getStartState().getId(), "Oops!",
new TestException());
flow.handleException(e, context);
assertFalse(context.getFlowExecutionContext().isActive());
}
public void testHandleExceptionNoMatch() {
MockRequestControlContext context = new MockRequestControlContext(flow);
FlowExecutionException e = new FlowExecutionException(flow.getId(), flow.getStartState().getId(), "Oops!",
new TestException());
try {
flow.handleException(e, context);
} catch (FlowExecutionException ex) {
// expected
}
}
public void testDestroy() {
GenericApplicationContext context = new GenericApplicationContext();
context.refresh();
flow.setApplicationContext(context);
assertTrue(context.isActive());
flow.destroy();
assertFalse(context.isActive());
}
public TransitionCriteria on(String eventId) {
return new MockTransitionCriteria(eventId);
}
protected TargetStateResolver to(String stateId) {
return new DefaultTargetStateResolver(stateId);
}
}