Friday, December 19, 2008

Writing Webscripts 1 - Unit Testing Web Scripts

There is a big push in the agile community to develop code using a Test Driven Development approach. This could be anything from 'test first' extreme programming to assuring a certain coverage metric is achieved with unit tests that accompany delivered code. Either case, the test driven approach yields better code in a shorter period of time, is more self documented, better factored, and is not as prone to chaos effects when changed. Should web scripts be subject to unit testing? I believe it is important to test at least the data web scripts using a testing harness like xUnit that can be run during builds or in continuous integration. There are examples of this type of test within the Alfresco 3.0 source tree.

This blog entry will focus on creating a project in Eclipse that leverages the Alfresco SDK (currently 3c) to test and deploy web scripts to the Alfresco repository. These unit tests will run with Embedded Alfresco configuration to be as self-contained as possible. Although these tests will not be unit tests in the strictest sense of the word, they will help facilitate more rapid development of code with the appropriate tests within an IDE. These tests can also be run as part of continuous integration.

The sourcecode for this article is available from this site.

Step 1 - Download and Install SDK with Eclipse
Follow the Alfresco SDK wiki page to install the SDK. Essentially, you will execute the following steps:
  1. Download alfresco-sdk-3c
  2. Import into Eclipse
  3. Add dependency libraries to SDK AlfrescoEmbedded
  4. Validate with SDK FirstFoundationClient

Step 2 - Setup the project
In order to run unit tests that test data web scripts properly, we need to set up a project that can lauch Alfresco as an Embedded server, with sufficient configuration files to allow us to test our data web scripts.
Here is a summary of the steps I followed:
  1. Copy SDK FirstFoundatoinClient as a starting point.
  2. Set project dependency to SDK AlfrescoEmbedded
  3. Modify dir.root of custom-repository.properties
  4. Test run as application main of FirstFoundationClient
To create the project 'unit-test-webscripts', I copied the SDK's FirstFoundationClient in Eclipse and renamed it to 'unit-test-webscripts'. This project should already reference the SDK AlfrescoEmbedded project, and be configured to run Alfresco as Embedded, but it will need to be altered to run web scripts this way.
Modify the 'custom-repository.properties' file to reference the alfresco data root directory that you have configured when running Alfresco. It will be necessary to reference the same dir.root as alfresco when it is run standalone to share a database instance on the same machine, and allow you to test in either embedded mode or remote mode. I also added the line 'index.recovery.mode=FULL' to force the indexer to recover any out of sync references.

At this point, you should be able to run the 'main' method of the FirstFoundationClass of this project with eclipse' 'Run as Application' to make sure all is well.

NOTE: This step failed for me with a class not found error until I included a library reference to: alfresco-jlan-embed.jar in the java build path of the SDK AlfrescoEmbedded project and exported it.

After running, you should see in the console the line 'Alfresco started' to indicate the test was completed. The result of the run should be a new content item created in the repository with the name 'Foundation API sample (current Time in Milliseconds)' within the 'Company Home' folder. Start tomcat where alfresco is deployed and log in to view the Company Home folder. You should see the new files added. Make sure to stop tomcat before running embedded tests.

Step 3 - Configure to Run Web scripts via embedded alfresco

Once the project is copied and we can run the test class, we can now configure it to run our test webscripts. To summarize the steps:

  1. Create a source folder 'test' to contain configuration files and unit test code
  2. Create a folder 'alfresco' within the 'test' source folder to which we will copy the necessary spring context files
  3. copy web-scripts-application-context.xml from $TOMCAT_HOME/webapps/alfresco/WEB-INF/classes/alfresco to test/alfresco
  4. copy webscript-framework-application-context.xml from $TOMCAT_HOME/webapps/alfresco/WEB-INF/classes/alfresco to test/alfresco
  5. copy web-scripts-application-context-test.xml from $TOMCAT_HOME/webapps/alfresco/WEB-INF/classes/alfresco to test/alfresco
  6. comment out web Script messages bean 'webscripts.resources' of webscript-framework-application-context.xml
  7. copy web-scripts-config.xml from $TOMCAT_HOME/webapps/alfresco/WEB-INF/classes/alfresco to test/alfresco
  8. comment out bean 'webscript.org.alfresco.repository.dictionary.getchildassoc.get' from web-scripts-application-context.xml since class 'org.afresco.repo.web.scripts.dictionary.GetChildAssociationDef' no longer exists in jars
  9. comment out bean 'webscript.org.alfresco.repository.dictionary.getchildassocs.get' from web-scripts-application-context.xml since class 'org.afresco.repo.web.scripts.dictionary.GetChildAssociationDefs' no longer exists in jars
  10. create required directory source/alfresco/templates/webscripts
  11. create required directory source/alfresco/webscripts
  12. copy webscript-framework-config.xml from $TOMCAT_HOME/webapps/alfresco/WEB-INF/classes/alfresco to test/alfresco
  13. create required directory source/alfresco/templates/activities
  14. create directory source/alfresco/extension/templates/webscripts where our webscritps will go
  15. copy 'status.ftl' from $TOMCAT_HOME/webapps/alfresco/WEB-INF/classes/alfresco to test/alfresco/extension/templates/webscripts to the new extension/templates/webscripts directory to provide a default status template
Now the project is set up to run our webscripts.

Step 4 - Write the unit test and webscript

Add our data webscript to test into the source/alfresco/extension/templates/webscripts folder
Create 'test.get.desc.xml' with the following contents:
<webscript>
<shortname>test</shortname>
<description>Return runas user name</description>
<format>argument</format>
<url>/someco/test</url>
<authentication runas="RunAsOne">user</authentication>
<transaction>required</transaction>
</webscript>
This description describes the webscript 'someco/test' that will run as the user RunAsOne.
Create 'test.get.html.ftl' with the following contents:
${userName!"<notset>"}
This template will display the value of userName.
Create 'test.get.js' with the following code:
model.userName = person.properties.userName;
This script will set the value of userName with the current userName we are running as.
Thats it for the webscript. Now create the unit test into the test/org/someco directory called 'RunAsTest' to test the data webscript:
package org.someco;

import org.alfresco.model.ContentModel;
import org.alfresco.repo.web.scripts.BaseWebScriptTest;
import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.util.PropertyMap;
import org.alfresco.web.scripts.TestWebScriptServer.GetRequest;
import org.alfresco.web.scripts.TestWebScriptServer.Response;

public class RunAsTest extends BaseWebScriptTest {
private AuthenticationService authenticationService;
private PersonService personService;

private static final String USER_ONE = "RunAsOne";

private static final String URL_GET_CONTENT = "/someco/test";

@Override
protected void setUp() throws Exception
{
super.setUp();

this.authenticationService = (AuthenticationService) getServer().getApplicationContext().getBean(
"AuthenticationService");
this.personService = (PersonService) getServer().getApplicationContext().getBean("PersonService");

// Create users
createUser(USER_ONE);
}

private void createUser(String userName)
{
if (this.authenticationService.authenticationExists(userName) == false)
{
this.authenticationService.createAuthentication(userName, "PWD".toCharArray());

PropertyMap ppOne = new PropertyMap(4);
ppOne.put(ContentModel.PROP_USERNAME, userName);
ppOne.put(ContentModel.PROP_FIRSTNAME, "firstName");
ppOne.put(ContentModel.PROP_LASTNAME, "lastName");
ppOne.put(ContentModel.PROP_EMAIL, "email@email.com");
ppOne.put(ContentModel.PROP_JOBTITLE, "jobTitle");

this.personService.createPerson(ppOne);
}
}

@Override
protected void tearDown() throws Exception
{
super.tearDown();
}

public void testRunAs() throws Exception
{
Response response = sendRequest(new GetRequest(URL_GET_CONTENT), 200, "admin");
assertEquals(USER_ONE, response.getContentAsString());
}

}

This test creates 'UserOne', runs the 'someco/test' webscript, and checks the result code as 200 (success) and to see if the user name this script 'run as' was returned in the response.

Step 5 - Run the unit test

In order to see results, you should copy the log4j.properties file from $TOMCAT_HOME/webapps/alfresco/WEB_INF/classes into your source directory and configure log4j.properties to show debugging information for running web scripts by setting 'log4j.logger.org.alfresco.web.scripts=debug' and 'log4j.logger.org.alfresco.repo.web.scripts=debug' as recommended in the logging session of the 3.0 Web scripts Framework page of alfresco wiki.

To run the test, simply select the test/com/someco/RunAsTest class in Eclipse and select the 'run as' JUnit test. The test should pass and the console should show something like 'Processed script url (get) /someco/test in 517.601ms' as its last line.

Step 6 - Deploy the web script
Once confirmed, the web script tested can be deployed either by uploading it to the Company Home/Data Dictionary/Web Scripts Extensions space or add the web scripts to the $TOMCAT_HOME/webapps/alfresco/WEB-INF/classes/alfresco/extension/templates/webscripts folder and restarting alfresco.

6 comments:

Jean Barmash said...

Hi Ed,

Great Stuff! I am working on updating Alfresco SDK, and would love to include your project as part of it (saves me rewriting it). Would you be interested in contributing it? If so, please email me at jbarmash at alfresco dot com.

Jean

Anonymous said...

Ed, I was working on the code , but have problem finding alfresco-jlan-embed.jar , I am new to alfresco so any help is appreciated.
thanks
Abe

Unknown said...

Hi Ed, I was trying to test the code you provided but I have been having errors and have made little progress in last few days. I am new to alfresco and want to work on data web script to leverage business requirement changes to existing repository db. I also could not fnd alfresco-jlan-embed.jar. Any help is greatly appreciated.
thanks

Unknown said...

Hi Ed, Is alfresco-jlan-embed-3.2_dev same as alfresco-jlan-embed.jar. I keep getting the followin error even after adding the above jar to SDK AlfrescoEmbedded libraries.

--------------------------------------

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'luceneFullTextSearchIndexer' defined in class path resource [alfresco/core-services-context.xml]: Cannot resolve reference to bean 'indexerAndSearcherFactory' while setting bean property 'indexerAndSearcherFactory'; nested exception is.......
--------------------------------------

I am in process of testing data webscripts. I spend few days on this error now so :( , so any help is greatly appreciated.
Abe

edlovesjava said...

alfresco-jlan-embed.jar was an older alfresco source build jar name for the jar of the alfresco-jlan project. Now the name of the jar in the dist is alfresco-jlan-embed-3.2_dev.jar. Since the alfresco-community-sdk-3c does not include this jar in the SDK AlfrescoEmbedded or SDK AlfrescoRemote projects, we have to include it ourselves :(.
The log snippet you provided doesn't show enough to diagnose. Perhaps a bit more?

AmunTeam said...

great artcile, but it doesn't work for me because first im using alfresco 4.0.d then i can't find this file : webscript-framework-application-context.xml.

One question how i can't do the some in my version of alfresco ?.