/* * Copyright(c) 2005 Center for E-Commerce Infrastructure Development, The * University of Hong Kong (HKU). All Rights Reserved. * * This software is licensed under the GNU GENERAL PUBLIC LICENSE Version 2.0 [1] * * [1] http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt */ package hk.hku.cecid.corvus.http; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URL; import java.util.Iterator; import java.util.Map; import java.util.HashMap; import java.util.LinkedHashMap; import junit.framework.TestCase; import org.apache.commons.fileupload.FileItemIterator; import org.apache.commons.fileupload.FileItemStream; import org.apache.commons.fileupload.FileUpload; import org.apache.commons.fileupload.RequestContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sun.misc.BASE64Decoder; import hk.hku.cecid.piazza.commons.io.IOHandler; import hk.hku.cecid.piazza.commons.util.FileLogger; import hk.hku.cecid.corvus.ws.data.KVPairData; import hk.hku.cecid.piazza.commons.test.utils.FixtureStore; /** * The <code>PartnershipSenderUnitTest</code> is unit test of <code>PartnershipSender</code>. * * TODO: Inadequate Test-case for error path. * * @author Twinsen Tsang * @version 1.0.0 * @since H2O 0908 */ public class PartnershipSenderUnitTest extends TestCase { // Instance logger Logger logger = LoggerFactory.getLogger(this.getClass()); // Fixture name. public static final String TEST_LOG = "test.log"; // Fixture loader private static ClassLoader FIXTURE_LOADER = FixtureStore.createFixtureLoader(false, PartnershipSender.class); // Parameters public static final int TEST_PORT = 1999; public static final String TEST_ENDPOINT = "http://localhost:" + TEST_PORT; public static final String USER_NAME = "corvus"; public static final String PASSWORD = "corvus"; /* * Since the partnership operation verifer requires Internet connectivity * and therefore i have added this dirty Proxy settings here. Changed if needed. */ static { /* System.setProperty("http.proxyHost", "proxy.cs.hku.hk"); System.setProperty("http.proxyPort", "8282"); */ } /** The testing target which is an PartnershipSender and the associated data*/ protected PartnershipSender target; protected KVPairData kvData; protected FileLogger testClassLogger; /** The helper for capturing the HTTP data */ private SimpleHttpMonitor monitor; /** Setup the fixture. */ public void setUp() throws Exception { this.initTestData(); this.initTestTarget(); logger = LoggerFactory.getLogger(this.getName()); logger.info(this.getName() + " Start "); } /** Initialize the test data **/ public void initTestData() { /* * Create an HTTP monitor which mimic the regular HTML response from the partnership administrative page. * Note, both AS2 and EbMS have the same output status line from the administrative page. */ this.monitor = new SimpleHttpMonitor(TEST_PORT) { private byte[] mockContent = "<td><a name=\"message\">Message: Partnership added successfully</a></td>".getBytes(); protected int onResponseLength(){ return mockContent.length; } protected void onResponse(final OutputStream os) throws IOException { super.onResponse(os); os.write(mockContent); } }; } /** Initialize the test target which is a PartnershipSender. */ public void initTestTarget() throws Exception { URL logURL = FIXTURE_LOADER.getResource(TEST_LOG); if (logURL == null) throw new NullPointerException("Missing fixture " + TEST_LOG + " in the fixture path"); /* * The data is constructed at this method instead of initTestData because * it has to be referred by the inner method for our testing target. */ this.kvData = new KVPairData(3); final Map props = this.kvData.getProperties(); final Map data2webForm = new LinkedHashMap(); for (int i = 0; i < 3; i++){ props.put("dataKeyName" + i, "testFormParamContent" + i); data2webForm.put("dataKeyName" + i, "testWebFormParamName" + i); } // Create a mock partnership operation mapping. final Map partnershipOpMap = new HashMap(); partnershipOpMap.put(new Integer(0), "add"); partnershipOpMap.put(new Integer(1), "delete"); partnershipOpMap.put(new Integer(2), "update"); File log = new File(logURL.getFile()); this.testClassLogger = new FileLogger(log); // Create an anonymous partnership sender and implement the abstract method for our testing. this.target = new PartnershipSender(this.testClassLogger, this.kvData){ public Map getPartnershipOperationMapping() { return partnershipOpMap; } public Map getPartnershipMapping() { return data2webForm; } }; this.target.setServiceEndPoint(TEST_ENDPOINT); this.target.setBasicAuthentication(USER_NAME, PASSWORD); } /** Test whether the add partnership request operation perform correctly **/ public void testAddPartnership() throws Exception { monitor.start(); // Start the HTTP monitor. Thread.sleep(1000); this.target.setExecuteOperation(PartnershipOp.ADD); this.target.run(); this.assertHttpRequestReceived(); } /** Test whether the update partnership request operation perform correctly **/ public void testUpdatePartnership() throws Exception { this.monitor.start(); // Start the HTTP monitor. Thread.sleep(1000); this.target.setExecuteOperation(PartnershipOp.UPDATE); this.target.run(); this.assertHttpRequestReceived(); } /** Test whether the update partnership request operation perform correctly **/ public void testDeletePartnership() throws Exception { this.monitor.start(); // Start the HTTP monitor. Thread.sleep(1000); this.target.setExecuteOperation(PartnershipOp.DELETE); this.target.run(); this.assertHttpRequestReceived(); } /** Stop the HTTP monitor preventing JVM port binding **/ public void tearDown() throws Exception { this.monitor.stop(); Thread.sleep(1500); // Make some delay for releasing the socket. logger.info(this.getName() + " End "); } /** * A Helper method which assert whether the HTTP content received in the HTTP monitor * is a multi-part form data, with basic-auth and well-formed partnership operation request. */ private void assertHttpRequestReceived() throws Exception { // Debug print information. Map headers = monitor.getHeaders(); // #0 Assertion assertFalse("No HTTP header found in the captured data.", headers.isEmpty()); Map.Entry tmp = null; Iterator itr = null; itr = headers.entrySet().iterator(); logger.info("Header information"); while (itr.hasNext()){ tmp = (Map.Entry) itr.next(); logger.info(tmp.getKey() + " : " + tmp.getValue()); } // #1 Check BASIC authentication value. String basicAuth = (String) headers.get("Authorization"); // #1 Assertion assertNotNull("No Basic Authentication found in the HTTP Header.", basicAuth); String[] authToken = basicAuth.split(" "); // There are 2 token, one is the "Basic" and another is the base64 auth value. assertTrue (authToken.length == 2); assertTrue ("Missing basic auth prefix 'Basic'", authToken[0].equalsIgnoreCase("Basic")); // #1 Decode the base64 authentication value to see whether it is "corvus:corvus". String decodedCredential = new String(new BASE64Decoder().decodeBuffer(authToken[1]), "UTF-8"); assertEquals("Invalid basic auth content", USER_NAME + ":" + PASSWORD, decodedCredential); // #2 Check content Type String contentType = monitor.getContentType(); String mediaType = contentType.split(";")[0]; assertEquals("Invalid content type", "multipart/form-data", mediaType); // #3 Check the multi-part content. // Make a request context that bridge the content from our monitor to FileUpload library. RequestContext rc = new RequestContext(){ public String getCharacterEncoding() { return "charset=ISO-8859-1"; } public int getContentLength() { return monitor.getContentLength(); } public String getContentType() { return monitor.getContentType(); } public InputStream getInputStream() { return monitor.getInputStream(); } }; FileUpload multipartParser = new FileUpload(); FileItemIterator item = multipartParser.getItemIterator(rc); FileItemStream fstream = null; /* * For each field in the partnership, we have to check the existence of the * associated field in the HTTP request. Also we check whether the content * of that web form field has same data to the field in the partnership. */ itr = this.target.getPartnershipMapping().entrySet().iterator(); Map data = ((KVPairData)this.target.properties).getProperties(); Map.Entry e; // an entry representing the partnership data to web form name mapping. String formParamName; // a temporary pointer pointing to the value in the entry. Object dataValue; // a temporary pointer pointing to the value in the partnership data. while (itr.hasNext()){ e = (Map.Entry) itr.next(); formParamName = (String) e.getValue(); // Add new part if the mapped key is not null. if (formParamName != null){ assertTrue("Insufficient number of web form parameter hit.", item.hasNext()); // Get the next multi-part element. fstream = item.next(); // Assert field name assertEquals("Missed web form parameter ?", formParamName, fstream.getFieldName()); // Assert field content dataValue = data.get(e.getKey()); if (dataValue instanceof String) { // Assert content equal. assertEquals((String)dataValue, IOHandler.readString(fstream.openStream(), null)); } else if (dataValue instanceof byte[]) { byte[] expectedBytes = (byte[])dataValue; byte[] actualBytes = IOHandler.readBytes(fstream.openStream()); // Assert byte length equal assertEquals(expectedBytes.length, actualBytes.length); for (int j = 0; j < expectedBytes.length; j++) assertEquals(expectedBytes[j], actualBytes[j]); } else { throw new IllegalArgumentException("Invalid content found in multipart."); } // Log information. logger.info("Field name found and verifed: " + fstream.getFieldName() + " content type:" + fstream.getContentType()); } } /* Check whether the partnership operation in the HTTP request is expected as i thought */ assertTrue("Missing request_action ?!", item.hasNext()); fstream = item.next(); assertEquals("request_action", fstream.getFieldName()); // Assert the request_action has same content the operation name. Map partnershipOpMap = this.target.getPartnershipOperationMapping(); assertEquals(partnershipOpMap.get(new Integer(this.target.getExecuteOperation())), IOHandler.readString(fstream.openStream(), null)); } }