/* * 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); } }