package org.pentaho.test.platform.engine.services.actions; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.logging.LogFactory; import org.junit.Before; import org.junit.Test; import org.pentaho.commons.connection.IPentahoStreamSource; import org.pentaho.platform.api.action.IAction; import org.pentaho.platform.api.action.IStreamingAction; import org.pentaho.platform.api.engine.ILogger; import org.pentaho.platform.api.engine.IOutputHandler; import org.pentaho.platform.api.engine.IPentahoSession; import org.pentaho.platform.api.engine.IPluginManager; import org.pentaho.platform.api.engine.ISolutionEngine; import org.pentaho.platform.api.engine.PluginBeanException; import org.pentaho.platform.api.engine.IPentahoDefinableObjectFactory.Scope; import org.pentaho.platform.api.repository.ContentException; import org.pentaho.platform.api.repository.IContentItem; import org.pentaho.platform.engine.core.output.SimpleOutputHandler; import org.pentaho.platform.engine.core.system.PentahoSystem; import org.pentaho.platform.engine.core.system.StandaloneSession; import org.pentaho.platform.engine.core.system.boot.PlatformInitializationException; import org.pentaho.platform.engine.services.outputhandler.BaseOutputHandler; import org.pentaho.platform.engine.services.solution.ActionDelegate; import org.pentaho.platform.engine.services.solution.SolutionEngine; import org.pentaho.platform.util.web.SimpleUrlFactory; import org.pentaho.test.platform.engine.core.MicroPlatform; import org.pentaho.test.platform.engine.core.PluginManagerAdapter; import org.pentaho.test.platform.engine.services.ServiceTestHelper; /** * This JUnit test verifies the proper functioning of IActions as surrogate components. * Let's not fool ourselves, these are not really unit tests, rather they are integration * tests created at as low altitude as possible to verify the correct functioning of * the ActionDelegate and proper execution of IAction. It would be too complex and * probably uselessly fragile to write real unit tests for the ActionDelegate. * <p> * ActionDelegate is the class that bridges the heavy IComponent layer with the lightweight * IAction. Essentially it brokers and manages IActions, so it is the actual unit under * test with respect to this set of JUnit tests. A test IAction, TestAction, is used to * verify that ActionDelegate is working properly. * <p> * NOTE: the only way to get around having to define a ISolutionRepository here (which * is out of scope of this project) and still be able to test action sequence resources * is to use only embedded resources in your test xactions. * It is also very important that ActionDelegate use the getInputStream * API in actionsequence-dom to fetch resources, rather than getDataSource * (which does not support embedded resources). * * @see ActionDelegate * @see IAction * @see IStreamingAction */ @SuppressWarnings("nls") public class ActionDelegateTest { private MicroPlatform booter; private ByteArrayOutputStream out; IOutputHandler outputHandler; //this list is here merely to provide a static accessor to the test action object public static List<IAction> actionList = new ArrayList<IAction>(); private static Map<String, String> veggieDataExpected = new HashMap<String, String>(); private static List<Map<String, String>> fruitDataExpected = new ArrayList<Map<String, String>>(); { veggieDataExpected.clear(); veggieDataExpected.put("name", "carrot"); veggieDataExpected.put("color", "orange"); veggieDataExpected.put("shape", "cone"); veggieDataExpected.put("texture", "bumpy"); Map<String, String> orange = new HashMap<String, String>(); orange.put("name", "orange"); orange.put("color", "orange"); orange.put("shape", "sphere"); orange.put("texture", "dimply"); Map<String, String> grapefruit = new HashMap<String, String>(); grapefruit.put("name", "grapefruit"); grapefruit.put("color", "Yellow"); grapefruit.put("shape", "sphere"); grapefruit.put("texture", "dimply"); Map<String, String> cucumber = new HashMap<String, String>(); cucumber.put("name", "cucumber"); cucumber.put("color", "green"); cucumber.put("shape", "ellipsoid"); cucumber.put("texture", "smooth"); fruitDataExpected.clear(); fruitDataExpected.add(orange); fruitDataExpected.add(grapefruit); fruitDataExpected.add(cucumber); } @Before public void init() throws PlatformInitializationException { System.setProperty("log4j.logger.org.pentaho", "INFO"); LogFactory.getLog("test").info("i'm here"); booter = new MicroPlatform(); booter.define(ISolutionEngine.class, SolutionEngine.class, Scope.GLOBAL); booter.define(IPluginManager.class, TestPluginManager.class, Scope.GLOBAL); booter.define("systemStartupSession", StandaloneSession.class, Scope.GLOBAL); booter.define("contentrepo", TestOutputHandler.class, Scope.GLOBAL); actionList.clear(); actionList.add(new TestAllIOAction()); booter.start(); PentahoSystem.get(ISolutionEngine.class).setLoggingLevel(ILogger.DEBUG); } @Test public void testIndexedInputs() { TestIndexedInputsAction action1 = new TestIndexedInputsAction(); execute("testIndexedInputs.xaction", action1); assertTrue("messages list should have elements", action1.getAllMessages().size() > 0); assertTrue("otherMessages list should have elements", action1.getOtherMessages().size() > 0); for (int i = 0; i < 3; i++) { assertEquals("action string type input \"messages_" + i + "\" is incorrect/not set", "indexed messages_" + i + " text", action1.getMessages(i)); assertEquals("action string type input \"otherMessages_" + i + "\" is incorrect/not set", "other indexed messages_" + i + " text", action1.getOtherMessages().get(i)); } assertEquals("action string type input \"scalarMessage\" is incorrect/not set", "scalar message text", action1 .getTextOfScalarMessage()); } @Test public void testMappedInput() { TestAction action1 = new TestAction(); action1.setMessageBoard("Action 1 was here!"); TestAction action2 = new TestAction(); execute("testMappedInput.xaction", action1, action2); assertEquals("action1 string type input \"message\" is incorrect/not set", "message text", action1.getMessage()); assertEquals("action2 string type input \"message\" is incorrect/not set", "internalMessage text", action2 .getMessage()); assertEquals("should see the message from action1 here", "Action 1 was here!", action2.getMessageBoard()); } @Test public void testVarArgs() { TestVarArgsAction action = new TestVarArgsAction(); execute("testVarArgs.xaction", action); //first check that normal bean inputs are working assertEquals("action1 string type input \"message\" is incorrect/not set", "message text", action.getMessage()); //then see if all the rest are passed via VarArgs assertNotNull("varArgs not set", action.getVarArgs()); assertEquals("incorrect number of varArgs set", 2, action.getVarArgs().size()); assertTrue("varArg1 was not set", action.getVarArgs().containsKey("varArg1")); assertEquals("varArg1 has incorrect value", "varArg1 text", action.getVarArgs().get("varArg1")); assertTrue("varArg2 was not set", action.getVarArgs().containsKey("varArg2")); assertNull("varArg2 has incorrect value", action.getVarArgs().get("varArg2")); } @Test public void testComponentDefinitionInputs() { TestAction action = new TestAction(); execute("testComponentDefinitionInputs.xaction", action); assertEquals("string type input \"embeddedMessage\" is incorrect/not set", "embedded message text", action .getEmbeddedMessage()); assertEquals("numeric type input \"embeddedNumber\" is incorrect/not set", new Integer(2001), action .getEmbeddedNumber()); assertNull( "bad numeric \"badEmbeddedNumber\" should not have been set, is [" + action.getBadEmbeddedNumber() + "]", action.getBadEmbeddedNumber()); /* * Elements with only a text node and no sub-elements are treated by ASD as normal inputs. However, if an element * in the component-definition has sub-elements, ASD will not return it as an input, so this test will verify that * current behavior -- that a bean property for a complex element will not be set. */ assertNull( "complex input (input with sub-elements) \"complexInputWithSubEelements\" is not currently supported as an input to an Action", action.getComplexInputWithSubEelements()); } @Test public void testCustomTypeIO() { TestAction.CustomType testCustomType = new TestAction.CustomType(); TestAction action1 = new TestAction(); action1.setCustom(testCustomType); TestAction action2 = new TestAction(); execute("testCustomTypeIO.xaction", action1, action2); assertSame("custom type object should have been passed from action1 to action2", testCustomType, action2 .getCustom()); } /** * Here we are testing that actions can handle the old convention of using dashes instead of camelCase, * for action sequence inputs and outputs. */ @Test public void testCompatibilityMode() { TestAllIOAction action = new TestAllIOAction(); execute("testCompatibilityMode.xaction", action); assertEquals("string type input \"message\" is incorrect/not set", "Test 1..2..3", action.getMessage()); assertNotNull("property-map type input \"veggie-data\" is not set", action.getVeggieData()); assertNotNull("resource \"embedded-xml-resource\" is incorrect/not set", action.getEmbeddedXmlResource()); assertEquals("output \"echo-message\" is not set or incorrect", "Test String Output", action.getEchoMessage()); } @Test public void testLogging() { TestLoggingSessionAwareAction action = new TestLoggingSessionAwareAction(); execute("testLoggingSessionAware.xaction", action); assertNotNull("logger was not set on action", action.getLogger()); } @Test public void testSessionAwareness() { TestLoggingSessionAwareAction action = new TestLoggingSessionAwareAction(); execute("testLoggingSessionAware.xaction", action); assertNotNull("session was not set on action", action.getSession()); } @Test public void testDefinitionAwareness() { TestDefinitionPreProcessingAction action = new TestDefinitionPreProcessingAction(); execute("testActionIOAllTypes.xaction", action); assertTrue("message should be in input list", action.getInputNames().contains("message")); assertTrue("addresses should be in input list", action.getInputNames().contains("addressees")); assertTrue("echoMessage should be in output list", action.getOutputNames().contains("echoMessage")); assertTrue("myContentOutput should be in output list", action.getOutputNames().contains("myContentOutput")); } @Test public void testPreProcessing() { TestDefinitionPreProcessingAction action = new TestDefinitionPreProcessingAction(); assertFalse("pre-execution method was called too early", action.isDoPreExecutionWasCalled()); assertFalse("execute method was called too early", action.isExecuteWasCalled()); execute("testActionIOAllTypes.xaction", action); assertTrue("pre-execution method was not called", action.isDoPreExecutionWasCalled()); assertTrue("execute method was not called", action.isExecuteWasCalled()); } @Test public void testStreaming() throws PlatformInitializationException, FileNotFoundException { TestStreamingAction action1 = new TestStreamingAction(); assertNull(action1.getMyContentOutputStream()); execute("testStreaming.xaction", action1); assertNotNull("output stream was not set on action1", action1.getMyContentOutputStream()); assertTrue( "the fact that we are executing an IStreamingAction should have caused the responseExpected flag to be set", outputHandler.isResponseExpected()); assertEquals("string type input \"message\" is incorrect/not set", "message input text", action1.getMessage()); assertTrue("output stream should contain this text", out.toString().contains("message input text")); } /** * Tests destination-less content outputs to make sure an Outputstream is still created and provided to the action * bean. * <p> * This test implies the following code snippets return non-null results for datasource and contentItem. * What this means to an action bean is it will be handed an outputstream for any output of type content * that it declares as an output, regardless of the fact that it may have a public counterpart with a destination. * <code> * IPentahoStreamSource datasource = runtimeContext.getDataSource(actionInput.getName()); * </code> * or * <code> * IActionParameter actionParameter = paramManager.getCurrentInput(parameterName); * IContentItem contentItem = actionParameter.getValue(); * </code> */ @Test public void testStreamingWithDestinationlessOutput() throws PlatformInitializationException, FileNotFoundException { TestStreamingAction action1 = new TestStreamingAction(); assertNull(action1.getMyContentOutputStream()); execute("testStreamingWithDestinationlessOutput.xaction", action1); assertNotNull("output stream was not set on action1", action1.getMyContentOutputStream()); assertTrue("output stream should contain this text", action1.getMyContentOutputStream().toString().contains("message input text")); } /* * Here we specify an content type output with coming from the "request" destination, which is unsupported by * current IOutputHandler implementations. */ @Test public void testUnsupportedContentOutput() throws PlatformInitializationException, FileNotFoundException { TestStreamingAction action1 = new TestStreamingAction(); assertNull(action1.getMyContentOutputStream()); execute("testUnsupportedContentOutput.xaction", action1); //by this point, we should see a nice stack trace in the log indicating the (expected) error assertNull("output stream was set on action1", action1.getMyContentOutputStream()); } @Test public void testActionIOAllTypes() throws PlatformInitializationException, FileNotFoundException { TestAllIOAction action = new TestAllIOAction(); execute("testActionIOAllTypes.xaction", action); // //Check inputs // assertEquals("string type input \"message\" is incorrect/not set", "Test 1..2..3", action.getMessage()); assertArrayEquals("addresseess input is incorrect/not set", new String[] { "joe", "suzy", "fred", "sam" }, action .getAddressees().toArray()); assertEquals("long type input \"count\" is incorrect/not set", new Long(99), action.getCount()); assertNotNull("property-map type input \"veggieData\" is not set", action.getVeggieData()); assertMapsEquivalent("property-map type input \"veggieData\" is incorrect", veggieDataExpected, action .getVeggieData()); assertNotNull("property-map-list type input \"fruitData\" is not set", action.getFruitData()); assertEquals("property-map-list type input \"fruitData\" wrong size", fruitDataExpected.size(), action .getFruitData().size()); for (int i = 0; i < fruitDataExpected.size(); i++) { assertMapsEquivalent("property-map-list type input \"fruitData\" list element [" + i + "] is incorrect", fruitDataExpected.get(i), action.getFruitData().get(i)); } // //Check resources // assertNotNull("resource \"embeddedXmlResource\" is incorrect/not set", action.getEmbeddedXmlResource()); // //Check outputs // assertEquals("output \"echoMessage\" is not set or incorrect", "Test String Output", action.getEchoMessage()); // //Check that that the various methods were invoked // assertTrue("execute method was not invoked", action.isExecuteWasCalled()); } private static boolean assertMapsEquivalent(String comment, Map<String, String> expected, Map<String, String> actual) { for (Map.Entry<String, String> entry : expected.entrySet()) { String expectedKey = entry.getKey(); String expectedVal = entry.getValue(); assertNotNull(comment + ": entry for key [" + expectedKey + "] was expect and not found", actual.get(expectedKey)); assertEquals(comment, expectedVal, actual.get(expectedKey)); } return true; } public static class TestPluginManager extends PluginManagerAdapter { private ArrayList<IAction> actions = new ArrayList<IAction>(); private int actionIdx = 0; @Override public boolean isBeanRegistered(String beanId) { return true; } public void addAction(IAction action) { this.actions.add(action); } @Override public Object getBean(String beanId) throws PluginBeanException { return actions.get(actionIdx++); } } @SuppressWarnings("unchecked") private void execute(String actionSequenceFile, IAction... actions) { TestPluginManager pm = (TestPluginManager) PentahoSystem.get(IPluginManager.class); for (IAction action : actions) { pm.addAction(action); } //content outputs will write to this stream out = new ByteArrayOutputStream(); /* * create SimpleOutputHandler (to handle outputs of type "response.content") */ outputHandler = new SimpleOutputHandler(out, false); outputHandler.setOutputPreference(IOutputHandler.OUTPUT_TYPE_DEFAULT); IPentahoSession session = new StandaloneSession("system"); ISolutionEngine solutionEngine = ServiceTestHelper.getSolutionEngine(); outputHandler.setSession(session); String xactionStr = ServiceTestHelper.getXAction("test-src/solution/test/ActionDelegateTest", actionSequenceFile); /* * execute the action sequence, providing the outputHandler created above */ solutionEngine.execute(xactionStr, actionSequenceFile, "action sequence to test the TestAction", false, true, null, false, new HashMap(), outputHandler, null, new SimpleUrlFactory(""), new ArrayList()); } public static class TestOutputHandler extends BaseOutputHandler { public static ByteArrayOutputStream BOS = new ByteArrayOutputStream(); private IContentItem contentItem = new IContentItem() { public void setName(String name) { // TODO Auto-generated method stub } public void setMimeType(String mimeType) { // TODO Auto-generated method stub } public void removeVersion(String fileId) { // TODO Auto-generated method stub } public void removeAllVersions() { // TODO Auto-generated method stub } public void makeTransient() { // TODO Auto-generated method stub } public String getUrl() { // TODO Auto-generated method stub return null; } public String getTitle() { // TODO Auto-generated method stub return null; } public Reader getReader() throws ContentException { // TODO Auto-generated method stub return null; } public String getPath() { // TODO Auto-generated method stub return null; } public OutputStream getOutputStream(String actionName) throws IOException { return BOS; } public String getName() { // TODO Auto-generated method stub return null; } public String getMimeType() { // TODO Auto-generated method stub return null; } public InputStream getInputStream() throws ContentException { // TODO Auto-generated method stub return null; } public String getId() { // TODO Auto-generated method stub return null; } @SuppressWarnings("unchecked") public List getFileVersions() { // TODO Auto-generated method stub return null; } public long getFileSize() { // TODO Auto-generated method stub return 0; } public String getFileId() { // TODO Auto-generated method stub return null; } public Date getFileDateTime() { // TODO Auto-generated method stub return null; } public IPentahoStreamSource getDataSource() { // TODO Auto-generated method stub return null; } public String getActionName() { // TODO Auto-generated method stub return null; } public void closeOutputStream() { // TODO Auto-generated method stub } }; @Override public IContentItem getFileOutputContentItem() { return contentItem; } } }